Skip to content

Commit ed15ff2

Browse files
committed
feat(onUnhandled*): UnhandledGet works as specced.
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.
1 parent 10d6a26 commit ed15ff2

21 files changed

+614
-214
lines changed

src/Router.js

Lines changed: 45 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
var Keys = require('./Keys');
22
var parseTree = require('./parse-tree');
33
var matcher = require('./operations/matcher');
4-
var normalizePathSets = require('./operations/ranges/normalizePathSets');
54
var recurseMatchAndExecute = require('./run/recurseMatchAndExecute');
6-
var optimizePathSets = require('./cache/optimizePathSets');
7-
var pathValueMerge = require('./cache/pathValueMerge');
8-
var runGetAction = require('./run/get/runGetAction');
95
var runSetAction = require('./run/set/runSetAction');
106
var runCallAction = require('./run/call/runCallAction');
11-
var $atom = require('./support/types').$atom;
12-
var get = 'get';
137
var set = 'set';
148
var call = 'call';
159
var pathUtils = require('falcor-path-utils');
1610
var collapse = pathUtils.collapse;
17-
var JSONGraphError = require('./JSONGraphError');
11+
var JSONGraphError = require('./errors/JSONGraphError');
1812
var MAX_REF_FOLLOW = 50;
13+
var unhandled = require('./run/unhandled');
1914

