Skip to content

Commit

Permalink
feat(onUnhandled*): UnhandledGet works as specced.
Browse files Browse the repository at this point in the history
Unhandled paths that are from get requests will be fed to the unhandled
handler after the get operation.  The results from the handler will be
merged into the overal results.  Any subsequent missing data will be
materialized.
  • Loading branch information
ThePrimeagen committed Dec 11, 2015
1 parent 10d6a26 commit ed15ff2
Show file tree
Hide file tree
Showing 21 changed files with 614 additions and 214 deletions.
100 changes: 45 additions & 55 deletions src/Router.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
var Keys = require('./Keys');
var parseTree = require('./parse-tree');
var matcher = require('./operations/matcher');
var normalizePathSets = require('./operations/ranges/normalizePathSets');
var recurseMatchAndExecute = require('./run/recurseMatchAndExecute');
var optimizePathSets = require('./cache/optimizePathSets');
var pathValueMerge = require('./cache/pathValueMerge');
var runGetAction = require('./run/get/runGetAction');
var runSetAction = require('./run/set/runSetAction');
var runCallAction = require('./run/call/runCallAction');
var $atom = require('./support/types').$atom;
var get = 'get';
var set = 'set';
var call = 'call';
var pathUtils = require('falcor-path-utils');
var collapse = pathUtils.collapse;
var JSONGraphError = require('./JSONGraphError');
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 @@ -25,41 +20,52 @@ 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) {
function C(options) {
var opts = options || {};
this._debug = opts.debug;
}
function C(options) {
var opts = options || {};
this._debug = opts.debug;
}

C.prototype = new Router(routes);
C.prototype.constructor = C;
C.prototype = new Router(routes);
C.prototype.constructor = C;

return C;
return C;
};

Router.prototype = {
get: function(paths) {
var jsongCache = {};
var action = runGetAction(this, jsongCache);
var router = this;
var normPS = normalizePathSets(paths);
return run(this._matcher, action, normPS, get, this, jsongCache).
map(function(jsongEnv) {
return materializeMissing(router, paths, jsongEnv);
});
/**
* Performs the get algorithm on the router.
* @param {PathSet[]} paths -
* @returns {JSONGraphEnvelope}
*/
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);
},

