Skip to content

Commit

Permalink
Added regex functionality to routes.
Browse files Browse the repository at this point in the history
- Also now require callback functions on custom routes, and require
them to return a valid response.
- Bumped bower version.
- closes #8, #9, #10.
  • Loading branch information
blakewest committed Jan 19, 2015
1 parent 51d5f54 commit f0a2435
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 52 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,26 @@
patientResource.addRoute({
method:"POST",
route:"/verify",
// This callback is called everytime the route is run. It is not the response.
// Currently the response for custom routes is just the item, or collection, depending on // the route.
callback: function(data, item ) {
callback: function(data, item, headers) {
item.verified = true;
// If the onItem flag was false, this function would be passed the collection,
// and not the item.
return [200, item, {}, 'OK'];
},
// this flag adds the route for every item in the db. eg. '/patients/1/verify'
onItem: true
});
```
**notes about the addRoute options hash**
`route`: Can be either a string or regex. If it's a regex, it can't be set to "onItem".

`onItem`: If this flag is omitted, or set to false, it will create the route on the collection.
eg. '/patients/verify';

`callback`: This is meant to let you "perform the action" of the route. Very similar to whatever your real backend might do for this route. The callback is passed the request data, and then the appropriate database object. Which is the item if it's an item route (eg. patients/3/verify), or the collection if it's a colleciton route (eg. patients/verify)
`callback`: This is **required** and is meant to let you "perform the action" of the route. Very similar to whatever your real backend might do for this route. It also returns the response of the route. The response must be in standard angular form which is
`[status, data, headers, status_text]`.
The callback is passed the request data, the appropriate database object (which is the item if it's an item route (eg. patients/3/verify), or the collection if it's a colleciton route (eg. patients/verify)). It's also passed the request headers.


**Creating Expectations!**
Facade provides convenience around HTTP expectations. Specifically, you can do,
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "facade",
"version": "0.3.0",
"version": "0.4.0",
"authors": [
"Blake West <[email protected]>"
],
Expand Down
72 changes: 50 additions & 22 deletions dist/facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G.
this.db[opts.name] = buildTable(opts.name);

// Create a slot for the resources routes in the master route list;
facadeRoutes[opts.name] = [];
facadeRoutes[opts.name] = {};

// Add resource to master list
return this.resources[opts.name] = buildResource(opts);
Expand Down Expand Up @@ -124,12 +124,20 @@ var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G.
var routeObj;
var fullRoute = [method, url].join(' ');
var exists = _.any(facadeRoutes, function(resourceRoutes) {
return Boolean(resourceRoutes[fullRoute]) && (routeObj = resourceRoutes[fullRoute]);
routeObj = resourceRoutes[fullRoute];
if (routeObj) {
return routeObj;
}
return routeObj = _.chain(resourceRoutes)
.filter('regExp')
.where({method: method})
.find(function(route) {
return route.regExp.test(url);
}).value();
});
if (!exists) {
throw new Error("The route " + fullRoute + " does not exist");
}

return routeObj;
}

Expand Down Expand Up @@ -281,37 +289,33 @@ var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G.


function createCustomRouteForItem(opts) {
throwIfRegex(opts.route);
var fullUrl = opts.resource.url + '/' + opts.item.id + opts.route;
Facade.backend.when(opts.method, fullUrl).respond(function(method, url, requestData, headers) {
requestData = requestData || {};
var route = Facade.findRoute(method, url);

if (!route.hasSpecialResponse()) {
_.isFunction(opts.callback) && opts.callback(requestData, opts.item);
}
var item = getTable(opts.resource).find(opts.item.id);

var response = route.getSpecialResponseOr(function() {
return [200, JSON.stringify(opts.item), {}, 'OK'];
return opts.callback(requestData, item, headers);
});
checkForValidResponse(response);

return response;
});
}

function createCustomRouteForCollection(opts) {
var fullUrl = opts.resource.url + opts.route;
var fullUrl = _.isRegExp(opts.route) ? opts.route : opts.resource.url + opts.route
Facade.backend.when(opts.method, fullUrl).respond(function(method, url, requestData, headers) {
requestData = requestData || {};
var collection = getTable(opts.resource).getAll();
var route = Facade.findRoute(method, url);

if (!route.hasSpecialResponse()) {
_.isFunction(opts.callback) && opts.callback(requestData, collection);
}

var response = route.getSpecialResponseOr(function() {
return [200, JSON.stringify(collection), {}, 'OK'];
return opts.callback(requestData, collection);
});
checkForValidResponse(response);

return response;
});
Expand Down Expand Up @@ -339,10 +343,16 @@ var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G.

function storeRoute(opts) {
opts.route = opts.route || '';
var prefix = opts.resource.url;
var fullUrl = opts.item ? prefix + '/' + opts.item.id + opts.route : prefix + opts.route;
var fullRoute = [opts.method, fullUrl].join(' ')
facadeRoutes[opts.resource.name][fullRoute] = buildRoute(fullRoute);
if (_.isRegExp(opts.route)) {
opts.regExp = opts.route;
}else {
var prefix = opts.resource.url;
var fullUrl = opts.item ? prefix + '/' + opts.item.id + opts.route : prefix + opts.route;
var fullRoute = [opts.method, fullUrl].join(' ')

opts.fullRoute = fullRoute;
}
facadeRoutes[opts.resource.name][(opts.regExp || opts.fullRoute)] = buildRoute(opts);
}

function storeRouteOpts(opts) {
Expand Down Expand Up @@ -437,10 +447,12 @@ var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G.
};
}

function buildRoute(fullRoute) {
function buildRoute(opts) {
var specialResponses = [];
return {
fullRoute: fullRoute,
fullRoute: opts.fullRoute,
regExp: opts.regExp,
method: opts.method,
nextResponse: function(status, data) {
specialResponses.push({status: status, data: data});
},
Expand Down Expand Up @@ -593,11 +605,27 @@ var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G.
}
}

function throwIfRegex(route) {
if (_.isRegExp(route)) {
throw new Error("Regex routes can't be used for item routes. Either make 'onItem' false" +
" or make the route a string")
}
}

function checkForRequiredRouteArgs(opts) {
checkForValidMethod(opts.method);
if (!_.isString(opts.route) && !_.isRegExp(opts.route)) {
throw new Error("You must supply a route (eg: '/my_route') as either a string or regex");
}
if (!opts.callback) {
throw new Error("You must supply a response callback for custom routes.");
}
}

if (!_.isString(opts.route)) {
throw new Error("You must supply a route. eg: '/my_route' ");
function checkForValidResponse(response) {
if (!_.isArray(response)) { throw new Error("Response must be an array");}
if (response.length !== 4) {
throw new Error("Response does not appear to be in the form of [status, data, headers, status_text]" )
}
}

Expand Down
4 changes: 2 additions & 2 deletions dist/facade.min.js

Large diffs are not rendered by default.

72 changes: 50 additions & 22 deletions src/facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
this.db[opts.name] = buildTable(opts.name);

// Create a slot for the resources routes in the master route list;
facadeRoutes[opts.name] = [];
facadeRoutes[opts.name] = {};

// Add resource to master list
return this.resources[opts.name] = buildResource(opts);
Expand Down Expand Up @@ -68,12 +68,20 @@
var routeObj;
var fullRoute = [method, url].join(' ');
var exists = _.any(facadeRoutes, function(resourceRoutes) {
return Boolean(resourceRoutes[fullRoute]) && (routeObj = resourceRoutes[fullRoute]);
routeObj = resourceRoutes[fullRoute];
if (routeObj) {
return routeObj;
}
return routeObj = _.chain(resourceRoutes)
.filter('regExp')
.where({method: method})
.find(function(route) {
return route.regExp.test(url);
}).value();
});
if (!exists) {
throw new Error("The route " + fullRoute + " does not exist");
}

return routeObj;
}

Expand Down Expand Up @@ -225,37 +233,33 @@


function createCustomRouteForItem(opts) {
throwIfRegex(opts.route);
var fullUrl = opts.resource.url + '/' + opts.item.id + opts.route;
Facade.backend.when(opts.method, fullUrl).respond(function(method, url, requestData, headers) {
requestData = requestData || {};
var route = Facade.findRoute(method, url);

if (!route.hasSpecialResponse()) {
_.isFunction(opts.callback) && opts.callback(requestData, opts.item);
}
var item = getTable(opts.resource).find(opts.item.id);

var response = route.getSpecialResponseOr(function() {
return [200, JSON.stringify(opts.item), {}, 'OK'];
return opts.callback(requestData, item, headers);
});
checkForValidResponse(response);

return response;
});
}

function createCustomRouteForCollection(opts) {
var fullUrl = opts.resource.url + opts.route;
var fullUrl = _.isRegExp(opts.route) ? opts.route : opts.resource.url + opts.route
Facade.backend.when(opts.method, fullUrl).respond(function(method, url, requestData, headers) {
requestData = requestData || {};
var collection = getTable(opts.resource).getAll();
var route = Facade.findRoute(method, url);

if (!route.hasSpecialResponse()) {
_.isFunction(opts.callback) && opts.callback(requestData, collection);
}

var response = route.getSpecialResponseOr(function() {
return [200, JSON.stringify(collection), {}, 'OK'];
return opts.callback(requestData, collection);
});
checkForValidResponse(response);

return response;
});
Expand Down Expand Up @@ -283,10 +287,16 @@

function storeRoute(opts) {
opts.route = opts.route || '';
var prefix = opts.resource.url;
var fullUrl = opts.item ? prefix + '/' + opts.item.id + opts.route : prefix + opts.route;
var fullRoute = [opts.method, fullUrl].join(' ')
facadeRoutes[opts.resource.name][fullRoute] = buildRoute(fullRoute);
if (_.isRegExp(opts.route)) {
opts.regExp = opts.route;
}else {
var prefix = opts.resource.url;
var fullUrl = opts.item ? prefix + '/' + opts.item.id + opts.route : prefix + opts.route;
var fullRoute = [opts.method, fullUrl].join(' ')

opts.fullRoute = fullRoute;
}
facadeRoutes[opts.resource.name][(opts.regExp || opts.fullRoute)] = buildRoute(opts);
}

function storeRouteOpts(opts) {
Expand Down Expand Up @@ -381,10 +391,12 @@
};
}

function buildRoute(fullRoute) {
function buildRoute(opts) {
var specialResponses = [];
return {
fullRoute: fullRoute,
fullRoute: opts.fullRoute,
regExp: opts.regExp,
method: opts.method,
nextResponse: function(status, data) {
specialResponses.push({status: status, data: data});
},
Expand Down Expand Up @@ -537,11 +549,27 @@
}
}

function throwIfRegex(route) {
if (_.isRegExp(route)) {
throw new Error("Regex routes can't be used for item routes. Either make 'onItem' false" +
" or make the route a string")
}
}

function checkForRequiredRouteArgs(opts) {
checkForValidMethod(opts.method);
if (!_.isString(opts.route) && !_.isRegExp(opts.route)) {
throw new Error("You must supply a route (eg: '/my_route') as either a string or regex");
}
if (!opts.callback) {
throw new Error("You must supply a response callback for custom routes.");
}
}

if (!_.isString(opts.route)) {
throw new Error("You must supply a route. eg: '/my_route' ");
function checkForValidResponse(response) {
if (!_.isArray(response)) { throw new Error("Response must be an array");}
if (response.length !== 4) {
throw new Error("Response does not appear to be in the form of [status, data, headers, status_text]" )
}
}

Expand Down
43 changes: 42 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@
route:"/verify",
callback: function(data, item) {
item.verified = true;
return [200, item, {}, 'OK'];
},
onItem: true
});
Expand Down Expand Up @@ -431,6 +432,7 @@
_.each(collection, function(item) {
item.verified = true;
})
return [200, collection, {}, 'OK'];
}
});

Expand All @@ -451,6 +453,7 @@
route:"/verify",
callback: function(data, item, headers) {
item.verified = true;
return [200, item, {}, 'OK'];
},
onItem: true
});
Expand All @@ -464,9 +467,47 @@

$rootScope.verifiedPatient.verified.should.eql(true);
});
xit("should be able to change the status and response value of custom routes", function() {
it("should be able to take a regex for the route", function() {
patientResource.addRoute({
method:"POST",
route: /verify/,
callback: function(data, collection, headers) {
_.each(collection, function(item) {item.verified = true});
return [200, collection[0], {}, 'OK'];
}
});
$rootScope.getOne("patients", patient1.id);
$httpBackend.flush();
$rootScope.item.verified.should.eql(false);

$rootScope.verifyPatient(patient1.id);
$httpBackend.flush();

$rootScope.verifiedPatient.verified.should.eql(true);
});
it("should throw an error if the custom route returns an invalid response", function() {
patientResource.addRoute({
method:"POST",
route: /verify/,
callback: function(data, collection, headers) {
_.each(collection, function(item) {item.verified = true});
return [collection[0], {}, 'OK'];
}
});

(function() {
$rootScope.verifyPatient(patient1.id);
$httpBackend.flush();
}).should.throw(/Response/);
});
it("should throw an error if the custom route doesn't have a callback", function() {
(function() {
patientResource.addRoute({
method:"POST",
route: /verify/
});
}).should.throw(/supply a response/)
})
xdescribe("short cut route methods", function() {
it("should have a shortcut GET method", function() {

Expand Down

0 comments on commit f0a2435

Please sign in to comment.