swagger-router
Advanced tools
Comparing version 0.0.1 to 0.0.3
569
index.js
"use strict"; | ||
// For Map primarily | ||
require("es6-shim"); | ||
if (!global.Promise || !global.Promise.promisify) { | ||
global.Promise = require('bluebird'); | ||
} | ||
/*** | ||
* :SECTION 1: | ||
* Private module variables and methods | ||
***/ | ||
function robustDecodeURIComponent(uri) { | ||
if (!/%/.test(uri)) { | ||
return uri; | ||
} else { | ||
return uri.replace(/(%[0-9a-fA-F][0-9a-fA-F])+/g, function(m) { | ||
try { | ||
return decodeURIComponent( m ); | ||
} catch ( e ) { | ||
return m; | ||
} | ||
}); | ||
} | ||
} | ||
// / ( {pattern} or {+pattern} )|( {/pattern} | ||
var splitRe = /(\/)(?:\{([\+])?([^:\}\/]+)(?::([^}]+))?\}|([^\/\{]*))|(?:{([\/\+]))([^:\}\/]+)(?::([^}]+))?\}/g; | ||
function parsePattern (pattern) { | ||
var res = []; | ||
splitRe.lastIndex = 0; | ||
var m; | ||
do { | ||
m = splitRe.exec(pattern); | ||
if (m) { | ||
if (m[1] === '/') { | ||
if (m[5] !== undefined) { | ||
// plain path segment | ||
res.push(robustDecodeURIComponent(m[5])); | ||
} else if (m[3]) { | ||
// templated path segment | ||
res.push({ | ||
name: m[3], | ||
modifier: m[2], | ||
pattern: m[4] | ||
}); | ||
} | ||
} else if (m[7]) { | ||
// Optional path segment: | ||
// - {/foo} or {/foo:bar} | ||
// - {+foo} | ||
res.push({ | ||
name: m[7], | ||
modifier: m[6], | ||
pattern: m[8] | ||
}); | ||
} else { | ||
throw new Error('The impossible happened!'); | ||
} | ||
} | ||
} while (m); | ||
return res; | ||
} | ||
// Parse a path or pattern | ||
function parsePath (path, isPattern) { | ||
if (Array.isArray(path)) { | ||
return path; | ||
} else if (!isPattern) { | ||
var bits = path.replace(/^\//, '').split(/\//); | ||
if (!/%/.test(path)) { | ||
// fast path | ||
return bits; | ||
} else { | ||
return bits.map(function(bit) { | ||
return robustDecodeURIComponent(bit); | ||
}); | ||
} | ||
} else { | ||
return parsePattern(path); | ||
} | ||
} | ||
/*** | ||
* :SECTION 2: | ||
* Module class definitions | ||
***/ | ||
/** | ||
* Represents a URI object which can optionally contain and | ||
* bind optional variables encountered in the URI string | ||
* | ||
* @param {String|URI} uri the URI path or object to create a new URI from | ||
* @param {Object} params the values for variables encountered in the URI path (optional) | ||
* @param {boolean} asPattern Whether to parse the URI as a pattern (optional) | ||
* @return {URI} URI object. Public properties: | ||
* - `params` {object} mutable. Parameter object. | ||
* - `path` {array} immutable. | ||
*/ | ||
function URI(uri, params, asPattern) { | ||
this.params = params || {}; | ||
if (uri && uri.constructor === URI) { | ||
// this.path is considered immutable, so can be shared with other URI | ||
// instances | ||
this.path = uri.path; | ||
} else if (uri && (uri.constructor === String || Array.isArray(uri))) { | ||
this.path = parsePath(uri, asPattern); | ||
} else if (uri !== '') { | ||
throw new Error('Invalid path passed into URI constructor: ' + uri); | ||
} | ||
} | ||
/** | ||
* Builds and returns the full, bounded string path for this URI object | ||
* | ||
* @return {String} the complete path of this URI object | ||
* @param {string} format Either 'simplePattern' or 'fullPattern'. [optional] | ||
* @return {string} URI path | ||
*/ | ||
URI.prototype.toString = function (format) { | ||
var uriStr = ''; | ||
for (var i = 0; i < this.path.length; i++) { | ||
var segment = this.path[i]; | ||
if (segment && segment.constructor === Object) { | ||
var segmentValue = this.params[segment.name]; | ||
if (segmentValue === undefined) { | ||
segmentValue = segment.pattern; | ||
} | ||
if (segmentValue !== undefined) { | ||
if (!format || format === 'simplePattern' || !segment.name) { | ||
// Normal mode | ||
uriStr += '/' + encodeURIComponent(segmentValue); | ||
} else { | ||
uriStr += '/{' + (segment.modifier || '') | ||
+ encodeURIComponent(segment.name) + ':' | ||
+ encodeURIComponent(segmentValue) + '}'; | ||
} | ||
} else if (format && !segment.modifier) { | ||
uriStr += '/{' + encodeURIComponent(segment.name) + '}'; | ||
} else if (format) { | ||
uriStr += '{' + (segment.modifier || '') | ||
+ encodeURIComponent(segment.name) | ||
+ '}'; | ||
} else { | ||
if (segment.modifier === '+') { | ||
// Add trailing slash | ||
uriStr += '/'; | ||
} | ||
// Omit optional segment & return | ||
return uriStr; | ||
} | ||
} else { | ||
uriStr += '/' + encodeURIComponent(segment); | ||
} | ||
} | ||
return uriStr; | ||
}; | ||
/** | ||
* Expand all parameters in the URI and return a new URI. | ||
* @return {URI} | ||
*/ | ||
URI.prototype.expand = function() { | ||
var res = new Array(this.path.length); | ||
for (var i = 0; i < this.path.length; i++) { | ||
var segment = this.path[i]; | ||
if (segment && segment.constructor === Object) { | ||
var segmentValue = this.params[segment.name]; | ||
if (segmentValue === undefined) { | ||
segmentValue = segment.pattern; | ||
if (segmentValue === undefined) { | ||
if (segment.modifier) { | ||
// Okay to end the URI here | ||
// Pop over-allocated entries | ||
while (res[res.length - 1] === undefined) { | ||
res.pop(); | ||
} | ||
return new URI(res); | ||
} else { | ||
throw new Error('URI.expand: parameter ' + segment.name + ' not defined!'); | ||
} | ||
} | ||
} | ||
res[i] = segmentValue; | ||
} else { | ||
res[i] = segment; | ||
} | ||
} | ||
return new URI(res); | ||
}; | ||
/** | ||
* Checks if the URI starts with the given path prefix | ||
* | ||
* @param {String|URI} pathOrURI the prefix path to check for | ||
* @return {Boolean} whether this URI starts with the given prefix path | ||
*/ | ||
URI.prototype.startsWith = function (pathOrURI) { | ||
var uri; | ||
if (!pathOrURI) { | ||
return true; | ||
} | ||
if (pathOrURI.constructor === URI) { | ||
uri = pathOrURI; | ||
} else { | ||
uri = new URI(pathOrURI); | ||
} | ||
// if our URI is shorter than the one we are | ||
// comparing to, it doesn't start with that prefix | ||
if (this.path.length < uri.path.length) { | ||
return false; | ||
} | ||
// check each component | ||
for (var idx = 0; idx < uri.path.length; idx++) { | ||
var mySeg = this.path[idx]; | ||
var otherSeg = uri.path[idx]; | ||
if (mySeg.constructor === Object && otherSeg.constructor === Object) { | ||
// both path are named variables | ||
// nothing to do | ||
continue; | ||
} else if (mySeg.constructor === Object) { | ||
// we have a named variable, but there is a string | ||
// given in the prefix | ||
if (mySeg.pattern && mySeg.pattern !== otherSeg) { | ||
// they differ | ||
return false; | ||
} | ||
} else if (otherSeg.constructor === Object) { | ||
// we have a fixed string, but a variable has been | ||
// given in the prefix - nothing to do | ||
continue; | ||
} else if (mySeg !== otherSeg) { | ||
// both are strings, but they differ | ||
return false; | ||
} | ||
} | ||
// ok, no differences found | ||
return true; | ||
}; | ||
// For JSON.stringify | ||
URI.prototype.toJSON = URI.prototype.toString; | ||
// For util.inspect, console.log & co | ||
URI.prototype.inspect = function () { | ||
// Quote the string | ||
return JSON.stringify(this.toString()); | ||
}; | ||
/* | ||
@@ -12,51 +261,65 @@ * A node in the lookup graph. | ||
*/ | ||
function Node () { | ||
function Node (value) { | ||
// The value for a path ending on this node. Public property. | ||
this.value = null; | ||
this.value = value || null; | ||
// Internal properties. | ||
this._map = {}; | ||
this._name = undefined; | ||
this._wildcard = undefined; | ||
this._children = {}; | ||
this._paramName = null; | ||
this._parent = null; | ||
} | ||
Node.prototype.set = function(key, value) { | ||
Node.prototype._keyPrefix = '/'; | ||
Node.prototype._keyPrefixRegExp = /^\//; | ||
Node.prototype.setChild = function(key, child) { | ||
var self = this; | ||
if (key.constructor === String) { | ||
this._map['k' + key] = value; | ||
} else if (key.name && key.pattern && key.pattern.constructor === String) { | ||
// A named but plain key. Check if the name matches & set it normally. | ||
if (this._name && this._name !== key.name) { | ||
throw new Error("Captured pattern parameter " + key.name | ||
+ " does not match existing name " + this._name); | ||
} | ||
this._name = key.name; | ||
this._map['k' + key.pattern] = value; | ||
this._children[this._keyPrefix + key] = child; | ||
} else if (key.name && key.pattern | ||
&& key.modifier !== '+' | ||
&& key.pattern.constructor === String) { | ||
// A named but plain key. | ||
child._paramName = key.name; | ||
this._children[this._keyPrefix + key.pattern] = child; | ||
} else if (key.modifier === '+') { | ||
child._paramName = key.name; | ||
this._children['**'] = child; | ||
} else { | ||
// Setting up a wildcard match | ||
// Check if there are already other non-empty keys | ||
var longKeys = Object.keys(this._map).filter(function(key) { | ||
return key.length > 1; | ||
}); | ||
if (longKeys.length) { | ||
throw new Error("Can't register \"" + key + "\" in a wildcard path segment!"); | ||
} else { | ||
this._name = key.name; | ||
// Could handle a modifier or regexp here as well | ||
this._wildcard = value; | ||
} | ||
child._paramName = key.name; | ||
this._children['*'] = child; | ||
} | ||
}; | ||
Node.prototype.get = function(segment, params) { | ||
Node.prototype.getChild = function(segment, params) { | ||
if (segment.constructor === String) { | ||
// Fast path | ||
if (segment !== '') { | ||
var res = this._map['k' + segment] || this._wildcard; | ||
if (this._name && res) { | ||
params[this._name] = segment; | ||
var res = this._children[this._keyPrefix + segment]; | ||
if (!res) { | ||
if (segment !== '') { | ||
// Fall back to the wildcard match, but only if the segment is | ||
// non-empty. | ||
res = this._children['*']; | ||
if (!res && this._children['**']) { | ||
res = this._children['**']; | ||
// Build up an array for ** matches ({+foo}) | ||
if (!Array.isArray(params[res._paramName])) { | ||
params[res._paramName] = [segment]; | ||
} else { | ||
params[res._paramName].push(segment); | ||
} | ||
// We are done. | ||
return res; | ||
} | ||
} | ||
} | ||
if (res) { | ||
if (res._paramName) { | ||
params[res._paramName] = segment; | ||
} | ||
return res; | ||
} else { | ||
// Don't match the wildcard with an empty segment. | ||
return this._map['k' + segment]; | ||
return null; | ||
} | ||
@@ -68,6 +331,7 @@ | ||
// Unwrap the pattern | ||
return this.get(segment.pattern, params); | ||
} else if (segment.name === this._name) { | ||
return this.getChild(segment.pattern, params); | ||
} else if (this._children['*'] | ||
&& this._children['*']._paramName === segment.name) { | ||
// XXX: also compare modifier! | ||
return this._wildcard; | ||
return this._children['*'] || null; | ||
} | ||
@@ -77,3 +341,3 @@ }; | ||
Node.prototype.hasChildren = function () { | ||
return Object.keys(this._map).length || this._wildcard; | ||
return Object.keys(this._children).length || this._children['*']; | ||
}; | ||
@@ -83,11 +347,11 @@ | ||
var self = this; | ||
if (this._wildcard) { | ||
if (this._children['*'] || this._children['**']) { | ||
return []; | ||
} else { | ||
var res = []; | ||
Object.keys(this._map).forEach(function(key) { | ||
Object.keys(this._children).forEach(function(key) { | ||
// Only list '' if there are children (for paths like | ||
// /double//slash) | ||
if (key !== 'k' || self._map[key].hasChildren()) { | ||
res.push(key.replace(/^k/, '')); | ||
if (key !== self._keyPrefix || self._children[key].hasChildren()) { | ||
res.push(key.replace(self._keyPrefixRegExp, '')); | ||
} | ||
@@ -99,61 +363,101 @@ }); | ||
function Router () { | ||
this._root = new Node(); | ||
// Map for sharing of sub-trees corresponding to the same specs, using | ||
// object identity. | ||
this._nodes = new Map(); | ||
} | ||
//- interface: | ||
// - `#addSpec(spec, [prefix])` | ||
// - `#delSpec(spec, [prefix])` | ||
// - `#route(path)` | ||
// - path / prefix are arrays by default | ||
// Shallow clone, allows sharing of subtrees in DAG | ||
Node.prototype.clone = function () { | ||
var c = new Node(); | ||
c._children = this._children; | ||
return c; | ||
}; | ||
function normalizePath (path) { | ||
if (Array.isArray(path)) { | ||
// Nothing to be done | ||
return path; | ||
} else if (path.split) { | ||
return path.replace(/^\//, '').split(/\//); | ||
} else { | ||
throw new Error("Invalid path: " + path); | ||
} | ||
} | ||
// Call promise-returning fn for each node value, with the path to the value | ||
Node.prototype.visitAsync = function(fn, path) { | ||
path = path || []; | ||
var self = this; | ||
// First value, then each of the children (one by one) | ||
return fn(self.value, path) | ||
.then(function() { | ||
return Promise.resolve(Object.keys(self._children)) | ||
.each(function(childKey) { | ||
var segment = childKey.replace(/^\//, ''); | ||
var child = self._children[childKey]; | ||
if (child === self) { | ||
// Don't enter an infinite loop on ** | ||
return; | ||
} else { | ||
return child.visitAsync(fn, path.concat([segment])); | ||
} | ||
}); | ||
}); | ||
}; | ||
function parsePattern (pattern) { | ||
var bits = normalizePath(pattern); | ||
// Re-join {/var} patterns | ||
for (var i = 0; i < bits.length - 1; i++) { | ||
if (bits[i] === '{' && /}$/.test(bits[i+1])) { | ||
bits.splice(i, 2, '{/' + bits[i+1]); | ||
} | ||
// Work around recursive structure in ** terminal nodes | ||
function printableValue (value) { | ||
var res = {}; | ||
if (!value || ! (value instanceof Object)) { | ||
return value; | ||
} | ||
// Parse pattern segments and convert them to objects to be consumed by | ||
// Node.set(). | ||
return bits.map(function(bit) { | ||
// Support named but fixed values as | ||
// {domain:en.wikipedia.org} | ||
var m = /^{([+\/])?([a-zA-Z0-9_]+)(?::([^}]+))?}$/.exec(bit); | ||
if (m) { | ||
if (m[1]) { | ||
throw new Error("Modifiers are not yet implemented!"); | ||
} | ||
return { | ||
modifier: m[1], | ||
name: m[2], | ||
pattern: m[3] | ||
}; | ||
Object.keys(value).forEach(function(key) { | ||
var val = value[key]; | ||
if (key === 'methods') { | ||
var newMethods = {}; | ||
Object.keys(val).forEach(function(method) { | ||
newMethods[method] = '<' + val[method].name + '>'; | ||
}); | ||
res.methods = newMethods; | ||
} else { | ||
return bit; | ||
res[key] = val; | ||
} | ||
}); | ||
return res; | ||
} | ||
Router.prototype._buildTree = function(segments, value) { | ||
Node.prototype.toJSON = function () { | ||
if (this._children['**'] === this) { | ||
return { | ||
value: printableValue(this.value), | ||
_children: '<recursive>', | ||
_paramName: this._paramName | ||
}; | ||
} else { | ||
return { | ||
value: printableValue(this.value), | ||
_children: this._children, | ||
_paramName: this._paramName | ||
}; | ||
} | ||
}; | ||
/* | ||
* The main router object | ||
*/ | ||
function Router (options) { | ||
// Options: | ||
// - specHandler(spec) -> spec' | ||
// - pathHandler(pathSpec) -> pathSpec' | ||
this._options = options || {}; | ||
this._root = new Node(); | ||
} | ||
// XXX modules: variant that builds a prefix tree from a path array, but pass | ||
// in a spec instead of a value | ||
Router.prototype._buildTree = function(path, value) { | ||
var node = new Node(); | ||
if (segments.length) { | ||
var segment = segments[0]; | ||
var subTree = this._buildTree(segments.slice(1), value); | ||
node.set(segment, subTree); | ||
if (path.length) { | ||
var segment = path[0]; | ||
if (segment.modifier === '+') { | ||
// Set up a recursive match and end the traversal | ||
var recursionNode = new Node(); | ||
recursionNode.value = value; | ||
recursionNode.setChild(segment, recursionNode); | ||
node.setChild(segment, recursionNode); | ||
} else { | ||
var subTree = this._buildTree(path.slice(1), value); | ||
node.setChild(segment, subTree); | ||
if (segment.modifier === '/') { | ||
// Set the value for each optional path segment ({/foo}) | ||
node.value = value; | ||
subTree.value = value; | ||
} | ||
} | ||
} else { | ||
@@ -165,31 +469,40 @@ node.value = value; | ||
Router.prototype.addSpec = function addSpec(spec, prefix) { | ||
var self = this; | ||
if (!spec || !spec.paths) { | ||
throw new Error("No spec or no paths defined in spec!"); | ||
} | ||
// Get the prefix | ||
prefix = parsePattern(prefix || []); | ||
for (var path in spec.paths) { | ||
// Skip over the empty first element | ||
var segments = parsePattern(path); | ||
self._extend(prefix.concat(segments), self._root, spec.paths[path]); | ||
Router.prototype.specToTree = function (spec) { | ||
var root = new Node(); | ||
for (var pathPattern in spec.paths) { | ||
var path = parsePath(pathPattern, true); | ||
this._extend(path, root, spec.paths[pathPattern]); | ||
} | ||
return root; | ||
}; | ||
Router.prototype.setTree = function(tree) { | ||
this._root = tree; | ||
}; | ||
Router.prototype.delSpec = function delSpec(spec, prefix) { | ||
// Possible implementation: | ||
// - perform a *recursive* lookup for the leaf node | ||
// - | ||
// - Perform a *recursive* lookup for each leaf node. | ||
// - Walk up the tree and remove nodes as long as `.hasChildren()` is | ||
// false. | ||
// This will work okay in a tree, but would clash with subtree sharing in | ||
// a graph. We should perform some benchmarks to see if subtree sharing is | ||
// worth it. Until then we probably don't need spec deletion anyway, as we | ||
// can always re-build the entire router from scratch. | ||
throw new Error("Not implemented"); | ||
}; | ||
// Extend an existing route tree with a new path by walking the existing tree | ||
// and inserting new subtrees at the desired location. | ||
Router.prototype._extend = function route(path, node, value) { | ||
var params = {}; | ||
var origNode = node; | ||
for (var i = 0; i < path.length; i++) { | ||
var nextNode = node.get(path[i], params); | ||
if (!nextNode || !nextNode.get) { | ||
var nextNode = node.getChild(path[i], params); | ||
if (!nextNode || !nextNode.getChild) { | ||
// Found our extension point | ||
node.set(path[i], this._buildTree(path.slice(i+1), value)); | ||
node.setChild(path[i], this._buildTree(path.slice(i+1), value)); | ||
//if (path[path.length - 1].modifier === '+') { | ||
// console.log(JSON.stringify(node, null, 2)); | ||
//} | ||
return; | ||
@@ -200,5 +513,8 @@ } else { | ||
} | ||
node.value = value; | ||
if (value !== undefined) { | ||
node.value = value; | ||
} | ||
}; | ||
// Lookup worker. | ||
Router.prototype._lookup = function route(path, node) { | ||
@@ -208,9 +524,9 @@ var params = {}; | ||
for (var i = 0; i < path.length; i++) { | ||
if (!node || !node.get) { | ||
if (!node || !node.getChild) { | ||
return null; | ||
} | ||
prevNode = node; | ||
node = node.get(path[i], params); | ||
node = node.getChild(path[i], params); | ||
} | ||
if (node && node.value) { | ||
if (node || prevNode && path[path.length - 1] === '') { | ||
if (path[path.length - 1] === '') { | ||
@@ -222,3 +538,3 @@ // Pass in a listing | ||
params: params, | ||
value: node.value | ||
value: (node && node.value || null) | ||
}; | ||
@@ -244,6 +560,25 @@ } else { | ||
Router.prototype.lookup = function route(path) { | ||
path = normalizePath(path); | ||
return this._lookup(path, this._root); | ||
if (!path) { | ||
throw new Error('Path expected!'); | ||
} else if (path.constructor === String) { | ||
path = parsePath(path); | ||
} else if (path.constructor === URI) { | ||
path = path.path; | ||
} | ||
var res = this._lookup(path, this._root); | ||
if (res) { | ||
return { | ||
params: res.params, | ||
value: res.value | ||
}; | ||
} else { | ||
return res; | ||
} | ||
}; | ||
module.exports = Router; | ||
module.exports = { | ||
Router: Router, | ||
URI: URI, | ||
Node: Node | ||
}; | ||
{ | ||
"name": "swagger-router", | ||
"version": "0.0.1", | ||
"version": "0.0.3", | ||
"description": "An efficient swagger 2 based router with support for multiple APIs. For use in RESTBase.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha" | ||
"test": "mocha", | ||
"coverage": "istanbul cover _mocha -- -R spec", | ||
"coveralls": "cat ./coverage/lcov.info | coveralls" | ||
}, | ||
@@ -25,8 +27,13 @@ "repository": { | ||
"dependencies": { | ||
"es6-shim": "^0.22.1" | ||
"bluebird": "^2.7.1", | ||
"es6-shim": "^0.22.1", | ||
"js-yaml": "~3.2.2" | ||
}, | ||
"devDependencies": { | ||
"mocha": "x.x.x", | ||
"mocha-jshint": "0.0.9" | ||
"mocha-jshint": "0.0.9", | ||
"istanbul": "0.3.5", | ||
"mocha-lcov-reporter": "0.0.1", | ||
"coveralls": "2.11.2" | ||
} | ||
} |
# Swagger 2 router | ||
[![Build | ||
Status](https://travis-ci.org/gwicke/swagger-router.svg?branch=master)](https://travis-ci.org/gwicke/swagger-router) | ||
@@ -3,0 +5,0 @@ ## Features |
@@ -9,3 +9,5 @@ 'use strict'; | ||
var deepEqual = require('assert').deepEqual; | ||
var Router = require('../index'); | ||
var swaggerRouter = require('../index'); | ||
var Router = swaggerRouter.Router; | ||
var URI = swaggerRouter.URI; | ||
@@ -32,3 +34,8 @@ function listingHandler (list) { return list; } | ||
'/double//': '/double//', | ||
'/double//slash': '/double//slash' | ||
'/double//slash': '/double//slash', | ||
'/some/really/long/path': '/some/really/long/path', | ||
// Modifiers: optional path segments | ||
'/simple/{templated}{/path}': '/simple/{templated}{/path}', | ||
'/several{/optional}{/path}{+segments}': '/several{/optional}{/path}{+segments}', | ||
'/optional/{+path}': '/optional/{+path}' | ||
} | ||
@@ -120,3 +127,103 @@ } | ||
}, | ||
'/en.wikipedia.org/v1/some/really/long/path': { | ||
value: '/some/really/long/path', | ||
params: { | ||
domain: 'en.wikipedia.org' | ||
} | ||
}, | ||
// Optional path segments | ||
'/en.wikipedia.org/v1/several': { | ||
value: '/several{/optional}{/path}{+segments}', | ||
params: { | ||
domain: 'en.wikipedia.org' | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/several/optional': { | ||
value: '/several{/optional}{/path}{+segments}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
optional: 'optional' | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/several/optional/path': { | ||
value: '/several{/optional}{/path}{+segments}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
optional: 'optional', | ||
path: 'path' | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/several/optional/path/segments': { | ||
value: '/several{/optional}{/path}{+segments}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
optional: 'optional', | ||
path: 'path', | ||
segments: ['segments'], | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/several/optional/path/segments/a': { | ||
value: '/several{/optional}{/path}{+segments}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
optional: 'optional', | ||
path: 'path', | ||
segments: ['segments','a'], | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/several/optional/path/segments/a/b': { | ||
value: '/several{/optional}{/path}{+segments}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
optional: 'optional', | ||
path: 'path', | ||
segments: ['segments','a','b'], | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/simple/templated': { | ||
value: '/simple/{templated}{/path}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
templated: 'templated' | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/simple/templated/path': { | ||
value: '/simple/{templated}{/path}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
templated: 'templated', | ||
path: 'path' | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/simple/templated/path/toolong': null, | ||
'/en.wikipedia.org/v1/optional': { | ||
params: { | ||
domain: 'en.wikipedia.org' | ||
}, | ||
value: null | ||
}, | ||
'/en.wikipedia.org/v1/optional/': { | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
_ls: [] | ||
}, | ||
value: null | ||
}, | ||
'/en.wikipedia.org/v1/optional/path': { | ||
value: '/optional/{+path}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
path: ['path'] | ||
} | ||
}, | ||
'/en.wikipedia.org/v1/optional/path/bits': { | ||
value: '/optional/{+path}', | ||
params: { | ||
domain: 'en.wikipedia.org', | ||
path: ['path','bits'] | ||
} | ||
}, | ||
// A few paths that should not match | ||
@@ -129,12 +236,42 @@ '/en.wikipedia.org/v1/pages': null, | ||
var domains = ['en.wikipedia.org','de.wikipedia.org']; | ||
function makeFullSpec () { | ||
var domains = ['en.wikipedia.org', 'de.wikipedia.org', 'fr.wikipedia.org', 'es.wikipedia.org']; | ||
function addPrefixedPaths(newPaths, prefix, paths) { | ||
var newSpec = {}; | ||
for (var path in paths) { | ||
newPaths[prefix + path] = paths[path]; | ||
} | ||
} | ||
var fullPaths = {}; | ||
specs.forEach(function(spec) { | ||
domains.forEach(function(domain) { | ||
addPrefixedPaths(fullPaths, '/{domain:' + domain + '}/v1', spec.paths); | ||
}); | ||
}); | ||
return { | ||
paths: fullPaths | ||
}; | ||
} | ||
var router = new Router(); | ||
specs.forEach(function(spec) { | ||
domains.forEach(function(domain) { | ||
router.addSpec(spec, '/{domain:' + domain + '}/v1'); | ||
var fullSpec = makeFullSpec(); | ||
var tree = router.specToTree(fullSpec); | ||
router.setTree(tree); | ||
describe('Set of lookups', function() { | ||
Object.keys(expectations).forEach(function(key) { | ||
var val = expectations[key]; | ||
it('match: ' + JSON.stringify(key), function() { | ||
deepEqual(router.lookup(key), val); | ||
}); | ||
}); | ||
}); | ||
describe('swagger-router', function() { | ||
router.setTree(tree.clone()); | ||
describe('Repeat on cloned tree', function() { | ||
@@ -149,1 +286,81 @@ Object.keys(expectations).forEach(function(key) { | ||
describe('URI', function() { | ||
it('to URI and back', function() { | ||
var uri = new URI('/{domain:some}/path/to/something', {}, true); | ||
uri = new URI(uri, {domain: 'foo/bar'}); | ||
deepEqual(uri.toString(), '/foo%2Fbar/path/to/something'); | ||
deepEqual(uri.expand().path, ['foo/bar','path','to','something']); | ||
}); | ||
it('to URI and back, no pattern', function() { | ||
var uri = new URI('/{domain:some}/path/to/something', {domain: 'foo'}); | ||
deepEqual(uri.toString(), '/%7Bdomain%3Asome%7D/path/to/something'); | ||
deepEqual(uri.expand().path, ['{domain:some}','path','to','something']); | ||
}); | ||
it('{/patterns} empty', function() { | ||
var uri = new URI('/{domain:some}/path/to{/optionalPath}', {}, true); | ||
uri = new URI(uri, {domain: 'foo'}); | ||
deepEqual(uri.toString(), '/foo/path/to'); | ||
}); | ||
it('{/patterns} bound', function() { | ||
var uri = new URI('/{domain:some}/path/to{/optionalPath}', {}, true); | ||
uri.params = {optionalPath: 'foo'}; | ||
deepEqual(uri.toString(), '/some/path/to/foo'); | ||
}); | ||
it('{+patterns} empty', function() { | ||
var uri = new URI('/{domain:some}/path/to/{+rest}', {}, true); | ||
deepEqual(uri.toString(), '/some/path/to/'); | ||
}); | ||
it('{+patterns} bound', function() { | ||
var uri = new URI('/{domain:some}/path/to/{+rest}', | ||
{rest: 'foo'}, true); | ||
deepEqual(uri.toString(), '/some/path/to/foo'); | ||
}); | ||
it('decoding / encoding', function() { | ||
var uri = new URI('/{domain:some}/a%2Fb/to/100%/%FF', {domain: 'foo/bar'}, true); | ||
// Note how the invalid % encoding is fixed up to %25 | ||
deepEqual(uri.toString(), '/foo%2Fbar/a%2Fb/to/100%25/%25FF'); | ||
}); | ||
it('construct from array', function() { | ||
var uri = new URI([{ | ||
name: 'domain', | ||
pattern: 'some' | ||
},'a/b', 'to', '100%'], {domain: 'foo/bar'}, true); | ||
// Note how the invalid % encoding is fixed up to %25 | ||
deepEqual(uri.toString(), '/foo%2Fbar/a%2Fb/to/100%25'); | ||
// Try once more for caching | ||
deepEqual(uri.toString(), '/foo%2Fbar/a%2Fb/to/100%25'); | ||
}); | ||
it('append a suffix path', function() { | ||
var baseURI = new URI('/{domain:test.com}/v1', {}, true); | ||
var suffix = new URI('/page/{title}', {}, true); | ||
var uri = new URI(baseURI.path.concat(suffix.path), {title: 'foo'}); | ||
deepEqual(uri.toString(), '/test.com/v1/page/foo', {}, true); | ||
deepEqual(uri.expand().path, ['test.com', 'v1', 'page', 'foo']); | ||
}); | ||
it('remove a suffix path', function() { | ||
var basePath = new URI('/{domain:test.com}/v1/page/{title}', {}, true).path; | ||
var uri = new URI(basePath.slice(0, basePath.length - 2)); | ||
deepEqual(uri.toString(), '/test.com/v1'); | ||
}); | ||
it('should serialize with "simplePattern" and "fullPattern" formats', function() { | ||
var uri = new URI('/{domain:test.com}/v1/{title}{/foo}{+bar}', {}, true); | ||
deepEqual(uri.toString(), '/test.com/v1'); | ||
deepEqual(uri.toString('simplePattern'), '/test.com/v1/{title}{/foo}{+bar}'); | ||
deepEqual(uri.toString('fullPattern'), '/{domain:test.com}/v1/{title}{/foo}{+bar}'); | ||
}); | ||
it('check for a prefix path', function() { | ||
var uri = new URI('/{domain:test.com}/v1/page/{title}', {}, true); | ||
deepEqual(uri.startsWith('/test.com/v1/page'), true); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
32885
9
859
71
3
5
1
+ Addedbluebird@^2.7.1
+ Addedjs-yaml@~3.2.2
+ Addedargparse@1.0.10(transitive)
+ Addedbluebird@2.11.0(transitive)
+ Addedesprima@2.0.0(transitive)
+ Addedjs-yaml@3.2.7(transitive)
+ Addedsprintf-js@1.0.3(transitive)