Skip to content

Commit

Permalink
feat(onUnhandled*): The call feature for unhandled paths.
Browse files Browse the repository at this point in the history
Updated the call version of the algorithm to handle the function does
not exist error.  If there is an error and its CallNotFoundError then
we will chain.  I also updated the interface to be the agreed upon
interface of a DataSource.
  • Loading branch information
ThePrimeagen committed Dec 15, 2015
1 parent 64f47b3 commit 2532999
Show file tree
Hide file tree
Showing 20 changed files with 252 additions and 275 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
"test": "test"
},
"dependencies": {
"rx": "~2.5.3",
"falcor-path-syntax": "0.2.4",
"falcor-path-utils": "0.3.0"
"falcor-path-utils": "0.3.0",
"rx": "^4.0.7"
},
"devDependencies": {
"chai": "^2.3.0",
Expand Down
80 changes: 15 additions & 65 deletions src/Router.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
var Keys = require('./Keys');
var parseTree = require('./parse-tree');
var matcher = require('./operations/matcher');
var recurseMatchAndExecute = require('./run/recurseMatchAndExecute');
var runCallAction = require('./run/call/runCallAction');
var call = 'call';
var pathUtils = require('falcor-path-utils');
var collapse = pathUtils.collapse;
var JSONGraphError = require('./errors/JSONGraphError');
var MAX_REF_FOLLOW = 50;
var unhandled = require('./run/unhandled');

var Router = function(routes, options) {
var opts = options || {};
Expand All @@ -18,7 +12,6 @@ var Router = function(routes, options) {
this._matcher = matcher(this._rst);
this._debug = opts.debug;
this.maxRefFollow = opts.maxRefFollow || MAX_REF_FOLLOW;
this._unhandled = {};
};

Router.createClass = function(routes) {
Expand All @@ -41,17 +34,6 @@ Router.prototype = {
*/
get: require('./router/get'),

/**
* Takes in a function to call that has the same return inteface as any
* route that will be called in the event of "unhandledPaths" on a get.
*
* @param {Function} unhandledHandler -
* @returns {undefined}
*/
onUnhandledGet: function(unhandledHandler) {
this._unhandled.get = unhandled(this, unhandledHandler);
},

/**
* Takes in a jsonGraph and outputs a Observable.<jsonGraph>. The set
* method will use get until it evaluates the last key of the path inside
Expand All @@ -68,59 +50,27 @@ Router.prototype = {
set: require('./router/set'),

/**
* Takes in a function to call that has the same return inteface as any
* route that will be called in the event of "unhandledPaths" on a set.
*
* What will come into the set function will be the subset of the jsonGraph
* that represents the unhandledPaths of set.
* Invokes a function in the DataSource's JSONGraph object at the path
* provided in the callPath argument. If there are references that are
* followed, a get will be performed to get to the call function.
*
* @param {Function} unhandledHandler -
* @returns {undefined}
* @param {Path} callPath -
* @param {Array.<*>} args -
* @param {Array.<PathSet>} refPaths -
* @param {Array.<PathSet>} thisPaths -
*/
onUnhandledSet: function(unhandledHandler) {
this._unhandled.set = unhandled(this, unhandledHandler);
},

call: function(callPath, args, suffixes, paths) {
var jsongCache = {};
var action = runCallAction(this, callPath, args,
suffixes, paths, jsongCache);
var callPaths = [callPath];
return run(this._matcher, action, callPaths, call, this, jsongCache).
map(function(jsongResult) {
var reportedPaths = jsongResult.reportedPaths;
var jsongEnv = {
jsonGraph: jsongResult.jsonGraph
};
call: require('./router/call'),

// Call must report the paths that have been produced.
if (reportedPaths.length) {
// Collapse the reported paths as they may be inefficient
// to send across the wire.
jsongEnv.paths = collapse(reportedPaths);
}
else {
jsongEnv.paths = [];
jsongEnv.jsonGraph = {};
}

// add the invalidated paths to the jsonGraph Envelope
var invalidated = jsongResult.invalidated;
if (invalidated && invalidated.length) {
jsongEnv.invalidated = invalidated;
}

return jsongEnv;
});
/**
* When a route misses on a call, get, or set the unhandledDataSource will
* have a chance to fulfill that request.
* @param {DataSource} dataSource -
*/
onUnhandledOperation: function unhandledDataSource(dataSource) {
this._unhandled = dataSource;
}
};

function run(matcherFn, actionRunner, paths, method,
routerInstance, jsongCache) {
return recurseMatchAndExecute(matcherFn, actionRunner, paths, method,
routerInstance, jsongCache);
}

Router.ranges = Keys.ranges;
Router.integers = Keys.integers;
Router.keys = Keys.keys;
Expand Down
8 changes: 8 additions & 0 deletions src/errors/CallNotFoundError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var MESSAGE = 'function does not exist.';
var CallNotFoundError = module.exports = function CallNotFoundError() {
this.message = MESSAGE;
this.stack = (new Error()).stack;
};

CallNotFoundError.prototype = new Error();

9 changes: 9 additions & 0 deletions src/errors/CallRequiresPathsError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var MESSAGE = 'Any JSONG-Graph returned from call must have paths.';
var CallRequiresPathsError = function CallRequiresPathsError() {
this.message = MESSAGE;
this.stack = (new Error()).stack;
};

CallRequiresPathsError.prototype = new Error();

module.exports = CallRequiresPathsError;
1 change: 0 additions & 1 deletion src/exceptions/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/*eslint-disable*/
module.exports = {
callJSONGraphWithouPaths: 'Any JSONG-Graph returned from call must have paths.',
innerReferences: 'References with inner references are not allowed.',
unknown: 'Unknown Error',
routeWithSamePrecedence: 'Two routes cannot have the same precedence or path.',
Expand Down
3 changes: 2 additions & 1 deletion src/operations/matcher/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var pluckIntegers = require('./pluckIntergers');
var pathUtils = require('falcor-path-utils');
var collapse = pathUtils.collapse;
var isRoutedToken = require('./../../support/isRoutedToken');
var CallNotFoundError = require('./../../errors/CallNotFoundError');

var intTypes = [{
type: Keys.ranges,
Expand Down Expand Up @@ -45,7 +46,7 @@ module.exports = function matcher(rst) {
// We are at the end of the path but there is no match and its a
// call. Therefore we are going to throw an informative error.
if (method === call && matched.length === 0) {
var err = new Error('function does not exist');
var err = new CallNotFoundError();
err.throwToNext = true;

throw err;
Expand Down
66 changes: 66 additions & 0 deletions src/router/call.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
var call = 'call';
var runCallAction = require('./../run/call/runCallAction');
var recurseMatchAndExecute = require('./../run/recurseMatchAndExecute');
var normalizePathSets = require('../operations/ranges/normalizePathSets');
var CallNotFoundError = require('./../errors/CallNotFoundError');
var materialize = require('../run/materialize');
var pathUtils = require('falcor-path-utils');
var collapse = pathUtils.collapse;

/**
* Performs the call mutation. If a call is unhandled, IE throws error, then
* we will chain to the next dataSource in the line.
*/
module.exports = function routerCall(callPath, args,
refPathsArg, thisPathsArg) {
var refPaths = normalizePathSets(refPathsArg || []);
var thisPaths = normalizePathSets(thisPathsArg || []);

var jsongCache = {};
var router = this;
var action = runCallAction(router, callPath, args,
refPaths, thisPaths, jsongCache);
var callPaths = [callPath];
return recurseMatchAndExecute(router._matcher, action, callPaths, call,
router, jsongCache).

// Take that
map(function(jsongResult) {
var reportedPaths = jsongResult.reportedPaths;
var jsongEnv = {
jsonGraph: jsongResult.jsonGraph
};

// Call must report the paths that have been produced.
if (reportedPaths.length) {
// Collapse the reported paths as they may be inefficient
// to send across the wire.
jsongEnv.paths = collapse(reportedPaths);
}
else {
jsongEnv.paths = [];
jsongEnv.jsonGraph = {};
}

// add the invalidated paths to the jsonGraph Envelope
var invalidated = jsongResult.invalidated;
if (invalidated && invalidated.length) {
jsongEnv.invalidated = invalidated;
}

// Calls are currently materialized.
materialize(router, reportedPaths, jsongEnv);
return jsongEnv;
}).

// For us to be able to chain call requests then the error that is
// caught has to be a 'function does not exist.' error. From that we
// will try the next dataSource in the line.
catch(function catchException(e) {
if (e instanceof CallNotFoundError && router._unhandled) {
return router._unhandled.
call(callPath, args, refPaths, thisPaths);
}
throw e;
});
};
18 changes: 14 additions & 4 deletions src/router/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var recurseMatchAndExecute = require('../run/recurseMatchAndExecute');
var normalizePathSets = require('../operations/ranges/normalizePathSets');
var materialize = require('../run/materialize');
var Observable = require('rx').Observable;
var mCGRI = require('./../run/mergeCacheAndGatherRefsAndInvalidations');

/**
* The router get function
Expand All @@ -26,14 +27,23 @@ module.exports = function routerGet(paths) {

// If the unhandledPaths are present then we need to
// call the backup method for generating materialized.
if (details.unhandledPaths.length && router._unhandled.get) {
if (details.unhandledPaths.length && router._unhandled) {
var unhandledPaths = details.unhandledPaths;

// The 3rd argument is the beginning of the actions arguments,
// which for get is the same as the unhandledPaths.
return router._unhandled.get(out,
unhandledPaths,
unhandledPaths);
return router._unhandled.
get(unhandledPaths).

// Merge the solution back into the overall message.
map(function(jsonGraphFragment) {
mCGRI(out.jsonGraph, [{
jsonGraph: jsonGraphFragment.jsonGraph,
paths: unhandledPaths
}]);
return out;
}).
defaultIfEmpty(out);
}

return Observable.return(out);
Expand Down
17 changes: 14 additions & 3 deletions src/router/set.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var getValue = require('./../cache/getValue');
var normalizePathSets = require('../operations/ranges/normalizePathSets');
var pathUtils = require('falcor-path-utils');
var collapse = pathUtils.collapse;
var mCGRI = require('./../run/mergeCacheAndGatherRefsAndInvalidations');

/**
* @returns {Observable.<JSONGraph>}
Expand All @@ -36,7 +37,7 @@ module.exports = function routerSet(jsonGraph) {
// If there is an unhandler then we should call that method and
// provide the subset of jsonGraph that represents the missing
// routes.
if (details.unhandledPaths.length && router._unhandled.set) {
if (details.unhandledPaths.length && router._unhandled) {
var unhandledPaths = details.unhandledPaths;
var jsonGraphFragment = {};

Expand Down Expand Up @@ -115,8 +116,18 @@ module.exports = function routerSet(jsonGraph) {
return pV.path;
}));

return router._unhandled.set(out, unhandledPaths,
jsonGraphEnvelope);
return router._unhandled.
set(jsonGraphEnvelope).

// Merge the solution back into the overall message.
map(function(unhandledJsonGraphEnv) {
mCGRI(out.jsonGraph, [{
jsonGraph: unhandledJsonGraphEnv.jsonGraph,
paths: unhandledPaths
}]);
return out;
}).
defaultIfEmpty(out);
}

return Observable.return(out);
Expand Down
4 changes: 2 additions & 2 deletions src/run/call/runCallAction.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var isJSONG = require('./../../support/isJSONG');
var outputToObservable = require('./../conversion/outputToObservable');
var noteToJsongOrPV = require('./../conversion/noteToJsongOrPV');
var errors = require('./../../exceptions');
var CallRequiresPathsError = require('./../../errors/CallRequiresPathsError');
var mCGRI = require('./../mergeCacheAndGatherRefsAndInvalidations');
var Observable = require('rx').Observable;

Expand Down Expand Up @@ -64,7 +64,7 @@ function runCallAction(matchAndPath, routerInstance, callPath, args,
// This is a hard error and must fully stop the server
if (!r.paths) {
var err =
new Error(errors.callJSONGraphWithouPaths);
new CallRequiresPathsError();
err.throwToNext = true;
throw err;
}
Expand Down
2 changes: 1 addition & 1 deletion src/run/conversion/noteToJsongOrPV.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function convertNoteToJsongOrPV(pathOrPathSet, note, isPathSet) {
// Convert the error to a pathValue.
else {
incomingJSONGOrPathValues =
errorToPathValue(note.exception, pathOrPathSet);
errorToPathValue(note.error, pathOrPathSet);
}

// If its jsong we may need to optionally attach the
Expand Down
2 changes: 1 addition & 1 deletion src/run/precedence/runByPrecedence.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module.exports = function runByPrecedence(pathSet, matches, actionRunner) {

if (execs.unhandledPaths) {
setOfMatchedPaths = setOfMatchedPaths.
concat(Observable.returnValue({
concat(Observable.return({
match: {suffix: []},
value: {
isMessage: true,
Expand Down
Loading

0 comments on commit 2532999

Please sign in to comment.