set: function(jsong) {
// TODO: Remove the modelContext and replace with just jsongEnv
// when http://github.com/Netflix/falcor-router/issues/24 is addressed

var jsongCache = {};
var action = runSetAction(this, jsong, jsongCache);
var router = this;
return run(this._matcher, action, jsong.paths, set, this, jsongCache).
map(function(jsongEnv) {
return materializeMissing(router, jsong.paths, jsongEnv);

// Turn it(jsongGraph, invalidations, missing, etc.) into a
// jsonGraph envelope
map(function(details) {
return {
jsonGraph: details.jsonGraph
};
});
},

Expand All @@ -68,55 +74,39 @@ Router.prototype = {
var action = runCallAction(this, callPath, args,
suffixes, paths, jsongCache);
var callPaths = [callPath];
var router = this;
return run(this._matcher, action, callPaths, call, this, jsongCache).
map(function(jsongResult) {
var reportedPaths = jsongResult.reportedPaths;
var jsongEnv = materializeMissing(
router,
reportedPaths,
jsongResult);

var jsongEnv = {
jsonGraph: jsongResult.jsonGraph
};

// Call must report the paths that have been produced.
if (reportedPaths.length) {
jsongEnv.paths = reportedPaths;
// 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;
}
jsongEnv.paths = collapse(jsongEnv.paths);

return jsongEnv;
});
}
};

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

function materializeMissing(router, paths, jsongEnv, missingAtom) {
var jsonGraph = jsongEnv.jsonGraph;
var materializedAtom = missingAtom || {$type: $atom};

// Optimizes the pathSets from the jsong then
// inserts atoms of undefined.
optimizePathSets(jsonGraph, paths, router.maxRefFollow).
forEach(function(optMissingPath) {
pathValueMerge(jsonGraph, {
path: optMissingPath,
value: materializedAtom
});
});

return {jsonGraph: jsonGraph};
return recurseMatchAndExecute(matcherFn, actionRunner, paths, method,
routerInstance, jsongCache);
}

Router.ranges = Keys.ranges;
Expand Down
41 changes: 14 additions & 27 deletions src/cache/jsongMerge.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
var iterateKeySet = require('falcor-path-utils').iterateKeySet;
var types = require('./../support/types');
var $ref = types.$ref;
var $atom = types.$atom;
var clone = require('./../support/clone');
var cloneArray = require('./../support/cloneArray');
var catAndSlice = require('./../support/catAndSlice');
Expand All @@ -14,6 +13,7 @@ module.exports = function jsongMerge(cache, jsongEnv) {
var j = jsongEnv.jsonGraph;
var references = [];
var values = [];

paths.forEach(function(p) {
merge({
cacheRoot: cache,
Expand All @@ -25,6 +25,7 @@ module.exports = function jsongMerge(cache, jsongEnv) {
ignoreCount: 0
}, cache, j, 0, p);
});

return {
references: references,
values: values
Expand Down Expand Up @@ -87,20 +88,21 @@ function merge(config, cache, message, depth, path, fromParent, fromKey) {
var cacheRes = cache[key];
var messageRes = message[key];

var nextPath = path;
var nextDepth = depth + 1;
if (updateRequestedPath) {
requestedPath[requestIdx] = key;
}
// We no longer materialize inside of jsonGraph merge. Either the
// client should specify all of the paths
if (messageRes !== undefined) {

// Cache does not exist but message does.
if (cacheRes === undefined) {
cacheRes = cache[key] = {};
}
var nextPath = path;
var nextDepth = depth + 1;
if (updateRequestedPath) {
requestedPath[requestIdx] = key;
}

// TODO: Can we hit a leaf node in the cache when traversing?
// We do not continue with this branch since the cache
if (cacheRes === undefined) {
cacheRes = cache[key] = {};
}

if (messageRes !== undefined) {
var nextIgnoreCount = ignoreCount;

// TODO: Potential performance gain since we know that
Expand Down Expand Up @@ -129,21 +131,6 @@ function merge(config, cache, message, depth, path, fromParent, fromKey) {
config.ignoreCount = ignoreCount;
}

// The second the incoming jsong must be fully qualified,
// anything that is not will be materialized into the provided cache
else {

// do not materialize, continue down the cache.
if (depth < path.length - 1) {
merge(config, cacheRes, {}, nextDepth, nextPath, cache, key);
}

// materialize the node
else {
cache[key] = {$type: $atom};
}
}

if (updateRequestedPath) {
requestedPath.length = requestIdx;
}
Expand Down
17 changes: 9 additions & 8 deletions src/cache/pathValueMerge.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,31 @@ module.exports = function pathValueMerge(cache, pathValue) {
var refs = [];
var values = [];
var invalidations = [];
var valueType = true;

// The invalidation case. Needed for reporting
// of call.
if (pathValue.value === undefined) {
// The pathValue invalidation shape.
if (pathValue.invalidated === true) {
invalidations.push({path: pathValue.path});
valueType = false;
}

// References. Needed for evaluationg suffixes in
// both call and get/set.
// References. Needed for evaluationg suffixes in all three types, get,
// call and set.
else if ((pathValue.value !== null) && (pathValue.value.$type === $ref)) {
refs.push({
path: pathValue.path,
value: pathValue.value.value
});
}


// Values. Needed for reporting for call.
else {
values.push(pathValue);
}

if (invalidations.length === 0) {
// Merges the values/refs/invs into the cache.
// If the type of pathValue is a valueType (reference or value) then merge
// it into the jsonGraph cache.
if (valueType) {
innerPathValueMerge(cache, pathValue);
}

Expand Down
File renamed without changes.
49 changes: 49 additions & 0 deletions src/router/get.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
var runGetAction = require('../run/get/runGetAction');
var get = 'get';
var recurseMatchAndExecute = require('../run/recurseMatchAndExecute');
var normalizePathSets = require('../operations/ranges/normalizePathSets');
var materialize = require('../run/materialize');
var Observable = require('rx').Observable;

/**
* The router get function
*/
module.exports = function routerGet(paths) {
var jsongCache = {};
var router = this;
var action = runGetAction(router, jsongCache);
var normPS = normalizePathSets(paths);
return recurseMatchAndExecute(router._matcher, action, normPS,
get, router, jsongCache).

// Turn it(jsongGraph, invalidations, missing, etc.) into a
// jsonGraph envelope
flatMap(function flatMapAfterRouterGet(details) {
var out = {
jsonGraph: details.jsonGraph
};


// If the unhandledPaths are present then we need to
// call the backup method for generating materialized.
if (details.unhandledPaths.length && router._unhandled.get) {
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 Observable.return(out);
}).

// We will continue to materialize over the whole jsonGraph message.
// This makes sense if you think about pathValues and an API that if
// ask for a range of 10 and only 8 were returned, it would not
// materialize for you, instead, allow the router to do that.
map(function(jsonGraphEnvelope) {
return materialize(router, normPS, jsonGraphEnvelope);
});
};
5 changes: 4 additions & 1 deletion src/run/call/runCallAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,8 @@ function runCallAction(matchAndPath, routerInstance, callPath, args,
filter(function(note) {
return note.kind !== 'C';
}).
map(noteToJsongOrPV(matchAndPath));
map(noteToJsongOrPV(matchAndPath.path)).
map(function(jsonGraphOrPV) {
return [matchAndPath.match, jsonGraphOrPV];
});
}
25 changes: 25 additions & 0 deletions src/run/conversion/errorToPathValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
var JSONGraphError = require('./../../errors/JSONGraphError');
module.exports = function errorToPathValue(error, path) {
var typeValue = {
$type: 'error',
value: {}
};

if (error.throwToNext) {
throw error;
}

// If it is a special JSONGraph error then pull all the data
if (error instanceof JSONGraphError) {
typeValue = error.typeValue;
}

else if (error instanceof Error) {
typeValue.value.message = error.message;
}

return {
path: path,
value: typeValue
};
};
Loading

0 comments on commit ed15ff2

Please sign in to comment.