2015
var Router = function(routes, options) {
2116
var opts = options || {};
@@ -25,41 +20,52 @@ var Router = function(routes, options) {
2520
this._matcher = matcher(this._rst);
2621
this._debug = opts.debug;
2722
this.maxRefFollow = opts.maxRefFollow || MAX_REF_FOLLOW;
23+
this._unhandled = {};
2824
};
2925

3026
Router.createClass = function(routes) {
31-
function C(options) {
32-
var opts = options || {};
33-
this._debug = opts.debug;
34-
}
27+
function C(options) {
28+
var opts = options || {};
29+
this._debug = opts.debug;
30+
}
3531

36-
C.prototype = new Router(routes);
37-
C.prototype.constructor = C;
32+
C.prototype = new Router(routes);
33+
C.prototype.constructor = C;
3834

39-
return C;
35+
return C;
4036
};
4137

4238
Router.prototype = {
43-
get: function(paths) {
44-
var jsongCache = {};
45-
var action = runGetAction(this, jsongCache);
46-
var router = this;
47-
var normPS = normalizePathSets(paths);
48-
return run(this._matcher, action, normPS, get, this, jsongCache).
49-
map(function(jsongEnv) {
50-
return materializeMissing(router, paths, jsongEnv);
51-
});
39+
/**
40+
* Performs the get algorithm on the router.
41+
* @param {PathSet[]} paths -
42+
* @returns {JSONGraphEnvelope}
43+
*/
44+
get: require('./router/get'),
45+
46+
/**
47+
* Takes in a function to call that has the same return inteface as any
48+
* route that will be called in the event of "unhandledPaths" on a get.
49+
*
50+
* @param {Function} unhandledHandler -
51+
* @returns {undefined}
52+
*/
53+
onUnhandledGet: function(unhandledHandler) {
54+
this._unhandled.get = unhandled(this, unhandledHandler);
5255
},
5356

5457
set: function(jsong) {
55-
// TODO: Remove the modelContext and replace with just jsongEnv
56-
// when http://github.com/Netflix/falcor-router/issues/24 is addressed
58+
5759
var jsongCache = {};
5860
var action = runSetAction(this, jsong, jsongCache);
59-
var router = this;
6061
return run(this._matcher, action, jsong.paths, set, this, jsongCache).
61-
map(function(jsongEnv) {
62-
return materializeMissing(router, jsong.paths, jsongEnv);
62+
63+
// Turn it(jsongGraph, invalidations, missing, etc.) into a
64+
// jsonGraph envelope
65+
map(function(details) {
66+
return {
67+
jsonGraph: details.jsonGraph
68+
};
6369
});
6470
},
6571

@@ -68,55 +74,39 @@ Router.prototype = {
6874
var action = runCallAction(this, callPath, args,
6975
suffixes, paths, jsongCache);
7076
var callPaths = [callPath];
71-
var router = this;
7277
return run(this._matcher, action, callPaths, call, this, jsongCache).
7378
map(function(jsongResult) {
7479
var reportedPaths = jsongResult.reportedPaths;
75-
var jsongEnv = materializeMissing(
76-
router,
77-
reportedPaths,
78-
jsongResult);
79-
80+
var jsongEnv = {
81+
jsonGraph: jsongResult.jsonGraph
82+
};
8083

84+
// Call must report the paths that have been produced.
8185
if (reportedPaths.length) {
82-
jsongEnv.paths = reportedPaths;
86+
// Collapse the reported paths as they may be inefficient
87+
// to send across the wire.
88+
jsongEnv.paths = collapse(reportedPaths);
8389
}
8490
else {
8591
jsongEnv.paths = [];
8692
jsongEnv.jsonGraph = {};
8793
}
8894

95+
// add the invalidated paths to the jsonGraph Envelope
8996
var invalidated = jsongResult.invalidated;
9097
if (invalidated && invalidated.length) {
9198
jsongEnv.invalidated = invalidated;
9299
}
93-
jsongEnv.paths = collapse(jsongEnv.paths);
100+
94101
return jsongEnv;
95102
});
96103
}
97104
};
98105

99106
function run(matcherFn, actionRunner, paths, method,
100107
routerInstance, jsongCache) {
101-
return recurseMatchAndExecute(
102-
matcherFn, actionRunner, paths, method, routerInstance, jsongCache);
103-
}
104-
105-
function materializeMissing(router, paths, jsongEnv, missingAtom) {
106-
var jsonGraph = jsongEnv.jsonGraph;
107-
var materializedAtom = missingAtom || {$type: $atom};
108-
109-
// Optimizes the pathSets from the jsong then
110-
// inserts atoms of undefined.
111-
optimizePathSets(jsonGraph, paths, router.maxRefFollow).
112-
forEach(function(optMissingPath) {
113-
pathValueMerge(jsonGraph, {
114-
path: optMissingPath,
115-
value: materializedAtom
116-
});
117-
});
118-
119-
return {jsonGraph: jsonGraph};
108+
return recurseMatchAndExecute(matcherFn, actionRunner, paths, method,
109+
routerInstance, jsongCache);
120110
}
121111

122112
Router.ranges = Keys.ranges;

src/cache/jsongMerge.js

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
var iterateKeySet = require('falcor-path-utils').iterateKeySet;
22
var types = require('./../support/types');
33
var $ref = types.$ref;
4-
var $atom = types.$atom;
54
var clone = require('./../support/clone');
65
var cloneArray = require('./../support/cloneArray');
76
var catAndSlice = require('./../support/catAndSlice');
@@ -14,6 +13,7 @@ module.exports = function jsongMerge(cache, jsongEnv) {
1413
var j = jsongEnv.jsonGraph;
1514
var references = [];
1615
var values = [];
16+
1717
paths.forEach(function(p) {
1818
merge({
1919
cacheRoot: cache,
@@ -25,6 +25,7 @@ module.exports = function jsongMerge(cache, jsongEnv) {
2525
ignoreCount: 0
2626
}, cache, j, 0, p);
2727
});
28+
2829
return {
2930
references: references,
3031
values: values
@@ -87,20 +88,21 @@ function merge(config, cache, message, depth, path, fromParent, fromKey) {
8788
var cacheRes = cache[key];
8889
var messageRes = message[key];
8990

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

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

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

103-
if (messageRes !== undefined) {
104106
var nextIgnoreCount = ignoreCount;
105107

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

132-
// The second the incoming jsong must be fully qualified,
133-
// anything that is not will be materialized into the provided cache
134-
else {
135-
136-
// do not materialize, continue down the cache.
137-
if (depth < path.length - 1) {
138-
merge(config, cacheRes, {}, nextDepth, nextPath, cache, key);
139-
}
140-
141-
// materialize the node
142-
else {
143-
cache[key] = {$type: $atom};
144-
}
145-
}
146-
147134
if (updateRequestedPath) {
148135
requestedPath.length = requestIdx;
149136
}

src/cache/pathValueMerge.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,31 @@ module.exports = function pathValueMerge(cache, pathValue) {
1010
var refs = [];
1111
var values = [];
1212
var invalidations = [];
13+
var valueType = true;
1314

14-
// The invalidation case. Needed for reporting
15-
// of call.
16-
if (pathValue.value === undefined) {
15+
// The pathValue invalidation shape.
16+
if (pathValue.invalidated === true) {
1717
invalidations.push({path: pathValue.path});
18+
valueType = false;
1819
}
1920

20-
// References. Needed for evaluationg suffixes in
21-
// both call and get/set.
21+
// References. Needed for evaluationg suffixes in all three types, get,
22+
// call and set.
2223
else if ((pathValue.value !== null) && (pathValue.value.$type === $ref)) {
2324
refs.push({
2425
path: pathValue.path,
2526
value: pathValue.value.value
2627
});
2728
}
2829

29-
3030
// Values. Needed for reporting for call.
3131
else {
3232
values.push(pathValue);
3333
}
3434

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

File renamed without changes.

src/router/get.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
var runGetAction = require('../run/get/runGetAction');
2+
var get = 'get';
3+
var recurseMatchAndExecute = require('../run/recurseMatchAndExecute');
4+
var normalizePathSets = require('../operations/ranges/normalizePathSets');
5+
var materialize = require('../run/materialize');
6+
var Observable = require('rx').Observable;
7+
8+
/**
9+
* The router get function
10+
*/
11+
module.exports = function routerGet(paths) {
12+
var jsongCache = {};
13+
var router = this;
14+
var action = runGetAction(router, jsongCache);
15+
var normPS = normalizePathSets(paths);
16+
return recurseMatchAndExecute(router._matcher, action, normPS,
17+
get, router, jsongCache).
18+
19+
// Turn it(jsongGraph, invalidations, missing, etc.) into a
20+
// jsonGraph envelope
21+
flatMap(function flatMapAfterRouterGet(details) {
22+
var out = {
23+
jsonGraph: details.jsonGraph
24+
};
25+
26+
27+
// If the unhandledPaths are present then we need to
28+
// call the backup method for generating materialized.
29+
if (details.unhandledPaths.length && router._unhandled.get) {
30+
var unhandledPaths = details.unhandledPaths;
31+
32+
// The 3rd argument is the beginning of the actions arguments,
33+
// which for get is the same as the unhandledPaths.
34+
return router._unhandled.get(out,
35+
unhandledPaths,
36+
unhandledPaths);
37+
}
38+
39+
return Observable.return(out);
40+
}).
41+
42+
// We will continue to materialize over the whole jsonGraph message.
43+
// This makes sense if you think about pathValues and an API that if
44+
// ask for a range of 10 and only 8 were returned, it would not
45+
// materialize for you, instead, allow the router to do that.
46+
map(function(jsonGraphEnvelope) {
47+
return materialize(router, normPS, jsonGraphEnvelope);
48+
});
49+
};

src/run/call/runCallAction.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,5 +180,8 @@ function runCallAction(matchAndPath, routerInstance, callPath, args,
180180
filter(function(note) {
181181
return note.kind !== 'C';
182182
}).
183-
map(noteToJsongOrPV(matchAndPath));
183+
map(noteToJsongOrPV(matchAndPath.path)).
184+
map(function(jsonGraphOrPV) {
185+
return [matchAndPath.match, jsonGraphOrPV];
186+
});
184187
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
var JSONGraphError = require('./../../errors/JSONGraphError');
2+
module.exports = function errorToPathValue(error, path) {
3+
var typeValue = {
4+
$type: 'error',
5+
value: {}
6+
};
7+
8+
if (error.throwToNext) {
9+
throw error;
10+
}
11+
12+
// If it is a special JSONGraph error then pull all the data
13+
if (error instanceof JSONGraphError) {
14+
typeValue = error.typeValue;
15+
}
16+
17+
else if (error instanceof Error) {
18+
typeValue.value.message = error.message;
19+
}
20+
21+
return {
22+
path: path,
23+
value: typeValue
24+
};
25+
};

0 commit comments

Comments
 (0)