-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #160 from michaelbpaulson/on-unhandled-set
feat(unhandled*): The set feature for unhandled paths.
- Loading branch information
Showing
6 changed files
with
566 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
src/operations/matcher/intersection/hasIntersectionWithTree.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/** | ||
* @param {PathSet} path - A simple path | ||
* @param {Object} tree - The tree should have `null` leaves to denote a | ||
* leaf node. | ||
*/ | ||
module.exports = function hasIntersectionWithTree(path, tree) { | ||
return _hasIntersection(path, tree, 0); | ||
}; | ||
|
||
function _hasIntersection(path, node, depth) { | ||
|
||
// Exit / base condition. We have reached the | ||
// length of our path and we are at a node of null. | ||
if (depth === path.length && node === null) { | ||
return true; | ||
} | ||
|
||
var key = path[depth]; | ||
var next = node[key]; | ||
|
||
// If its not undefined, then its a branch. | ||
if (node !== undefined) { | ||
return _hasIntersection(path, next, depth + 1); | ||
} | ||
|
||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
var set = 'set'; | ||
var recurseMatchAndExecute = require('./../run/recurseMatchAndExecute'); | ||
var runSetAction = require('./../run/set/runSetAction'); | ||
var materialize = require('../run/materialize'); | ||
var Observable = require('rx').Observable; | ||
var spreadPaths = require('./../support/spreadPaths'); | ||
var pathValueMerge = require('./../cache/pathValueMerge'); | ||
var optimizePathSets = require('./../cache/optimizePathSets'); | ||
var hasIntersectionWithTree = | ||
require('./../operations/matcher/intersection/hasIntersectionWithTree'); | ||
var getValue = require('./../cache/getValue'); | ||
var normalizePathSets = require('../operations/ranges/normalizePathSets'); | ||
var pathUtils = require('falcor-path-utils'); | ||
var collapse = pathUtils.collapse; | ||
|
||
/** | ||
* @returns {Observable.<JSONGraph>} | ||
* @private | ||
*/ | ||
module.exports = function routerSet(jsonGraph) { | ||
var jsongCache = {}; | ||
var router = this; | ||
var action = runSetAction(router, jsonGraph, jsongCache); | ||
jsonGraph.paths = normalizePathSets(jsonGraph.paths); | ||
return recurseMatchAndExecute(router._matcher, action, jsonGraph.paths, | ||
set, router, jsongCache). | ||
|
||
// Takes the jsonGraphEnvelope and extra details that comes out of the | ||
// recursive matching algorithm and either attempts the fallback | ||
// options or returns the built jsonGraph. | ||
flatMap(function(details) { | ||
var out = { | ||
jsonGraph: details.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) { | ||
var unhandledPaths = details.unhandledPaths; | ||
var jsonGraphFragment = {}; | ||
|
||
// PERFORMANCE: We know this is a potential performance downfall | ||
// PERFORMANCE: but we want to see if its even a corner case. | ||
// PERFORMANCE: Most likely this will not be hit, but if it does | ||
// PERFORMANCE: then we can take care of it | ||
// Set is interesting. This is what has to happen. | ||
// 1. incoming paths are spread so that each one is simple. | ||
// 2. incoming path, one at a time, are optimized by the | ||
// incoming jsonGraph. | ||
// 3. test intersection against incoming optimized path and | ||
// unhandledPathSet | ||
// 4. If 3 is true, build the jsonGraphFragment by using a | ||
// pathValue of optimizedPath and vale from un-optimized | ||
// path and original jsonGraphEnvelope. | ||
var jsonGraphEnvelope = {jsonGraph: jsonGraphFragment}; | ||
var unhandledPathsTree = unhandledPaths. | ||
reduce(function(acc, path) { | ||
pathValueMerge(acc, {path: path, value: null}); | ||
return acc; | ||
}, {}); | ||
|
||
// 1. Spread | ||
var pathIntersection = spreadPaths(jsonGraph.paths). | ||
|
||
// 2.1 Optimize. We know its one at a time therefore we | ||
// just pluck [0] out. | ||
map(function(path) { | ||
return [ | ||
// full path | ||
path, | ||
|
||
// optimized path | ||
optimizePathSets(details.jsonGraph, [path], | ||
router.maxRefFollow)[0]] | ||
}). | ||
|
||
// 2.2 Remove all the optimized paths that were found in | ||
// the cache. | ||
filter(function(x) { return x[1]; }). | ||
|
||
// 3.1 test intersection. | ||
map(function(pathAndOPath) { | ||
var oPath = pathAndOPath[1]; | ||
var hasIntersection = hasIntersectionWithTree( | ||
oPath, unhandledPathsTree); | ||
|
||
// Creates the pathValue if there are a path | ||
// intersection | ||
if (hasIntersection) { | ||
var value = | ||
getValue(jsonGraph.jsonGraph, pathAndOPath[0]); | ||
|
||
return { | ||
path: oPath, | ||
value: value | ||
}; | ||
} | ||
|
||
return null; | ||
}). | ||
|
||
// 3.2 strip out nulls (the non-intersection paths). | ||
filter(function(x) { return x !== null; }); | ||
|
||
// 4. build the optimized JSONGraph envelope. | ||
pathIntersection. | ||
reduce(function(acc, pathValue) { | ||
pathValueMerge(acc, pathValue); | ||
return acc; | ||
}, jsonGraphFragment); | ||
|
||
jsonGraphEnvelope.paths = collapse( | ||
pathIntersection.map(function(pV) { | ||
return pV.path; | ||
})); | ||
|
||
return router._unhandled.set(out, unhandledPaths, | ||
jsonGraphEnvelope); | ||
} | ||
|
||
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, jsonGraph.paths, jsonGraphEnvelope); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
var R = require('../../../src/Router'); | ||
var noOp = function() {}; | ||
var chai = require('chai'); | ||
var expect = chai.expect; | ||
var sinon = require('sinon'); | ||
var pathValueMerge = require('./../../../src/cache/pathValueMerge'); | ||
var Observable = require('rx').Observable; | ||
var $atom = require('./../../../src/support/types').$atom; | ||
|
||
describe('#get', function() { | ||
it('should return an empty Observable and just materialize values.', function(done) { | ||
var router = new R([]); | ||
var onUnhandledPaths = sinon.spy(function convert(paths) { | ||
return Observable.empty(); | ||
}); | ||
router.onUnhandledGet(onUnhandledPaths); | ||
|
||
var obs = router. | ||
get([['videos', 'summary']]); | ||
var onNext = sinon.spy(); | ||
obs. | ||
doAction(onNext, noOp, function() { | ||
expect(onNext.calledOnce).to.be.ok; | ||
expect(onNext.getCall(0).args[0]).to.deep.equals({ | ||
jsonGraph: { | ||
videos: { | ||
summary: {$type: $atom} | ||
} | ||
} | ||
}); | ||
expect(onUnhandledPaths.calledOnce).to.be.ok; | ||
expect(onUnhandledPaths.getCall(0).args[0]).to.deep.equals([ | ||
['videos', 'summary'] | ||
]); | ||
}). | ||
subscribe(noOp, done, done); | ||
}); | ||
it('should call the onUnhandledGet when the route completely misses a route.', function(done) { | ||
var router = new R([]); | ||
var onUnhandledPaths = sinon.spy(function convert(paths) { | ||
return paths.reduce(function(jsonGraph, path) { | ||
pathValueMerge(jsonGraph.jsonGraph, { | ||
path: path, | ||
value: 'missing' | ||
}); | ||
return jsonGraph; | ||
}, {jsonGraph: {}}); | ||
}); | ||
router.onUnhandledGet(onUnhandledPaths); | ||
|
||
var obs = router. | ||
get([['videos', 'summary']]); | ||
var onNext = sinon.spy(); | ||
obs. | ||
doAction(onNext, noOp, function() { | ||
expect(onNext.calledOnce).to.be.ok; | ||
expect(onNext.getCall(0).args[0]).to.deep.equals({ | ||
jsonGraph: { | ||
videos: { | ||
summary: 'missing' | ||
} | ||
} | ||
}); | ||
expect(onUnhandledPaths.calledOnce).to.be.ok; | ||
expect(onUnhandledPaths.getCall(0).args[0]).to.deep.equals([ | ||
['videos', 'summary'] | ||
]); | ||
}). | ||
subscribe(noOp, done, done); | ||
}); | ||
|
||
it('should call the onUnhandledGet when the route partially misses a route.', function(done) { | ||
var router = new R([{ | ||
route: 'videos.length', | ||
get: function() { | ||
return { | ||
path: ['videos', 'length'], | ||
value: 5 | ||
}; | ||
} | ||
}]); | ||
var onUnhandledPaths = sinon.spy(function convert(paths) { | ||
return paths.reduce(function(jsonGraph, path) { | ||
pathValueMerge(jsonGraph.jsonGraph, { | ||
path: path, | ||
value: 'missing' | ||
}); | ||
return jsonGraph; | ||
}, {jsonGraph: {}}); | ||
}); | ||
router.onUnhandledGet(onUnhandledPaths); | ||
|
||
var obs = router. | ||
get([['videos', ['length', 'summary']]]); | ||
var onNext = sinon.spy(); | ||
obs. | ||
doAction(onNext, noOp, function() { | ||
expect(onNext.calledOnce).to.be.ok; | ||
expect(onNext.getCall(0).args[0]).to.deep.equals({ | ||
jsonGraph: { | ||
videos: { | ||
summary: 'missing', | ||
length: 5 | ||
} | ||
} | ||
}); | ||
expect(onUnhandledPaths.calledOnce).to.be.ok; | ||
expect(onUnhandledPaths.getCall(0).args[0]).to.deep.equals([ | ||
['videos', 'summary'] | ||
]); | ||
}). | ||
subscribe(noOp, done, done); | ||
}); | ||
}); |
Oops, something went wrong.