Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mappersmith

Package Overview
Dependencies
Maintainers
3
Versions
121
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mappersmith - npm Package Compare versions

Comparing version 0.1.1 to 0.2.0

src/create-gateway.js

333

build/mappersmith.js

@@ -5,20 +5,56 @@ !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Mappersmith=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){

Gateway: require('./src/gateway'),
Mapper: require('./src/mapper.js'),
Mapper: require('./src/mapper'),
VanillaGateway: require('./src/gateway/vanilla-gateway'),
JQueryGateway: require('./src/gateway/jquery-gateway'),
forge: function(manifest, gateway) {
return new this.Mapper(
manifest,
gateway || this.VanillaGateway
).build();
forge: require('./src/forge'),
createGateway: require('./src/create-gateway')
}
},{"./src/create-gateway":2,"./src/forge":3,"./src/gateway":4,"./src/gateway/jquery-gateway":5,"./src/gateway/vanilla-gateway":6,"./src/mapper":7,"./src/utils":8}],2:[function(require,module,exports){
var Utils = require('./utils');
var Gateway = require('./gateway');
module.exports = function(methods) {
var newGateway = function() {
this.init && this.init();
return Gateway.apply(this, arguments);
}
newGateway.prototype = Utils.extend({}, Gateway.prototype, methods);
return newGateway;
}
},{"./src/gateway":2,"./src/gateway/jquery-gateway":3,"./src/gateway/vanilla-gateway":4,"./src/mapper.js":5,"./src/utils":6}],2:[function(require,module,exports){
},{"./gateway":4,"./utils":8}],3:[function(require,module,exports){
var Mapper = require('./mapper');
var VanillaGateway = require('./gateway/vanilla-gateway');
module.exports = function(manifest, gateway, bodyAttr) {
return new Mapper(
manifest,
gateway || VanillaGateway,
bodyAttr || 'body'
).build();
}
},{"./gateway/vanilla-gateway":6,"./mapper":7}],4:[function(require,module,exports){
var Utils = require('./utils');
var Gateway = function(url, method, opts) {
this.url = url;
this.method = method;
this.opts = opts || {};
/**
* Gateway constructor
* @param args {Object} with url, method, params and opts
*
* * url: The full url of the resource, including host and query strings
* * method: The name of the HTTP method (get, head, post, put, delete and patch)
* to be used, in lower case.
* * params: request params (query strings, url params and body)
* * opts: gateway implementation specific options
*/
var Gateway = function(args) {
this.url = args.url;
this.method = args.method;
this.processor = args.processor;
this.params = args.params || {};
this.body = args.body;
this.opts = args.opts || {};

@@ -33,3 +69,3 @@ this.successCallback = Utils.noop;

call: function() {
this[this.method]();
this[this.method].apply(this, arguments);
return this;

@@ -39,3 +75,9 @@ },

success: function(callback) {
this.successCallback = callback;
if (this.processor !== undefined) {
this.successCallback = function(data) {
callback(this.processor(data));
}
} else {
this.successCallback = callback;
}
return this;

@@ -54,2 +96,6 @@ },

shouldEmulateHTTP: function(method) {
return !!(this.opts.emulateHTTP && /^(delete|put|patch)/i.test(method));
},
get: function() {

@@ -79,28 +125,57 @@ throw new Utils.Exception('Gateway#get not implemented');

},{"./utils":6}],3:[function(require,module,exports){
},{"./utils":8}],5:[function(require,module,exports){
var Utils = require('../utils');
var Gateway = require('../gateway');
var CreateGateway = require('../create-gateway');
var JQueryGateway = function() {
if (window.jQuery === undefined) {
throw new Utils.Exception(
'JQueryGateway requires jQuery but it was not found! ' +
'Change the gateway implementation or add jQuery on the page'
);
}
var JQueryGateway = module.exports = CreateGateway({
return Gateway.apply(this, arguments);
}
init: function() {
if (window.jQuery === undefined) {
throw new Utils.Exception(
'JQueryGateway requires jQuery but it was not found! ' +
'Change the gateway implementation or add jQuery on the page'
);
}
},
JQueryGateway.prototype = Utils.extend({}, Gateway.prototype, {
jQueryAjax: function(config) {
jQuery.ajax(Utils.extend({url: this.url}, config)).
done(function() { this.successCallback.apply(this, arguments) }.bind(this)).
fail(function() { this.failCallback.apply(this, arguments) }.bind(this)).
always(function() { this.completeCallback.apply(this, arguments) }.bind(this));
},
get: function() {
var defaults = {dataType: "json", url: this.url};
var config = Utils.extend(defaults, this.opts);
this.jQueryAjax(this.opts);
return this;
},
jQuery.ajax(config).
done(function() { this.successCallback.apply(this, arguments) }.bind(this)).
fail(function() { this.failCallback.apply(this, arguments) }.bind(this)).
always(function() { this.completeCallback.apply(this, arguments) }.bind(this));
post: function() {
return this._performRequest('POST');
},
put: function() {
return this._performRequest('PUT');
},
patch: function() {
return this._performRequest('PATCH');
},
delete: function() {
return this._performRequest('DELETE');
},
_performRequest: function(method) {
var requestMethod = method;
if (this.shouldEmulateHTTP(method)) {
requestMethod = 'POST';
this.body = this.body || {};
if (typeof this.body === 'object') this.body._method = method;
this.opts.headers = Utils.extend(this.opts.header, {'X-HTTP-Method-Override': method});
}
var defaults = {type: requestMethod, data: Utils.params(this.body)};
this.jQueryAjax(Utils.extend(defaults, this.opts));
return this;

@@ -111,17 +186,9 @@ }

module.exports = JQueryGateway;
},{"../gateway":2,"../utils":6}],4:[function(require,module,exports){
},{"../create-gateway":2,"../utils":8}],6:[function(require,module,exports){
var Utils = require('../utils');
var Gateway = require('../gateway');
var CreateGateway = require('../create-gateway');
var VanillaGateway = function() {
return Gateway.apply(this, arguments);
}
var VanillaGateway = module.exports = CreateGateway({
VanillaGateway.prototype = Utils.extend({}, Gateway.prototype, {
get: function() {
var request = new XMLHttpRequest();
configureCallbacks: function(request) {
request.onload = function() {

@@ -132,3 +199,9 @@ var data = null;

if (request.status >= 200 && request.status < 400) {
data = JSON.parse(request.responseText);
if (request.getResponseHeader('Content-Type') === 'application/json') {
data = JSON.parse(request.responseText);
} else {
data = request.responseText;
}
this.successCallback(data);

@@ -143,3 +216,3 @@

} finally {
this.completeCallback(data);
this.completeCallback(data, request);
}

@@ -157,5 +230,49 @@

}
},
get: function() {
var request = new XMLHttpRequest();
this.configureCallbacks(request);
request.open('GET', this.url, true);
request.send();
},
post: function() {
this._performRequest('POST');
},
put: function() {
this._performRequest('PUT');
},
patch: function() {
this._performRequest('PATCH');
},
delete: function() {
this._performRequest('DELETE');
},
_performRequest: function(method) {
var emulateHTTP = this.shouldEmulateHTTP(method);
var requestMethod = method;
var request = new XMLHttpRequest();
this.configureCallbacks(request);
if (emulateHTTP) {
this.body = this.body || {};
if (typeof this.body === 'object') this.body._method = method;
requestMethod = 'POST';
}
request.open(requestMethod, this.url, true);
if (emulateHTTP) request.setRequestHeader('X-HTTP-Method-Override', method);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
var args = [];
if (this.body !== undefined) {
args.push(Utils.params(this.body));
}
request.send.apply(request, args);
}

@@ -165,9 +282,18 @@

module.exports = VanillaGateway;
},{"../create-gateway":2,"../utils":8}],7:[function(require,module,exports){
var Utils = require('./utils');
},{"../gateway":2,"../utils":6}],5:[function(require,module,exports){
var Mapper = function(manifest, Gateway) {
/**
* Mapper constructor
* @param manifest {Object} with host and resources
* @param gateway {Object} with an implementation of {Mappersmith.Gateway}
* @param bodyAttr {String}, name of the body attribute used for HTTP methods
* such as POST and PUT
*/
var Mapper = function(manifest, Gateway, bodyAttr) {
this.manifest = manifest;
this.host = this.manifest.host;
this.rules = this.manifest.rules || [];
this.Gateway = Gateway;
this.host = this.manifest.host;
this.bodyAttr = bodyAttr;
}

@@ -191,2 +317,13 @@

var descriptor = methods[methodName];
if (typeof(descriptor) === 'string') {
var compactDefinitionMethod = descriptor.match( /^(get|post|delete|put|patch):(.*)/ )
if (compactDefinitionMethod != null) {
descriptor = {method: compactDefinitionMethod[1], path: compactDefinitionMethod[2]};
} else {
descriptor = {method: 'get', path: descriptor};
}
}
var httpMethod = (descriptor.method || 'get').toLowerCase();

@@ -196,3 +333,4 @@

httpMethod,
descriptor.path
descriptor.path,
descriptor.processor
);

@@ -206,6 +344,10 @@

urlFor: function(path, urlParams) {
// using `Utils.extend` avoids undesired changes to `urlParams`
var params = Utils.extend({}, urlParams);
var normalizedPath = /^\//.test(path) ? path : '/' + path;
var host = this.host.replace(/\/$/, '');
var params = urlParams || {};
var normalizedPath = /^\//.test(path) ? path : '/' + path;
// does not includes the body param into the URL
delete params[this.bodyAttr];
Object.keys(params).forEach(function(key) {

@@ -221,9 +363,6 @@ var value = params[key];

var paramsString = Object.keys(params).
filter(function(key) { return key !== undefined && key !== null}).
map(function(key){ return key + '=' + params[key]}).
join('&');
if (paramsString.length !== 0)
var paramsString = Utils.params(params);
if (paramsString.length !== 0) {
paramsString = '?' + paramsString;
}

@@ -233,3 +372,12 @@ return host + normalizedPath + paramsString;

newGatewayRequest: function(method, path) {
newGatewayRequest: function(method, path, processor) {
var rules = this.rules.
filter(function(rule) { return rule.match === undefined || rule.match.test(path) }).
reduce(function(context, rule) {
var mergedGateway = Utils.extend(context.gateway, rule.values.gateway);
context = Utils.extend(context, rule.values);
context.gateway = mergedGateway;
return context;
}, {});
return function(params, callback, opts) {

@@ -242,4 +390,16 @@ if (typeof params === 'function') {

var url = this.urlFor(path, params);
return new this.Gateway(url, method, opts).
opts = Utils.extend({}, opts, rules.gateway);
if(Utils.isObjEmpty(opts)) opts = undefined;
var body = (params || {})[this.bodyAttr];
var gatewayOpts = Utils.extend({}, {
url: this.urlFor(path, params),
method: method,
processor: processor || rules.processor,
params: params,
body: body,
opts: opts
})
return new this.Gateway(gatewayOpts).
success(callback).

@@ -255,6 +415,15 @@ call();

},{}],6:[function(require,module,exports){
},{"./utils":8}],8:[function(require,module,exports){
var Utils = module.exports = {
r20: /%20/g,
noop: function() {},
isObjEmpty: function(obj) {
for(var key in obj) {
if(obj.hasOwnProperty(key)) return false;
}
return true;
},
extend: function(out) {

@@ -268,3 +437,3 @@ out = out || {};

for (var key in arguments[i]) {
if (arguments[i].hasOwnProperty(key))
if (arguments[i].hasOwnProperty(key) && arguments[i][key] !== undefined)
out[key] = arguments[i][key];

@@ -277,2 +446,40 @@ }

params: function(entry) {
if (typeof entry !== 'object') {
return entry;
}
var validKeys = function(entry) {
return Object.keys(entry).
filter(function(key) {
return entry[key] !== undefined &&
entry[key] !== null
});
}
var buildRecursive = function(key, value, suffix) {
suffix = suffix || '';
var isArray = Array.isArray(value);
var isObject = typeof value === 'object';
if (!isArray && !isObject) {
return encodeURIComponent(key + suffix) + '=' + encodeURIComponent(value);
}
if (isArray) {
return value.map(function(v) { return buildRecursive(key, v, suffix + '[]') }).
join('&');
}
return validKeys(value).
map(function(k) { return buildRecursive(key, value[k], suffix + '[' + k + ']') }).
join('&');
}
return validKeys(entry).
map(function(key) { return buildRecursive(key, entry[key]) }).
join('&').
replace(Utils.r20, '+');
},
Exception: function(message) {

@@ -279,0 +486,0 @@ this.message = message;

3

build/mappersmith.min.js

@@ -1,1 +0,2 @@

!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Mappersmith=e()}}(function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module){module.exports={Utils:require("./src/utils"),Gateway:require("./src/gateway"),Mapper:require("./src/mapper.js"),VanillaGateway:require("./src/gateway/vanilla-gateway"),JQueryGateway:require("./src/gateway/jquery-gateway"),forge:function(manifest,gateway){return new this.Mapper(manifest,gateway||this.VanillaGateway).build()}}},{"./src/gateway":2,"./src/gateway/jquery-gateway":3,"./src/gateway/vanilla-gateway":4,"./src/mapper.js":5,"./src/utils":6}],2:[function(require,module){var Utils=require("./utils"),Gateway=function(url,method,opts){this.url=url,this.method=method,this.opts=opts||{},this.successCallback=Utils.noop,this.failCallback=Utils.noop,this.completeCallback=Utils.noop};Gateway.prototype={call:function(){return this[this.method](),this},success:function(callback){return this.successCallback=callback,this},fail:function(callback){return this.failCallback=callback,this},complete:function(callback){return this.completeCallback=callback,this},get:function(){throw new Utils.Exception("Gateway#get not implemented")},post:function(){throw new Utils.Exception("Gateway#post not implemented")},put:function(){throw new Utils.Exception("Gateway#put not implemented")},"delete":function(){throw new Utils.Exception("Gateway#delete not implemented")},patch:function(){throw new Utils.Exception("Gateway#patch not implemented")}},module.exports=Gateway},{"./utils":6}],3:[function(require,module){var Utils=require("../utils"),Gateway=require("../gateway"),JQueryGateway=function(){if(void 0===window.jQuery)throw new Utils.Exception("JQueryGateway requires jQuery but it was not found! Change the gateway implementation or add jQuery on the page");return Gateway.apply(this,arguments)};JQueryGateway.prototype=Utils.extend({},Gateway.prototype,{get:function(){var defaults={dataType:"json",url:this.url},config=Utils.extend(defaults,this.opts);return jQuery.ajax(config).done(function(){this.successCallback.apply(this,arguments)}.bind(this)).fail(function(){this.failCallback.apply(this,arguments)}.bind(this)).always(function(){this.completeCallback.apply(this,arguments)}.bind(this)),this}}),module.exports=JQueryGateway},{"../gateway":2,"../utils":6}],4:[function(require,module){var Utils=require("../utils"),Gateway=require("../gateway"),VanillaGateway=function(){return Gateway.apply(this,arguments)};VanillaGateway.prototype=Utils.extend({},Gateway.prototype,{get:function(){var request=new XMLHttpRequest;request.onload=function(){var data=null;try{request.status>=200&&request.status<400?(data=JSON.parse(request.responseText),this.successCallback(data)):this.failCallback(request)}catch(e){this.failCallback(request)}finally{this.completeCallback(data)}}.bind(this),request.onerror=function(){this.failCallback.apply(this,arguments),this.completeCallback.apply(this,arguments)}.bind(this),this.opts.configure&&this.opts.configure(request),request.open("GET",this.url,!0),request.send()}}),module.exports=VanillaGateway},{"../gateway":2,"../utils":6}],5:[function(require,module){var Mapper=function(manifest,Gateway){this.manifest=manifest,this.Gateway=Gateway,this.host=this.manifest.host};Mapper.prototype={build:function(){return Object.keys(this.manifest.resources||{}).map(function(name){return this.buildResource(name)}.bind(this)).reduce(function(context,resource){return context[resource.name]=resource.methods,context},{})},buildResource:function(resourceName){var methods=this.manifest.resources[resourceName];return Object.keys(methods).reduce(function(context,methodName){var descriptor=methods[methodName],httpMethod=(descriptor.method||"get").toLowerCase();return context.methods[methodName]=this.newGatewayRequest(httpMethod,descriptor.path),context}.bind(this),{name:resourceName,methods:{}})},urlFor:function(path,urlParams){var host=this.host.replace(/\/$/,""),params=urlParams||{},normalizedPath=/^\//.test(path)?path:"/"+path;Object.keys(params).forEach(function(key){var value=params[key],pattern="{"+key+"}";new RegExp(pattern).test(normalizedPath)&&(normalizedPath=normalizedPath.replace("{"+key+"}",value),delete params[key])});var paramsString=Object.keys(params).filter(function(key){return void 0!==key&&null!==key}).map(function(key){return key+"="+params[key]}).join("&");return 0!==paramsString.length&&(paramsString="?"+paramsString),host+normalizedPath+paramsString},newGatewayRequest:function(method,path){return function(params,callback,opts){"function"==typeof params&&(opts=callback,callback=params,params=void 0);var url=this.urlFor(path,params);return new this.Gateway(url,method,opts).success(callback).call()}.bind(this)}},module.exports=Mapper},{}],6:[function(require,module){module.exports={noop:function(){},extend:function(out){out=out||{};for(var i=1;i<arguments.length;i++)if(arguments[i])for(var key in arguments[i])arguments[i].hasOwnProperty(key)&&(out[key]=arguments[i][key]);return out},Exception:function(message){this.message=message,this.toString=function(){return"[Mappersmith] "+this.message}}}},{}]},{},[1])(1)});
/*! Mappersmith - https://github.com/tulios/mappersmith - Generated by browserify */
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Mappersmith=e()}}(function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module){module.exports={Utils:require("./src/utils"),Gateway:require("./src/gateway"),Mapper:require("./src/mapper"),VanillaGateway:require("./src/gateway/vanilla-gateway"),JQueryGateway:require("./src/gateway/jquery-gateway"),forge:require("./src/forge"),createGateway:require("./src/create-gateway")}},{"./src/create-gateway":2,"./src/forge":3,"./src/gateway":4,"./src/gateway/jquery-gateway":5,"./src/gateway/vanilla-gateway":6,"./src/mapper":7,"./src/utils":8}],2:[function(require,module){var Utils=require("./utils"),Gateway=require("./gateway");module.exports=function(methods){var newGateway=function(){return this.init&&this.init(),Gateway.apply(this,arguments)};return newGateway.prototype=Utils.extend({},Gateway.prototype,methods),newGateway}},{"./gateway":4,"./utils":8}],3:[function(require,module){var Mapper=require("./mapper"),VanillaGateway=require("./gateway/vanilla-gateway");module.exports=function(manifest,gateway,bodyAttr){return new Mapper(manifest,gateway||VanillaGateway,bodyAttr||"body").build()}},{"./gateway/vanilla-gateway":6,"./mapper":7}],4:[function(require,module){var Utils=require("./utils"),Gateway=function(args){this.url=args.url,this.method=args.method,this.processor=args.processor,this.params=args.params||{},this.body=args.body,this.opts=args.opts||{},this.successCallback=Utils.noop,this.failCallback=Utils.noop,this.completeCallback=Utils.noop};Gateway.prototype={call:function(){return this[this.method].apply(this,arguments),this},success:function(callback){return this.successCallback=void 0!==this.processor?function(data){callback(this.processor(data))}:callback,this},fail:function(callback){return this.failCallback=callback,this},complete:function(callback){return this.completeCallback=callback,this},shouldEmulateHTTP:function(method){return!(!this.opts.emulateHTTP||!/^(delete|put|patch)/i.test(method))},get:function(){throw new Utils.Exception("Gateway#get not implemented")},post:function(){throw new Utils.Exception("Gateway#post not implemented")},put:function(){throw new Utils.Exception("Gateway#put not implemented")},delete:function(){throw new Utils.Exception("Gateway#delete not implemented")},patch:function(){throw new Utils.Exception("Gateway#patch not implemented")}},module.exports=Gateway},{"./utils":8}],5:[function(require,module){{var Utils=require("../utils"),CreateGateway=require("../create-gateway");module.exports=CreateGateway({init:function(){if(void 0===window.jQuery)throw new Utils.Exception("JQueryGateway requires jQuery but it was not found! Change the gateway implementation or add jQuery on the page")},jQueryAjax:function(config){jQuery.ajax(Utils.extend({url:this.url},config)).done(function(){this.successCallback.apply(this,arguments)}.bind(this)).fail(function(){this.failCallback.apply(this,arguments)}.bind(this)).always(function(){this.completeCallback.apply(this,arguments)}.bind(this))},get:function(){return this.jQueryAjax(this.opts),this},post:function(){return this._performRequest("POST")},put:function(){return this._performRequest("PUT")},patch:function(){return this._performRequest("PATCH")},delete:function(){return this._performRequest("DELETE")},_performRequest:function(method){var requestMethod=method;this.shouldEmulateHTTP(method)&&(requestMethod="POST",this.body=this.body||{},"object"==typeof this.body&&(this.body._method=method),this.opts.headers=Utils.extend(this.opts.header,{"X-HTTP-Method-Override":method}));var defaults={type:requestMethod,data:Utils.params(this.body)};return this.jQueryAjax(Utils.extend(defaults,this.opts)),this}})}},{"../create-gateway":2,"../utils":8}],6:[function(require,module){{var Utils=require("../utils"),CreateGateway=require("../create-gateway");module.exports=CreateGateway({configureCallbacks:function(request){request.onload=function(){var data=null;try{request.status>=200&&request.status<400?(data="application/json"===request.getResponseHeader("Content-Type")?JSON.parse(request.responseText):request.responseText,this.successCallback(data)):this.failCallback(request)}catch(e){this.failCallback(request)}finally{this.completeCallback(data,request)}}.bind(this),request.onerror=function(){this.failCallback.apply(this,arguments),this.completeCallback.apply(this,arguments)}.bind(this),this.opts.configure&&this.opts.configure(request)},get:function(){var request=new XMLHttpRequest;this.configureCallbacks(request),request.open("GET",this.url,!0),request.send()},post:function(){this._performRequest("POST")},put:function(){this._performRequest("PUT")},patch:function(){this._performRequest("PATCH")},delete:function(){this._performRequest("DELETE")},_performRequest:function(method){var emulateHTTP=this.shouldEmulateHTTP(method),requestMethod=method,request=new XMLHttpRequest;this.configureCallbacks(request),emulateHTTP&&(this.body=this.body||{},"object"==typeof this.body&&(this.body._method=method),requestMethod="POST"),request.open(requestMethod,this.url,!0),emulateHTTP&&request.setRequestHeader("X-HTTP-Method-Override",method),request.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");var args=[];void 0!==this.body&&args.push(Utils.params(this.body)),request.send.apply(request,args)}})}},{"../create-gateway":2,"../utils":8}],7:[function(require,module){var Utils=require("./utils"),Mapper=function(manifest,Gateway,bodyAttr){this.manifest=manifest,this.host=this.manifest.host,this.rules=this.manifest.rules||[],this.Gateway=Gateway,this.bodyAttr=bodyAttr};Mapper.prototype={build:function(){return Object.keys(this.manifest.resources||{}).map(function(name){return this.buildResource(name)}.bind(this)).reduce(function(context,resource){return context[resource.name]=resource.methods,context},{})},buildResource:function(resourceName){var methods=this.manifest.resources[resourceName];return Object.keys(methods).reduce(function(context,methodName){var descriptor=methods[methodName];if("string"==typeof descriptor){var compactDefinitionMethod=descriptor.match(/^(get|post|delete|put|patch):(.*)/);descriptor=null!=compactDefinitionMethod?{method:compactDefinitionMethod[1],path:compactDefinitionMethod[2]}:{method:"get",path:descriptor}}var httpMethod=(descriptor.method||"get").toLowerCase();return context.methods[methodName]=this.newGatewayRequest(httpMethod,descriptor.path,descriptor.processor),context}.bind(this),{name:resourceName,methods:{}})},urlFor:function(path,urlParams){var params=Utils.extend({},urlParams),normalizedPath=/^\//.test(path)?path:"/"+path,host=this.host.replace(/\/$/,"");delete params[this.bodyAttr],Object.keys(params).forEach(function(key){var value=params[key],pattern="{"+key+"}";new RegExp(pattern).test(normalizedPath)&&(normalizedPath=normalizedPath.replace("{"+key+"}",value),delete params[key])});var paramsString=Utils.params(params);return 0!==paramsString.length&&(paramsString="?"+paramsString),host+normalizedPath+paramsString},newGatewayRequest:function(method,path,processor){var rules=this.rules.filter(function(rule){return void 0===rule.match||rule.match.test(path)}).reduce(function(context,rule){var mergedGateway=Utils.extend(context.gateway,rule.values.gateway);return context=Utils.extend(context,rule.values),context.gateway=mergedGateway,context},{});return function(params,callback,opts){"function"==typeof params&&(opts=callback,callback=params,params=void 0),opts=Utils.extend({},opts,rules.gateway),Utils.isObjEmpty(opts)&&(opts=void 0);var body=(params||{})[this.bodyAttr],gatewayOpts=Utils.extend({},{url:this.urlFor(path,params),method:method,processor:processor||rules.processor,params:params,body:body,opts:opts});return new this.Gateway(gatewayOpts).success(callback).call()}.bind(this)}},module.exports=Mapper},{"./utils":8}],8:[function(require,module){var Utils=module.exports={r20:/%20/g,noop:function(){},isObjEmpty:function(obj){for(var key in obj)if(obj.hasOwnProperty(key))return!1;return!0},extend:function(out){out=out||{};for(var i=1;i<arguments.length;i++)if(arguments[i])for(var key in arguments[i])arguments[i].hasOwnProperty(key)&&void 0!==arguments[i][key]&&(out[key]=arguments[i][key]);return out},params:function(entry){if("object"!=typeof entry)return entry;var validKeys=function(entry){return Object.keys(entry).filter(function(key){return void 0!==entry[key]&&null!==entry[key]})},buildRecursive=function(key,value,suffix){suffix=suffix||"";var isArray=Array.isArray(value),isObject="object"==typeof value;return isArray||isObject?isArray?value.map(function(v){return buildRecursive(key,v,suffix+"[]")}).join("&"):validKeys(value).map(function(k){return buildRecursive(key,value[k],suffix+"["+k+"]")}).join("&"):encodeURIComponent(key+suffix)+"="+encodeURIComponent(value)};return validKeys(entry).map(function(key){return buildRecursive(key,entry[key])}).join("&").replace(Utils.r20,"+")},Exception:function(message){this.message=message,this.toString=function(){return"[Mappersmith] "+this.message}}}},{}]},{},[1])(1)});
module.exports = {
Utils: require('./src/utils'),
Gateway: require('./src/gateway'),
Mapper: require('./src/mapper.js'),
Mapper: require('./src/mapper'),
VanillaGateway: require('./src/gateway/vanilla-gateway'),
JQueryGateway: require('./src/gateway/jquery-gateway'),
forge: function(manifest, gateway) {
return new this.Mapper(
manifest,
gateway || this.VanillaGateway
).build();
}
forge: require('./src/forge'),
createGateway: require('./src/create-gateway')
}
{
"name": "mappersmith",
"version": "0.1.1",
"version": "0.2.0",
"description": "It is a lightweight rest client mapper for javascript",

@@ -12,4 +12,5 @@ "author": "Tulio Ornelas <ornelas.tulio@gmail.com>",

"scripts": {
"build": "./node_modules/.bin/browserify index.js --standalone Mappersmith --outfile build/mappersmith.js",
"release": "npm run build && ./node_modules/.bin/uglifyjs build/mappersmith.js -o build/mappersmith.min.js -c"
"build": "./node_modules/.bin/browserify index.js -s Mappersmith -o build/mappersmith.js",
"build-test": "./node_modules/.bin/browserify index.js -t rewireify -s Mappersmith -o build/mappersmith.test.js",
"release": "npm run build && ./node_modules/.bin/uglifyjs build/mappersmith.js -o build/mappersmith.min.js -c --screw-ie8 --preamble '/*! Mappersmith - https://github.com/tulios/mappersmith - Generated by browserify */'"
},

@@ -35,4 +36,6 @@ "repository": {

"mocha": "^2.0.1",
"rewireify": "0.0.13",
"shared-examples-for": "^0.1.3",
"uglify-js": "^2.4.16"
}
}

@@ -5,3 +5,3 @@ # Mappersmith

# Install
## Install

@@ -32,3 +32,3 @@ #### NPM

# Usage
## Usage

@@ -57,4 +57,4 @@ To create a client for your API, you will need to provide a simple manifest, which must have `host` and `resources` keys. Each resource has a name and a list of methods with its definitions, like:

Photo: {
save: {method: 'POST', path: '/v1/photos/{category}/save'}
}
save: {method: 'POST', path: '/v1/photos/{category}/save'}
}
...

@@ -86,2 +86,3 @@ ```

#### Parameters
If your method doesn't require any parameter, you can just call it without them:

@@ -99,4 +100,58 @@

# Gateways
#### Message Body
To send values in the request body (usually for POST or PUT methods) you will use the special parameter `body`:
```javascript
Client.Photo.save({category: 'family', body: {year: 2015, tags: ['party', 'family']}})
```
It will create a urlencoded version of the object (`year=2015&tags[]=party&tags[]=family`). If the `body` used
is not an object it will use the original value. If `body` is not possible as a special parameter
for your API you can configure it with another value, just pass the new name as the third argument
of method forge:
```javascript
var Client = new Mappersmith.forge(manifest, Mappersmith.VanillaGateway, 'data')
...
Client.Photo.save({category: 'family', data: {year: 2015, tags: ['party', 'family']}})
```
#### Processors
You can specify functions to process returned data before they are passed to success callback:
```javascript
...
Book: {
all: {
path: '/v1/books.json',
processor: function(data) {
return data.result;
}
}
}
...
```
#### Compact Syntax
If you find tiring having to map your API methods with hashes, you can use our incredible compact syntax:
```javascript
...
Book: {
all: 'get:/v1/books.json', // The same as {method: 'GET', path: '/v1/books.json'}
byId: '/v1/books/{id}.json' // The default is GET, as always
},
Photo: {
// The same as {method: 'POST', path: '/v1/photos/{category}/save.json'}
save: 'post:/v1/photos/{category}/save'
}
...
```
**A downside is that you can't use processor functions with compact syntax.**
## Gateways
**Mappersmith** allows you to customize the transport layer. You can use the default `Mappersmith.VanillaGateway`, the included `Mappersmith.JQueryGateway` or write your own version.

@@ -107,10 +162,9 @@

```javascript
var MyGateway = function() {
return Mappersmith.Gateway.apply(this, arguments);
}
MyGateway.prototype = Mappersmith.Utils.extend({},
Mappersmith.Gateway.prototype, {
var MyGateway = Mappersmith.createGateway({
get: function() {
// you will have `this.url` as the target url
// you will have:
// - this.url
// - this.params
// - this.body
// - this.opts
},

@@ -120,2 +174,4 @@

}
// and other HTTP methods
})

@@ -126,3 +182,3 @@ ```

Just provide an implementation of `Mappersmith.Gateway` as the second argument of the method `forge`:
Just provide an object created with `Mappersmith.createGateway` as the second argument of the method `forge`:

@@ -151,7 +207,55 @@ ```javascript

# Gateway Implementations
#### Global configurations and URL matching
Imagine that you are using `Mappersmith.JQueryGateway` and all of your methods must be called with `jsonp` or use a special header, it will be incredibly boring add those configurations every time. Global configurations allow you to configure gateway options and a processor that will be used for every method. Keep in mind that the processor configured in the resource will be prioritized instead to global, for example:
```javascript
var manifest = {
host: 'http://my.api.com',
rules: [
{ // This is our global configuration
values: {
gateway: {jsonp: true},
processor: function(data) { return data.result }
}
}
],
resources: {
Book: {
all: {path: '/v1/books.json'},
byId: {path: '/v1/books/{id}.json'}
},
Photo: {
byCategory: {path: '/v1/photos/{category}/all.json'}
}
}
}
```
It is possible to add some configurations based on matches in the URLs, let's include a header for every book URL:
```javascript
...
rules: [
{ // This is our global configuration
values: {
gateway: {jsonp: true},
processor: function(data) { return data.result }
}
},
{ // This will only be applied when the URL matches the regexp
match: /\/v1\/books/,
values: {headers: {'X-MY-HEADER': 'value'}}
}
]
...
```
Just keep in mind that the configurations and processors will be prioritized by their order, and the global configurations does not have a `match` key.
## Gateway Implementations
The gateways listed here are available through the `Mappersmith` namespace.
## VanillaGateway
### VanillaGateway

@@ -163,9 +267,14 @@ The default gateway - it uses plain `XMLHttpRequest`. Accepts a `configure` callback that allows you to change the request object before it is used.

- :ok: GET
- :x: POST
- :x: PUT
- :x: DELETE
- :x: PATCH
- :ok: POST
- :ok: PUT
- :ok: DELETE
- :ok: PATCH
## JQueryGateway
#### Available options:
- emulateHTTP: sends request as POST with `_method` in the body and `X-HTTP-Method-Override` header, both with requested method as value. (default `false`)
### JQueryGateway
It uses `$.ajax` and accepts an object that will be merged with `defaults`. It doesn't include **jquery**, so you will need to include that in your page.

@@ -176,14 +285,24 @@

- :ok: GET
- :x: POST
- :x: PUT
- :x: DELETE
- :x: PATCH
- :ok: POST
- :ok: PUT
- :ok: DELETE
- :ok: PATCH
# Tests
#### Available options:
1. Build the source (`npm run build`)
- emulateHTTP: sends request as POST with `_method` in the body and `X-HTTP-Method-Override` header, both with request method as value. (default `false`)
## Tests
1. Build the source (`npm run build-test`)
2. Open test.html
# Licence
## Compile and release
* Compile: `npm run build`
* Release (minified version): `npm run release`
## Licence
See [LICENCE](https://github.com/tulios/mappersmith/blob/master/LICENSE) for more details.
var Utils = require('./utils');
var Gateway = function(url, method, opts) {
this.url = url;
this.method = method;
this.opts = opts || {};
/**
* Gateway constructor
* @param args {Object} with url, method, params and opts
*
* * url: The full url of the resource, including host and query strings
* * method: The name of the HTTP method (get, head, post, put, delete and patch)
* to be used, in lower case.
* * params: request params (query strings, url params and body)
* * opts: gateway implementation specific options
*/
var Gateway = function(args) {
this.url = args.url;
this.method = args.method;
this.processor = args.processor;
this.params = args.params || {};
this.body = args.body;
this.opts = args.opts || {};

@@ -16,3 +29,3 @@ this.successCallback = Utils.noop;

call: function() {
this[this.method]();
this[this.method].apply(this, arguments);
return this;

@@ -22,3 +35,9 @@ },

success: function(callback) {
this.successCallback = callback;
if (this.processor !== undefined) {
this.successCallback = function(data) {
callback(this.processor(data));
}
} else {
this.successCallback = callback;
}
return this;

@@ -37,2 +56,6 @@ },

shouldEmulateHTTP: function(method) {
return !!(this.opts.emulateHTTP && /^(delete|put|patch)/i.test(method));
},
get: function() {

@@ -39,0 +62,0 @@ throw new Utils.Exception('Gateway#get not implemented');

var Utils = require('../utils');
var Gateway = require('../gateway');
var CreateGateway = require('../create-gateway');
var JQueryGateway = function() {
if (window.jQuery === undefined) {
throw new Utils.Exception(
'JQueryGateway requires jQuery but it was not found! ' +
'Change the gateway implementation or add jQuery on the page'
);
}
var JQueryGateway = module.exports = CreateGateway({
return Gateway.apply(this, arguments);
}
init: function() {
if (window.jQuery === undefined) {
throw new Utils.Exception(
'JQueryGateway requires jQuery but it was not found! ' +
'Change the gateway implementation or add jQuery on the page'
);
}
},
JQueryGateway.prototype = Utils.extend({}, Gateway.prototype, {
jQueryAjax: function(config) {
jQuery.ajax(Utils.extend({url: this.url}, config)).
done(function() { this.successCallback.apply(this, arguments) }.bind(this)).
fail(function() { this.failCallback.apply(this, arguments) }.bind(this)).
always(function() { this.completeCallback.apply(this, arguments) }.bind(this));
},
get: function() {
var defaults = {dataType: "json", url: this.url};
var config = Utils.extend(defaults, this.opts);
this.jQueryAjax(this.opts);
return this;
},
jQuery.ajax(config).
done(function() { this.successCallback.apply(this, arguments) }.bind(this)).
fail(function() { this.failCallback.apply(this, arguments) }.bind(this)).
always(function() { this.completeCallback.apply(this, arguments) }.bind(this));
post: function() {
return this._performRequest('POST');
},
put: function() {
return this._performRequest('PUT');
},
patch: function() {
return this._performRequest('PATCH');
},
delete: function() {
return this._performRequest('DELETE');
},
_performRequest: function(method) {
var requestMethod = method;
if (this.shouldEmulateHTTP(method)) {
requestMethod = 'POST';
this.body = this.body || {};
if (typeof this.body === 'object') this.body._method = method;
this.opts.headers = Utils.extend(this.opts.header, {'X-HTTP-Method-Override': method});
}
var defaults = {type: requestMethod, data: Utils.params(this.body)};
this.jQueryAjax(Utils.extend(defaults, this.opts));
return this;

@@ -30,3 +59,1 @@ }

});
module.exports = JQueryGateway;
var Utils = require('../utils');
var Gateway = require('../gateway');
var CreateGateway = require('../create-gateway');
var VanillaGateway = function() {
return Gateway.apply(this, arguments);
}
var VanillaGateway = module.exports = CreateGateway({
VanillaGateway.prototype = Utils.extend({}, Gateway.prototype, {
get: function() {
var request = new XMLHttpRequest();
configureCallbacks: function(request) {
request.onload = function() {

@@ -18,3 +12,9 @@ var data = null;

if (request.status >= 200 && request.status < 400) {
data = JSON.parse(request.responseText);
if (request.getResponseHeader('Content-Type') === 'application/json') {
data = JSON.parse(request.responseText);
} else {
data = request.responseText;
}
this.successCallback(data);

@@ -29,3 +29,3 @@

} finally {
this.completeCallback(data);
this.completeCallback(data, request);
}

@@ -43,9 +43,51 @@

}
},
get: function() {
var request = new XMLHttpRequest();
this.configureCallbacks(request);
request.open('GET', this.url, true);
request.send();
},
post: function() {
this._performRequest('POST');
},
put: function() {
this._performRequest('PUT');
},
patch: function() {
this._performRequest('PATCH');
},
delete: function() {
this._performRequest('DELETE');
},
_performRequest: function(method) {
var emulateHTTP = this.shouldEmulateHTTP(method);
var requestMethod = method;
var request = new XMLHttpRequest();
this.configureCallbacks(request);
if (emulateHTTP) {
this.body = this.body || {};
if (typeof this.body === 'object') this.body._method = method;
requestMethod = 'POST';
}
request.open(requestMethod, this.url, true);
if (emulateHTTP) request.setRequestHeader('X-HTTP-Method-Override', method);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
var args = [];
if (this.body !== undefined) {
args.push(Utils.params(this.body));
}
request.send.apply(request, args);
}
});
module.exports = VanillaGateway;

@@ -1,5 +0,16 @@

var Mapper = function(manifest, Gateway) {
var Utils = require('./utils');
/**
* Mapper constructor
* @param manifest {Object} with host and resources
* @param gateway {Object} with an implementation of {Mappersmith.Gateway}
* @param bodyAttr {String}, name of the body attribute used for HTTP methods
* such as POST and PUT
*/
var Mapper = function(manifest, Gateway, bodyAttr) {
this.manifest = manifest;
this.host = this.manifest.host;
this.rules = this.manifest.rules || [];
this.Gateway = Gateway;
this.host = this.manifest.host;
this.bodyAttr = bodyAttr;
}

@@ -23,2 +34,13 @@

var descriptor = methods[methodName];
if (typeof(descriptor) === 'string') {
var compactDefinitionMethod = descriptor.match( /^(get|post|delete|put|patch):(.*)/ )
if (compactDefinitionMethod != null) {
descriptor = {method: compactDefinitionMethod[1], path: compactDefinitionMethod[2]};
} else {
descriptor = {method: 'get', path: descriptor};
}
}
var httpMethod = (descriptor.method || 'get').toLowerCase();

@@ -28,3 +50,4 @@

httpMethod,
descriptor.path
descriptor.path,
descriptor.processor
);

@@ -38,6 +61,10 @@

urlFor: function(path, urlParams) {
// using `Utils.extend` avoids undesired changes to `urlParams`
var params = Utils.extend({}, urlParams);
var normalizedPath = /^\//.test(path) ? path : '/' + path;
var host = this.host.replace(/\/$/, '');
var params = urlParams || {};
var normalizedPath = /^\//.test(path) ? path : '/' + path;
// does not includes the body param into the URL
delete params[this.bodyAttr];
Object.keys(params).forEach(function(key) {

@@ -53,9 +80,6 @@ var value = params[key];

var paramsString = Object.keys(params).
filter(function(key) { return key !== undefined && key !== null}).
map(function(key){ return key + '=' + params[key]}).
join('&');
if (paramsString.length !== 0)
var paramsString = Utils.params(params);
if (paramsString.length !== 0) {
paramsString = '?' + paramsString;
}

@@ -65,3 +89,12 @@ return host + normalizedPath + paramsString;

newGatewayRequest: function(method, path) {
newGatewayRequest: function(method, path, processor) {
var rules = this.rules.
filter(function(rule) { return rule.match === undefined || rule.match.test(path) }).
reduce(function(context, rule) {
var mergedGateway = Utils.extend(context.gateway, rule.values.gateway);
context = Utils.extend(context, rule.values);
context.gateway = mergedGateway;
return context;
}, {});
return function(params, callback, opts) {

@@ -74,4 +107,16 @@ if (typeof params === 'function') {

var url = this.urlFor(path, params);
return new this.Gateway(url, method, opts).
opts = Utils.extend({}, opts, rules.gateway);
if(Utils.isObjEmpty(opts)) opts = undefined;
var body = (params || {})[this.bodyAttr];
var gatewayOpts = Utils.extend({}, {
url: this.urlFor(path, params),
method: method,
processor: processor || rules.processor,
params: params,
body: body,
opts: opts
})
return new this.Gateway(gatewayOpts).
success(callback).

@@ -78,0 +123,0 @@ call();

var Utils = module.exports = {
r20: /%20/g,
noop: function() {},
isObjEmpty: function(obj) {
for(var key in obj) {
if(obj.hasOwnProperty(key)) return false;
}
return true;
},
extend: function(out) {

@@ -12,3 +21,3 @@ out = out || {};

for (var key in arguments[i]) {
if (arguments[i].hasOwnProperty(key))
if (arguments[i].hasOwnProperty(key) && arguments[i][key] !== undefined)
out[key] = arguments[i][key];

@@ -21,2 +30,40 @@ }

params: function(entry) {
if (typeof entry !== 'object') {
return entry;
}
var validKeys = function(entry) {
return Object.keys(entry).
filter(function(key) {
return entry[key] !== undefined &&
entry[key] !== null
});
}
var buildRecursive = function(key, value, suffix) {
suffix = suffix || '';
var isArray = Array.isArray(value);
var isObject = typeof value === 'object';
if (!isArray && !isObject) {
return encodeURIComponent(key + suffix) + '=' + encodeURIComponent(value);
}
if (isArray) {
return value.map(function(v) { return buildRecursive(key, v, suffix + '[]') }).
join('&');
}
return validKeys(value).
map(function(k) { return buildRecursive(key, value[k], suffix + '[' + k + ']') }).
join('&');
}
return validKeys(entry).
map(function(key) { return buildRecursive(key, entry[key]) }).
join('&').
replace(Utils.r20, '+');
},
Exception: function(message) {

@@ -23,0 +70,0 @@ this.message = message;

var expect = chai.expect;
var shared = $shared;
var Utils = Mappersmith.Utils;

@@ -9,15 +10,21 @@

data,
processedData,
success,
fail,
complete;
complete,
processor;
beforeEach(function() {
fakeServer = sinon.fakeServer.create();
fakeServer.lastRequest = function() {
return fakeServer.requests[fakeServer.requests.length - 1];
}
url = 'http://full-url/';
method = 'get';
success = sinon.spy(function(){});
fail = sinon.spy(function(){});
complete = sinon.spy(function(){});
data = {id: 1, enable: false};
processedData = 'processed data';
});

@@ -29,11 +36,25 @@

function requestWithGateway(status, rawData, GatewayImpl, opts) {
function newGateway(GatewayImpl, opts) {
opts = opts || {};
return new GatewayImpl(Utils.extend({
url: url,
method: method,
processor: processor
}, opts));
}
function requestWithGateway(status, rawData, gateway) {
rawData = rawData || '';
var contentType = 'application/json';
try { JSON.parse(rawData) }
catch(e1) { contentType = 'text/plain' }
fakeServer.respondWith(
method,
url,
[status, {'Content-Type': 'application/json'}, rawData]
[status, {'Content-Type': contentType}, rawData]
);
new GatewayImpl(url, method, opts).
gateway.
success(success).

@@ -47,2 +68,90 @@ fail(fail).

shared.examplesFor('success JSON response', function(GatewayImpl) {
beforeEach(function() {
requestWithGateway(200, JSON.stringify(data), newGateway(GatewayImpl));
});
it('calls success callback', function() {
expect(success).to.have.been.calledWith(data);
});
it('calls complete callback', function() {
expect(complete).to.have.been.calledWith(data);
});
it('does not call fail callback', function() {
expect(fail).to.not.have.been.called;
});
});
shared.examplesFor('success with unexpected data', function(GatewayImpl) {
beforeEach(function() {
requestWithGateway(200, 'OK', newGateway(GatewayImpl));
});
it('calls success callback', function() {
expect(success).to.have.been.calledWith('OK');
});
it('calls complete callback', function() {
expect(complete).to.have.been.called;
});
it('does not call fail callback', function() {
expect(fail).to.not.have.been.called;
});
});
shared.examplesFor('success with processor', function(GatewayImpl) {
beforeEach(function() {
processor = sinon.spy(function() { return processedData; });
requestWithGateway(200, JSON.stringify(data), newGateway(GatewayImpl));
});
afterEach(function() {
processor = undefined;
});
it('is called on success', function() {
expect(processor).to.have.been.calledWith(data);
});
it('passes processed data to success callback', function() {
expect(success).to.have.been.calledWith(processedData);
});
});
shared.examplesFor('fail response', function(GatewayImpl) {
beforeEach(function() {
requestWithGateway(503, null, newGateway(GatewayImpl));
});
it('calls fail callback', function() {
expect(fail).to.have.been.called;
});
it('calls complete callback', function() {
expect(complete).to.have.been.called;
});
it('does not call success callback', function() {
expect(success).to.not.have.been.called;
});
});
shared.examplesFor('fail with processor', function(GatewayImpl) {
beforeEach(function() {
processor = sinon.spy(function(){ return 'processed data'; });
requestWithGateway(500, JSON.stringify(data), newGateway(GatewayImpl));
});
afterEach(function() {
processor = undefined;
});
it('is not called on failure', function() {
expect(processor).to.not.have.been.called;
});
});
describe('common behavior', function() {

@@ -60,36 +169,47 @@ [

describe('success', function() {
describe('with valid JSON', function() {
beforeEach(function() {
requestWithGateway(200, JSON.stringify(data), GatewayImpl);
});
describe('#get', function() {
beforeEach(function() {
method = 'get';
});
it('calls success callback', function() {
expect(success).to.have.been.calledWith(data);
});
describe('success', function() {
shared.shouldBehaveLike('success JSON response', GatewayImpl);
shared.shouldBehaveLike('success with unexpected data', GatewayImpl);
shared.shouldBehaveLike('success with processor', GatewayImpl);
});
it('calls complete callback', function() {
expect(complete).to.have.been.calledWith(data);
});
it('does not call fail callback', function() {
expect(fail).to.not.have.been.called;
});
describe('fail', function() {
shared.shouldBehaveLike('fail response', GatewayImpl);
shared.shouldBehaveLike('fail with processor', GatewayImpl);
});
});
describe('with invalid JSON', function() {
['put', 'patch', 'delete', 'post'].forEach(function(methodName) {
describe('#' + methodName, function() {
beforeEach(function() {
requestWithGateway(200, '{', GatewayImpl);
method = methodName;
});
it('does not call success callback', function() {
expect(success).to.not.have.been.called;
});
describe('success', function() {
shared.shouldBehaveLike('success JSON response', GatewayImpl);
shared.shouldBehaveLike('success with unexpected data', GatewayImpl);
shared.shouldBehaveLike('success with processor', GatewayImpl);
it('calls complete callback', function() {
expect(complete).to.have.been.called;
describe('with body data', function() {
var body;
beforeEach(function() {
body = {val1: 1, val2: 2};
requestWithGateway(200, 'OK', newGateway(GatewayImpl, {body: body}));
});
it('includes the formatted body to the request', function() {
expect(fakeServer.lastRequest().requestBody).to.equal(Utils.params(body));
});
});
});
it('calls fail callback', function() {
expect(fail).to.have.been.called;
describe('fail', function() {
shared.shouldBehaveLike('fail response', GatewayImpl);
shared.shouldBehaveLike('fail with processor', GatewayImpl);
});

@@ -99,20 +219,32 @@ });

describe('fail', function() {
beforeEach(function() {
requestWithGateway(503, null, GatewayImpl);
});
['put', 'patch', 'delete'].forEach(function(methodName) {
describe('emulating HTTP method ' + methodName.toUpperCase(), function() {
var body;
it('calls fail callback', function() {
expect(fail).to.have.been.called;
});
beforeEach(function() {
method = 'post';
});
it('calls complete callback', function() {
expect(complete).to.have.been.called;
});
describe('with body empty', function() {
beforeEach(function() {
body = {_method: methodName.toUpperCase()};
var localGateway = newGateway(GatewayImpl, {method: methodName, opts: {emulateHTTP: true}});
requestWithGateway(200, 'OK', localGateway);
});
it('does not call success callback', function() {
expect(success).to.not.have.been.called;
it('adds _method=' + methodName + ' to the body', function() {
expect(fakeServer.lastRequest().requestBody).to.equal(Utils.params(body));
});
it('adds header X-HTTP-Method-Override=' + methodName, function() {
var headerValue = fakeServer.lastRequest().requestHeaders['X-HTTP-Method-Override'];
expect(headerValue).to.equal(methodName.toUpperCase());
});
it('sends as POST', function() {
expect(fakeServer.lastRequest().method).to.equal('POST');
});
});
});
});
});

@@ -126,2 +258,3 @@ });

beforeEach(function() {
method = 'get';
configure = sinon.spy(function() {});

@@ -135,4 +268,3 @@ });

JSON.stringify(data),
VanillaGateway,
{configure: configure}
newGateway(VanillaGateway, {opts: {configure: configure}})
);

@@ -162,2 +294,3 @@

sinon.stub($, 'ajax').returns(ajax);
method = 'get';
});

@@ -172,3 +305,3 @@

var opts = {jsonp: true};
var defaults = {dataType: "json", url: url};
var defaults = {url: url};
var config = Utils.extend(defaults, opts);

@@ -179,4 +312,3 @@

JSON.stringify(data),
JQueryGateway,
opts
newGateway(JQueryGateway, {opts: opts})
);

@@ -183,0 +315,0 @@

@@ -28,3 +28,3 @@ var expect = chai.expect;

it('configures successCallback with noop', function() {
gateway = new Gateway(url, verb);
gateway = new Gateway({url: url, method: verb});
expect(gateway.successCallback).to.equals(noop);

@@ -34,3 +34,3 @@ });

it('configures failCallback with noop', function() {
gateway = new Gateway(url, verb);
gateway = new Gateway({url: url, method: verb});
expect(gateway.failCallback).to.equals(noop);

@@ -40,5 +40,27 @@ });

it('configures completeCallback with noop', function() {
gateway = new Gateway(url, verb);
gateway = new Gateway({url: url, method: verb});
expect(gateway.completeCallback).to.equals(noop);
});
describe('exposed args values', function() {
var args;
beforeEach(function() {
args = {
url: url,
method: verb,
params: {a: 1},
body: 'some-value',
opts: {b: 2}
}
gateway = new Gateway(args);
});
['url', 'method', 'params', 'body', 'opts'].forEach(function(attr) {
it(attr, function() {
expect(gateway[attr]).to.equal(args[attr]);
});
});
});
});

@@ -48,3 +70,3 @@

it('calls the configured method with the url', function() {
new Gateway(url, verb).call();
new Gateway({url: url, method: verb}).call();
expect(methodStub).to.have.been.called;

@@ -58,3 +80,3 @@ });

beforeEach(function() {
gateway = new Gateway(url, verb);
gateway = new Gateway({url: url, method: verb});
});

@@ -77,3 +99,3 @@

beforeEach(function() {
gateway = new Gateway(url, verb);
gateway = new Gateway({url: url, method: verb});
});

@@ -96,3 +118,3 @@

beforeEach(function() {
gateway = new Gateway(url, verb);
gateway = new Gateway({url: url, method: verb});
});

@@ -111,2 +133,53 @@

describe('#shouldEmulateHTTP', function() {
['delete', 'put', 'patch'].forEach(function(method) {
describe('when method is ' + method, function() {
describe('and emulating HTTP methods is enable', function() {
beforeEach(function() {
gateway = new Gateway({url: url, method: method, opts: {emulateHTTP: true}});
});
it('returns true', function() {
expect(gateway.shouldEmulateHTTP(method)).to.be.true();
});
});
describe('and emulating HTTP methods is not enable', function() {
beforeEach(function() {
gateway = new Gateway({url: url, method: method, opts: {emulateHTTP: false}});
});
it('returns false', function() {
expect(gateway.shouldEmulateHTTP(method)).to.be.false();
});
});
});
});
['post', 'get'].forEach(function(method) {
describe('when method is ' + method, function() {
describe('and emulating HTTP methods is enable', function() {
beforeEach(function() {
gateway = new Gateway({url: url, method: method, opts: {emulateHTTP: true}});
});
it('returns false', function() {
expect(gateway.shouldEmulateHTTP(method)).to.be.false();
});
});
describe('and emulating HTTP methods is not enable', function() {
beforeEach(function() {
gateway = new Gateway({url: url, method: method, opts: {emulateHTTP: false}});
});
it('returns false', function() {
expect(gateway.shouldEmulateHTTP(method)).to.be.false();
});
});
});
});
});
['get', 'post', 'put', 'delete', 'patch'].forEach(function(verb) {

@@ -122,3 +195,3 @@

it('throws Utils.Exception with NotImplemented message', function() {
expect(function() { new Gateway(url, verb).call() }).to.throw(Utils.Exception, /not implemented/);
expect(function() { new Gateway({url: url, method: verb}).call() }).to.throw(Utils.Exception, /not implemented/);
});

@@ -125,0 +198,0 @@ });

var expect = chai.expect;
var Utils = Mappersmith.Utils;
var VanillaGateway = Mappersmith.VanillaGateway;
var JQueryGateway = Mappersmith.JQueryGateway;
var Mapper = Mappersmith.Mapper;

@@ -28,33 +24,9 @@ describe('Mappersmith', function() {

describe('#forge', function() {
var mapper,
build,
manifest;
it('exposes method forge', function() {
expect(Mappersmith.forge).to.exist();
});
beforeEach(function() {
mapper = sinon.spy(Mappersmith, 'Mapper');
build = sinon.spy(Mappersmith.Mapper.prototype, 'build');
manifest = {resources: {}};
});
afterEach(function() {
Mappersmith.Mapper.restore();
Mappersmith.Mapper.prototype.build.restore();
});
it('builds a new mapper with default gateway', function() {
Mappersmith.forge(manifest);
expect(mapper).to.have.been.calledWith(manifest, VanillaGateway);
expect(build).to.have.been.called;
});
it('accepts a custom gateway', function() {
Mappersmith.forge(manifest, JQueryGateway);
expect(mapper).to.have.been.calledWith(manifest, JQueryGateway);
expect(build).to.have.been.called;
});
it('exposes method createGateway', function() {
expect(Mappersmith.createGateway).to.exist();
});
});
var expect = chai.expect;
var shared = $shared;
var Utils = Mappersmith.Utils;
var Mapper = Mappersmith.Mapper;

@@ -13,10 +15,19 @@

host: 'http://full-url',
emulateHTTP: false,
resources: {
Book: {
'all': {path: '/v1/books.json'},
'byId': {path: '/v1/books/{id}.json'}
all: {path: '/v1/books.json'},
byId: {path: '/v1/books/{id}.json'},
archived: '/v1/books/archived.json',
byCategory: 'get:/v1/books/{category}/all.json'
},
Photo: {
'byCategory': {path: '/v1/photos/{category}/all.json'},
'add': {method: 'post', path: '/v1/photos/create.json'}
byCategory: {path: '/v1/photos/{category}/all.json'},
add: {method: 'post', path: '/v1/photos/create.json'},
byId: {
path: '/v1/photos/{id}.json',
processor: function(data) {
return data.thumb;
}
}
}

@@ -60,2 +71,29 @@ }

});
it('holds a reference to bodyAttr', function() {
mapper = new Mapper(manifest, gateway, 'data');
expect(mapper).to.have.property('bodyAttr', 'data');
});
it('holds a reference to rules', function() {
manifest.rules = [];
mapper = new Mapper(manifest, gateway);
expect(mapper).to.have.property('rules', manifest.rules);
});
it('has a default value for rules', function() {
expect(manifest.rules).to.be.undefined;
expect(mapper.rules).to.eql([]);
});
it('holds a reference to rules', function() {
manifest.rules = [];
mapper = new Mapper(manifest, gateway);
expect(mapper).to.have.property('rules', manifest.rules);
});
it('has a default value for rules', function() {
expect(manifest.rules).to.be.undefined;
expect(mapper.rules).to.eql([]);
});
});

@@ -75,3 +113,3 @@

params = {a: true};
callback = function() {};
callback = Utils.noop;
});

@@ -90,3 +128,7 @@

expect(gateway.prototype.call).to.have.been.called;
expect(gateway).to.have.been.calledWith(fullUrl + '?a=true', method);
expect(gateway).to.have.been.calledWith({
url: fullUrl + '?a=true',
method: method,
params: params
});
});

@@ -101,3 +143,6 @@

expect(gateway.prototype.call).to.have.been.called;
expect(gateway).to.have.been.calledWith(fullUrl, method);
expect(gateway).to.have.been.calledWith({
url: fullUrl,
method: method
});
});

@@ -113,6 +158,101 @@

expect(gateway.prototype.call).to.have.been.called;
expect(gateway).to.have.been.calledWith(fullUrl, method, opts);
expect(gateway).to.have.been.calledWith({
url: fullUrl,
method: method,
opts: opts
});
});
});
});
describe('with body param', function() {
it('includes the value defined by bodyAttr in the key "body"', function() {
mapper.bodyAttr = 'body';
params[mapper.bodyAttr] = 'some-value';
var request = mapper.newGatewayRequest(method, path);
var result = {
url: fullUrl + '?a=true',
method: method,
params: params
}
result[mapper.bodyAttr] = params[mapper.bodyAttr];
expect(request(params, callback)).to.be.an.instanceof(gateway);
expect(gateway.prototype.success).to.have.been.calledWith(callback);
expect(gateway.prototype.call).to.have.been.called;
expect(gateway).to.have.been.calledWith(result);
});
});
describe('with configured rules', function() {
var opts;
beforeEach(function() {
path = manifest.resources.Book.all.path;
fullUrl = manifest.host + path;
});
shared.examplesFor('merged rules', function() {
it('always merge with gateway opts and processor', function() {
var request = mapper.newGatewayRequest(method, path);
expect(request(callback)).to.be.an.instanceof(gateway);
expect(gateway).to.have.been.calledWith({
url: fullUrl,
method: method,
opts: opts.gateway,
processor: opts.processor
});
});
});
describe('global', function() {
beforeEach(function() {
opts = {gateway: {global: true}, processor: Utils.noop};
manifest.rules = [{values: opts}];
mapper = new Mapper(manifest, gateway);
});
shared.shouldBehaveLike('merged rules');
});
describe('url match', function() {
beforeEach(function() {
opts = {gateway: {matchUrl: true}, processor: Utils.noop};
manifest.rules = [{match: /\/v1\/books/, values: opts}];
mapper = new Mapper(manifest, gateway);
});
shared.shouldBehaveLike('merged rules');
});
describe('mixed', function() {
var optsMatch;
beforeEach(function() {
opts = {gateway: {global: true}, processor: function globalMatch() {}};
optsMatch = {gateway: {matchUrl: true}, processor: function urlMatch() {}};
manifest.rules = [
{values: opts},
{match: /\/v1\/books/, values: optsMatch}
];
mapper = new Mapper(manifest, gateway);
});
it('merges both rules, using natural precedence for prioritization', function() {
var request = mapper.newGatewayRequest(method, path);
expect(request(callback)).to.be.an.instanceof(gateway);
expect(gateway).to.have.been.calledWith({
url: fullUrl,
method: method,
opts: Utils.extend({}, opts.gateway, optsMatch.gateway),
processor: optsMatch.processor
});
});
});
});
});

@@ -149,2 +289,10 @@

describe('with bodyAttr in params', function() {
it('does not include into the URL', function() {
var params = {};
params[mapper.bodyAttr] = 'some-value';
expect(mapper.urlFor('/path', params)).to.equals('http://full-url/path');
});
});
describe('with params in the path', function() {

@@ -188,2 +336,3 @@ it('replaces params and returns host and path', function() {

expect(result.Book.byId).to.be.a('function');
expect(result.Book.archived).to.be.a('function');
expect(result.Photo.byCategory).to.be.a('function');

@@ -206,3 +355,6 @@ });

result.Book.all(callback);
expect(gateway).to.have.been.calledWith(url, method);
expect(gateway).to.have.been.calledWith({
url: url,
method: method
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);

@@ -215,6 +367,11 @@ });

var path = manifest.resources.Book.all.path;
var url = mapper.urlFor(path, {b: 2});
var params = {b: 2};
var url = mapper.urlFor(path, params);
result.Book.all({b: 2}, callback);
expect(gateway).to.have.been.calledWith(url, method);
result.Book.all(params, callback);
expect(gateway).to.have.been.calledWith({
url: url,
method: method,
params: params
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);

@@ -224,9 +381,14 @@ });

describe('with params', function() {
describe('with params in the path', function() {
it('calls the gateway with the configured values', function() {
var path = manifest.resources.Book.byId.path;
var url = mapper.urlFor(path, {id: 3});
var params = {id: 3};
var url = mapper.urlFor(path, params);
result.Book.byId({id: 3}, callback);
expect(gateway).to.have.been.calledWith(url, method);
result.Book.byId(params, callback);
expect(gateway).to.have.been.calledWith({
url: url,
method: method,
params: params
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);

@@ -236,9 +398,14 @@ });

describe('with params and query string', function() {
describe('with params in the path and query string', function() {
it('calls the gateway with the configured values', function() {
var path = manifest.resources.Book.byId.path;
var url = mapper.urlFor(path, {id: 3, d: 4});
var params = {id: 3, d: 4};
var url = mapper.urlFor(path, params);
result.Book.byId({id: 3, d: 4}, callback);
expect(gateway).to.have.been.calledWith(url, method);
result.Book.byId(params, callback);
expect(gateway).to.have.been.calledWith({
url: url,
method: method,
params: params
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);

@@ -255,8 +422,103 @@ });

result.Photo.add(callback);
expect(gateway).to.have.been.calledWith(url, method);
expect(gateway).to.have.been.calledWith({
url: url,
method: method
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);
});
});
describe('with syntatic sugar for GET methods with no parameters', function() {
it('calls the gateway with method GET', function() {
var path = manifest.resources.Book.archived;
var url = mapper.urlFor(path);
result.Book.archived(callback);
expect(gateway).to.have.been.calledWith({
url: url,
method: method
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);
});
it('calls the gateway with query string', function() {
var path = manifest.resources.Book.archived;
var params = {author: 'Daniel'};
var url = mapper.urlFor(path, params);
result.Book.archived(params, callback);
expect(gateway).to.have.been.calledWith({
url: url,
method: method,
params: params
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);
});
});
describe('resource definition compact syntax', function() {
it('parses HTTP method and URL', function() {
var compactDefinition = manifest.resources.Book.byCategory;
var definitionComponents = compactDefinition.split(':');
expect(definitionComponents).to.have.length(2);
var method = definitionComponents[0];
var path = definitionComponents[1];
var url = mapper.urlFor(path);
result.Book.byCategory(callback);
expect(gateway).to.have.been.calledWith({
url: url,
method: method
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);
});
['get', 'post', 'delete', 'put', 'patch'].forEach(function(methodName) {
describe('methods', function() {
beforeEach(function() {
manifest = {
host: 'http://full-url',
resources: {
Book: {
test: methodName + ':/v1/books.json'
}
}
}
mapper = new Mapper(manifest, gateway);
result = mapper.build();
});
it('supports method ' + methodName , function() {
result.Book.test();
expect(gateway).to.have.been.calledWith({
url: mapper.urlFor('/v1/books.json'),
method: methodName
});
});
});
});
});
describe('processors', function() {
it('should be passed to gateway', function() {
var path = manifest.resources.Photo.byId.path;
var processor = manifest.resources.Photo.byId.processor;
var params = {id: 3};
var url = mapper.urlFor(path, params);
result.Photo.byId(params, callback);
expect(gateway).to.have.been.calledWith({
url: url,
method: method,
params: params,
processor: processor
});
expect(gateway.prototype.success).to.have.been.calledWith(callback);
});
});
});
});
});

@@ -11,2 +11,12 @@ var expect = chai.expect;

describe('#isObjEmpty', function() {
it('returns true for blank objects', function() {
expect(Utils.isObjEmpty({})).to.equal(true);
});
it('returns false for populated objects', function() {
expect(Utils.isObjEmpty({key: 'value'})).to.equal(false);
});
});
describe('#extend', function() {

@@ -22,4 +32,67 @@ it('extends the object attributes', function() {

});
it('ignores undefined values', function() {
var objA = {a: 1, b: 2};
var objB = {b: 1, c: undefined};
var result = Utils.extend({}, objA, objB);
expect(result).to.deep.equal({a: 1, b: 1});
});
});
describe('#params', function() {
describe('for non-object', function() {
it('returns the original entry', function() {
expect(Utils.params(1)).to.equal(1);
expect(Utils.params(1.1)).to.equal(1.1);
expect(Utils.params('value')).to.equal('value');
});
});
describe('for objects', function() {
it('ignores undefined or null values', function() {
expect(Utils.params({a: 1, b: undefined, c: null})).to.equal('a=1')
});
it('appends & for multiple values', function() {
expect(Utils.params({a: 1, b: 'val', c: true})).to.equal('a=1&b=val&c=true');
});
it('encodes "%20" to "+"', function() {
var params = {a: 'some big string'};
expect(Utils.params(params)).to.equal('a=some+big+string');
});
describe('in blank', function() {
it('returns an empty string', function() {
expect(Utils.params({})).to.equal('');
});
});
describe('with object values', function() {
it('converts the keys to "key[another-key]" pattern', function() {
var params = decodeURIComponent(Utils.params({a: {b: 1, c: 2}}));
expect(params).to.equal('a[b]=1&a[c]=2');
});
it('works with nested objects', function() {
var params = decodeURIComponent(Utils.params({a: {b: 1, c: {d: 2}}, e: 3}));
expect(params).to.equal('a[b]=1&a[c][d]=2&e=3');
});
});
describe('with array values', function() {
it('converts the keys to "key[]" pattern', function() {
var params = decodeURIComponent(Utils.params({a: [1, 2, 3]}));
expect(params).to.equal('a[]=1&a[]=2&a[]=3');
});
it('works with nested arrays', function() {
var params = decodeURIComponent(Utils.params({a: [1, [2, [3, 4]]]}));
expect(params).to.equal('a[]=1&a[][]=2&a[][][]=3&a[][][]=4');
});
});
});
});
describe('#Exception', function() {

@@ -26,0 +99,0 @@ var myError;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc