Comparing version 1.0.0 to 2.0.0
313
lib/index.js
@@ -6,3 +6,3 @@ // Load modules | ||
var Regex = require('./regex'); | ||
var Sort = require('./sort'); | ||
var Router = require('./router'); | ||
@@ -24,4 +24,5 @@ | ||
this.routes = {}; // Key: HTTP method or * for catch-all, value: sorted array of routes | ||
this.vhosts = null; // {} where Key: hostname, value: see this.routes | ||
this.routes = {}; // Key: HTTP method or * for catch-all, value: sorted array of routes | ||
this.ids = {}; // Key: route id, value: record | ||
this.vhosts = null; // {} where Key: hostname, value: see this.routes | ||
@@ -49,3 +50,3 @@ this.specials = { | ||
var table = (vhost === '*' ? self.routes : self.vhosts[vhost]); | ||
table[method] = table[method] || []; | ||
table[method] = table[method] || { routes: [], router: new Router() }; | ||
@@ -58,21 +59,22 @@ var analysis = config.analysis || this.analyze(config.path); | ||
params: analysis.params, | ||
fingerprint: analysis.fingerprint | ||
fingerprint: analysis.fingerprint, | ||
settings: this.settings | ||
}; | ||
// Check for existing route with same fingerprint | ||
// Add route | ||
var altFingerprint = (record.segments[record.segments.length - 1].isEmptyOk ? record.fingerprint.substring(0, record.fingerprint.length - 2) : ''); | ||
table[method].forEach(function (existing) { | ||
table[method].router.add(analysis.segments, record); | ||
table[method].routes.push(record); | ||
table[method].routes.sort(internals.sort); | ||
Hoek.assert(record.fingerprint !== existing.fingerprint, 'New route: ' + config.path + ' conflicts with existing: ' + existing.path); | ||
Hoek.assert(altFingerprint !== existing.fingerprint, 'New route: ' + config.path + ' conflicts with existing: ' + existing.path); | ||
var last = record.segments[record.segments.length - 1]; | ||
if (last.empty) { | ||
table[method].router.add(analysis.segments.slice(0, -1), record); | ||
} | ||
var altExistingFingerprint = (existing.segments[existing.segments.length - 1].isEmptyOk ? existing.fingerprint.substring(0, existing.fingerprint.length - 2) : ''); | ||
Hoek.assert(record.fingerprint !== altExistingFingerprint, 'New route: ' + config.path + ' conflicts with existing: ' + existing.path); | ||
}); | ||
if (config.id) { | ||
Hoek.assert(!this.ids[config.id], 'Route id', config.id, 'for path', config.path, 'conflicts with existing path', this.ids[config.id] && this.ids[config.id].path); | ||
this.ids[config.id] = record; | ||
} | ||
// Add and sort | ||
table[method].push(record); | ||
table[method].sort(Sort.sort); | ||
return record; | ||
@@ -92,12 +94,10 @@ }; | ||
var pathSegments = path.split('/'); | ||
var vhost = (this.vhosts && hostname && this.vhosts[hostname]); | ||
var route = (vhost && this._lookup(path, vhost, method, pathSegments)) || | ||
this._lookup(path, this.routes, method, pathSegments) || | ||
(method === 'head' && vhost && this._lookup(path, vhost, 'get', pathSegments)) || | ||
(method === 'head' && this._lookup(path, this.routes, 'get', pathSegments)) || | ||
var route = (vhost && this._lookup(path, vhost, method)) || | ||
this._lookup(path, this.routes, method) || | ||
(method === 'head' && vhost && this._lookup(path, vhost, 'get')) || | ||
(method === 'head' && this._lookup(path, this.routes, 'get')) || | ||
(method === 'options' && this.specials.options) || | ||
(vhost && this._lookup(path, vhost, '*', pathSegments)) || | ||
this._lookup(path, this.routes, '*', pathSegments) || | ||
(vhost && this._lookup(path, vhost, '*')) || | ||
this._lookup(path, this.routes, '*') || | ||
this.specials.notFound || Boom.notFound(); | ||
@@ -109,109 +109,40 @@ | ||
internals.Router.prototype._lookup = function (path, table, method, pathSegments) { | ||
internals.Router.prototype._lookup = function (path, table, method) { | ||
var match = false; | ||
var routes = table[method]; | ||
if (routes) { | ||
for (var i = 0, il = routes.length; !match && i < il; ++i) { | ||
var record = routes[i]; | ||
match = this._match(record, path, pathSegments); // Returns Error, false, or result object | ||
if (match) { | ||
if (match.isBoom) { | ||
match = this.specials.badRequest || match; | ||
} | ||
else { | ||
match.route = record.route; | ||
} | ||
} | ||
} | ||
var set = table[method]; | ||
if (!set) { | ||
return null; | ||
} | ||
return match; | ||
}; | ||
internals.Router.prototype._match = function (record, path, pathSegments) { | ||
var result = { | ||
params: {}, | ||
paramsArray: [] | ||
}; | ||
// Literal comparison | ||
if (!record.params.length) { | ||
return (record.path === (this.settings.isCaseSensitive ? path : path.toLowerCase()) ? result : false); | ||
var match = set.router.match(path, this.settings); // Returns Error, null, or result object | ||
if (!match) { | ||
return null; | ||
} | ||
// Mismatching segment count | ||
var pl = pathSegments.length - 1; | ||
var sl = record.segments.length; | ||
var last = record.segments[sl - 1]; | ||
if (pl !== sl && // Different count | ||
(pl !== sl - 1 || (!last.isEmptyOk && !last.isWildcard)) && // Not short one with empty or wildcard allowed | ||
(pl < sl || !last.isWildcard)) { | ||
return false; | ||
if (match.isBoom) { | ||
return this.specials.badRequest || match; | ||
} | ||
// Parameter matching | ||
var match = true; | ||
for (var i = 0; match && (match instanceof Error === false) && i < record.segments.length; ++i) { | ||
var segment = record.segments[i]; | ||
if (segment.isWildcard) { | ||
match = internals.setParam(segment.name, pathSegments.slice(i + 1).join('/'), result, true); | ||
} | ||
else if (segment.count) { | ||
match = internals.setParam(segment.name, pathSegments.slice(i + 1, i + 1 + segment.count).join('/'), result, false); | ||
i += (segment.count - 1); | ||
} | ||
else if (segment.name) { | ||
if (segment.extract) { | ||
var partial = pathSegments[i + 1].match(segment.extract); | ||
if (!partial) { | ||
match = false; | ||
} | ||
else { | ||
match = internals.setParam(segment.name, partial[1], result, segment.isEmptyOk); | ||
} | ||
var assignments = {}; | ||
var array = []; | ||
for (var i = 0, il = match.array.length; i < il; ++i) { | ||
var name = match.record.params[i]; | ||
var value = match.array[i]; | ||
if (value !== undefined) { | ||
if (assignments[name] !== undefined) { | ||
assignments[name] += '/' + value; | ||
} | ||
else { | ||
match = internals.setParam(segment.name, pathSegments[i + 1], result, segment.isEmptyOk); | ||
assignments[name] = value; | ||
} | ||
} | ||
else { | ||
match = (segment.literal === (this.settings.isCaseSensitive ? pathSegments[i + 1] : pathSegments[i + 1].toLowerCase())); | ||
} | ||
} | ||
if (match !== true) { // Can be Error | ||
return match; | ||
} | ||
if (i + 1 === il || | ||
name !== match.record.params[i + 1]) { | ||
return result; | ||
}; | ||
internals.setParam = function (name, value, result, isEmptyOk) { | ||
if (!isEmptyOk && !value) { | ||
return false; | ||
array.push(assignments[name]); | ||
} | ||
} | ||
} | ||
if (isEmptyOk && !value) { | ||
return true; | ||
} | ||
try { | ||
var decoded = decodeURIComponent(value); | ||
result.params[name] = decoded; | ||
result.paramsArray.push(decoded); | ||
return true; | ||
} | ||
catch (err) { | ||
return Boom.badRequest('Invalid request path'); | ||
} | ||
return { params: assignments, paramsArray: array, route: match.record.route }; | ||
}; | ||
@@ -256,3 +187,3 @@ | ||
var segments = []; | ||
var params = {}; | ||
var params = []; | ||
var fingers = []; | ||
@@ -262,22 +193,48 @@ | ||
var segment = pathParts[i]; | ||
var param = segment.match(internals.pathRegex.parseParam); | ||
if (param) { | ||
// Parameter | ||
// Literal | ||
var pre = param[1]; | ||
var name = param[2]; | ||
var isMulti = !!param[3]; | ||
var multiCount = param[4] && parseInt(param[4], 10); | ||
var isEmptyOk = !!param[5]; | ||
var post = param[6]; | ||
if (segment.indexOf('{') === -1) { | ||
segment = this.settings.isCaseSensitive ? segment : segment.toLowerCase(); | ||
fingers.push(segment); | ||
segments.push({ literal: segment }); | ||
continue; | ||
} | ||
Hoek.assert(!params[name], 'Cannot repeat the same parameter name:', name, 'in:', path); | ||
params[name] = true; | ||
// Parameter | ||
if (isMulti) { | ||
if (multiCount) { | ||
for (var m = 0; m < multiCount; ++m) { | ||
var parts = []; | ||
segment.replace(internals.pathRegex.parseParam, function (match, literal, name, wilcard, count, empty) { | ||
if (literal) { | ||
parts.push(literal); | ||
} | ||
else { | ||
parts.push({ | ||
name: name, | ||
wilcard: !!wilcard, | ||
count: count && parseInt(count, 10), | ||
empty: !!empty | ||
}); | ||
} | ||
return ''; | ||
}); | ||
if (parts.length === 1) { | ||
// Simple parameter | ||
var item = parts[0]; | ||
Hoek.assert(params.indexOf(item.name) === -1, 'Cannot repeat the same parameter name:', item.name, 'in:', path); | ||
params.push(item.name); | ||
if (item.wilcard) { | ||
if (item.count) { | ||
for (var m = 0; m < item.count; ++m) { | ||
fingers.push('?'); | ||
segments.push({ name: name, count: multiCount }); | ||
segments.push({ param: true }); | ||
if (m) { | ||
params.push(item.name); | ||
} | ||
} | ||
@@ -287,29 +244,42 @@ } | ||
fingers.push('#'); | ||
segments.push({ isWildcard: true, name: name }); | ||
segments.push({ param: true, wildcard: true }); | ||
} | ||
} | ||
else { | ||
fingers.push(pre + '?' + post); | ||
var segmentMeta = { | ||
name: name, | ||
isEmptyOk: isEmptyOk | ||
}; | ||
fingers.push('?'); | ||
segments.push({ param: true, empty: item.empty }); | ||
} | ||
} | ||
else { | ||
if (pre || post) { | ||
segmentMeta.mixed = true; | ||
segmentMeta.pre = pre; | ||
segmentMeta.post = post; | ||
segmentMeta.extract = new RegExp('^' + Hoek.escapeRegex(pre) + '(.' + (isEmptyOk ? '*' : '+') + ')' + Hoek.escapeRegex(post) + '$', (!this.settings.isCaseSensitive ? 'i' : '')); | ||
// Mixed parameter | ||
var seg = { | ||
param: true, | ||
length: parts.length, | ||
first: typeof parts[0] !== 'string', | ||
segments: [], | ||
}; | ||
var finger = ''; | ||
var regex = '^'; | ||
for (var p = 0, pl = parts.length; p < pl; ++p) { | ||
var part = parts[p]; | ||
if (typeof part === 'string') { | ||
finger += part; | ||
regex += Hoek.escapeRegex(part); | ||
seg.segments.push(part); | ||
} | ||
else { | ||
Hoek.assert(params.indexOf(part.name) === -1, 'Cannot repeat the same parameter name:', part.name, 'in:', path); | ||
params.push(part.name); | ||
segments.push(segmentMeta); | ||
finger += '?'; | ||
regex += '(.' + (part.empty ? '*' : '+') + ')'; | ||
} | ||
} | ||
} | ||
else { | ||
// Literal | ||
segment = this.settings.isCaseSensitive ? segment : segment.toLowerCase(); | ||
fingers.push(segment); | ||
segments.push({ literal: segment }); | ||
seg.mixed = new RegExp(regex + '$', (!this.settings.isCaseSensitive ? 'i' : '')), | ||
fingers.push(finger); | ||
segments.push(seg); | ||
} | ||
@@ -319,5 +289,6 @@ } | ||
return { | ||
path: path, | ||
segments: segments, | ||
fingerprint: '/' + fingers.join('/'), | ||
params: Object.keys(params) | ||
params: params | ||
} | ||
@@ -338,3 +309,3 @@ }; | ||
table[method].forEach(function (record) { | ||
table[method].routes.forEach(function (record) { | ||
@@ -357,1 +328,33 @@ result.push(record.route); | ||
}; | ||
internals.sort = function (a, b) { | ||
var aFirst = -1; | ||
var bFirst = 1; | ||
var as = a.segments; | ||
var bs = b.segments; | ||
if (as.length !== bs.length) { | ||
return (as.length > bs.length ? bFirst : aFirst); | ||
} | ||
for (var i = 0, il = as.length; ; ++i) { | ||
if (as[i].literal) { | ||
if (bs[i].literal) { | ||
if (as[i].literal === bs[i].literal) { | ||
continue; | ||
} | ||
return (as[i].literal > bs[i].literal ? bFirst : aFirst); | ||
} | ||
return aFirst; | ||
} | ||
else if (bs[i].literal) { | ||
return bFirst; | ||
} | ||
return (as[i].wildcard ? bFirst : aFirst); | ||
} | ||
}; |
@@ -19,3 +19,3 @@ // Load modules | ||
var empty = '(^\\/$)'; | ||
var empty = '(?:^\\/$)'; | ||
@@ -29,18 +29,19 @@ var legalChars = '[\\w\\!\\$&\'\\(\\)\\*\\+\\,;\\=\\:@\\-\\.~]'; | ||
var midParam = '(\\{\\w+(\\*[1-9]\\d*)?\\})'; // {p}, {p*2} | ||
var endParam = '(\\/(\\{\\w+((\\*([1-9]\\d*)?)|(\\?))?\\})?)?'; // {p}, {p*2}, {p*}, {p?} | ||
var mixParam = '(\\{\\w+\\??\\})'; // {p}, {p?} | ||
var midParam = '(?:\\{\\w+(?:\\*[1-9]\\d*)?\\})'; // {p}, {p*2} | ||
var endParam = '(?:\\/(?:\\{\\w+(?:(?:\\*(?:[1-9]\\d*)?)|(?:\\?))?\\})?)?'; // {p}, {p*2}, {p*}, {p?} | ||
var literalParam = '(' + literal + mixParam + literalOptional + ')|(' + literalOptional + mixParam + literal + ')'; | ||
var partialParam = '(?:\\{\\w+\\??\\})'; // {p}, {p?} | ||
var mixedParam = '(?:(?:' + literal + partialParam + ')+' + literalOptional + ')|(?:' + partialParam + '(?:' + literal + partialParam + ')+' + literalOptional + ')|(?:' + partialParam + literal + ')'; | ||
var segmentContent = '(' + literal + '|' + midParam + '|' + literalParam + ')'; | ||
var segmentContent = '(?:' + literal + '|' + midParam + '|' + mixedParam + ')'; | ||
var segment = '\\/' + segmentContent; | ||
var segments = '(' + segment + ')*'; | ||
var segments = '(?:' + segment + ')*'; | ||
var path = '(^' + segments + endParam + '$)'; | ||
var path = '(?:^' + segments + endParam + '$)'; | ||
var parseParam = '^(' + literalOptional + ')' + '\\{(\\w+)(?:(\\*)(\\d+)?)?(\\?)?\\}' + '(' + literalOptional + ')$'; | ||
// 1:literal 2:name 3:* 4:count 5:? | ||
var parseParam = '(' + literal + ')|(?:\\{(\\w+)(?:(\\*)(\\d+)?)?(\\?)?\\})'; | ||
var expressions = { | ||
parseParam: new RegExp(parseParam), // $1: literal-pre, $2: name, $3: *, $4: segments, $5: empty-ok, $6: literal-post | ||
parseParam: new RegExp(parseParam, 'g'), | ||
validatePath: new RegExp(empty + '|' + path), | ||
@@ -47,0 +48,0 @@ validatePathEncoded: /%(?:2[146-9A-E]|3[\dABD]|4[\dA-F]|5[\dAF]|6[1-9A-F]|7[\dAE])/g |
{ | ||
"name": "call", | ||
"description": "HTTP Router", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"repository": "git://github.com/hapijs/call", | ||
@@ -12,3 +12,3 @@ "main": "index", | ||
"engines": { | ||
"node": ">=0.10.30" | ||
"node": ">=0.10.32" | ||
}, | ||
@@ -20,3 +20,4 @@ "dependencies": { | ||
"devDependencies": { | ||
"lab": "4.x.x" | ||
"code": "1.x.x", | ||
"lab": "5.x.x" | ||
}, | ||
@@ -23,0 +24,0 @@ "scripts": { |
@@ -5,2 +5,3 @@ // Load modules | ||
var Call = require('../'); | ||
var Code = require('code'); | ||
@@ -18,3 +19,3 @@ | ||
var it = lab.it; | ||
var expect = Lab.expect; | ||
var expect = Code.expect; | ||
@@ -29,7 +30,7 @@ | ||
router.add({ method: 'get', path: '/a' }, '/a'); | ||
router.add({ method: 'get', path: '/b' }, '/b'); | ||
router.add({ method: 'get', path: '/a{b?}c{d}' }, '/a{b?}c{d}'); | ||
expect(router.route('get', '/').route).to.equal('/'); | ||
expect(router.route('get', '/a').route).to.equal('/a'); | ||
expect(router.route('get', '/b').route).to.equal('/b'); | ||
expect(router.route('get', '/abcd').route).to.equal('/a{b?}c{d}'); | ||
@@ -53,3 +54,3 @@ done(); | ||
it('matches routes in right order', function (done) { | ||
describe('sort', function () { | ||
@@ -90,5 +91,13 @@ var paths = [ | ||
'/{a}/b/{p*}', | ||
'/{p*}' | ||
'/{p*}', | ||
'/m/n/{p*}', | ||
'/m/{n}/{o}', | ||
'/n/{p}/{o*}' | ||
]; | ||
var router = new Call.Router(); | ||
for (var i = 0, il = paths.length; i < il; ++i) { | ||
router.add({ method: 'get', path: paths[i] }, paths[i]); | ||
} | ||
var requests = [ | ||
@@ -125,18 +134,24 @@ ['/', '/'], | ||
['/a/c/b/d', '/a/{p}/b/{x}'], | ||
['/a/b/c/d/e', '/{p*5}'], | ||
['/a/b/c/d/e', '/a/b/{p*}'], | ||
['/a/b/c/d/e/f', '/a/b/{p*}'], | ||
['/x/b/c/d/e/f/g', '/{a}/b/{p*}'], | ||
['/x/y/c/d/e/f/g', '/{p*}'] | ||
['/x/y/c/d/e/f/g', '/{p*}'], | ||
['/m/n/o', '/m/n/{p*}'], | ||
['/m/o/p', '/m/{n}/{o}'], | ||
['/n/a/b/c', '/n/{p}/{o*}'], | ||
['/n/a', '/n/{p}/{o*}'] | ||
]; | ||
var router = new Call.Router(); | ||
for (var i = 0, il = paths.length; i < il; ++i) { | ||
router.add({ method: 'get', path: paths[i] }, paths[i]); | ||
} | ||
var test = function (path, route) { | ||
it('matches \'' + path + '\' to \'' + route + '\'', function (done) { | ||
expect(router.route('get', path).route).to.equal(route); | ||
done(); | ||
}); | ||
}; | ||
for (i = 0, il = requests.length; i < il; ++i) { | ||
expect(router.route('get', requests[i][0]).route).to.equal(requests[i][1]); | ||
test(requests[i][0], requests[i][1]); | ||
} | ||
done(); | ||
}); | ||
@@ -146,2 +161,10 @@ | ||
it('adds a route with id', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/b/{c}', id: 'a' }); | ||
expect(router.ids.a.path).to.equal('/a/b/{c}'); | ||
done(); | ||
}); | ||
it('throws on duplicate route', function (done) { | ||
@@ -154,3 +177,3 @@ | ||
router.add({ method: 'get', path: '/a/b/{c}' }); | ||
}); | ||
}).to.throw('New route /a/b/{c} conflicts with existing /a/b/{c}'); | ||
@@ -160,2 +183,14 @@ done(); | ||
it('throws on duplicate route (id)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/b', id: '1' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/b', id: '1' }); | ||
}).to.throw('Route id 1 for path /b conflicts with existing path /a/b'); | ||
done(); | ||
}); | ||
it('throws on duplicate route (optional param in first)', function (done) { | ||
@@ -168,3 +203,3 @@ | ||
router.add({ method: 'get', path: '/a/b' }); | ||
}).to.throw('New route: /a/b conflicts with existing: /a/b/{c?}'); | ||
}).to.throw('New route /a/b conflicts with existing /a/b/{c?}'); | ||
@@ -181,6 +216,148 @@ done(); | ||
router.add({ method: 'get', path: '/a/b/{c?}' }); | ||
}).to.throw('New route: /a/b/{c?} conflicts with existing: /a/b'); | ||
}).to.throw('New route /a/b/{c?} conflicts with existing /a/b'); | ||
done(); | ||
}); | ||
it('throws on duplicate route (same fingerprint)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/test/{p1}/{p2}/end' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/test/{p*2}/end' }); | ||
}).to.throw('New route /test/{p*2}/end conflicts with existing /test/{p1}/{p2}/end'); | ||
done(); | ||
}); | ||
it('throws on duplicate route (case insensitive)', function (done) { | ||
var router = new Call.Router({ isCaseSensitive: false }); | ||
router.add({ method: 'get', path: '/test/a' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/test/A' }); | ||
}).to.throw('New route /test/A conflicts with existing /test/a'); | ||
done(); | ||
}); | ||
it('throws on duplicate route (wildcards)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/b/{c*}' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/a/b/{c*}' }); | ||
}).to.throw('New route /a/b/{c*} conflicts with existing /a/b/{c*}'); | ||
done(); | ||
}); | ||
it('throws on duplicate route (mixed)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/b/a{c}' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/a/b/a{c}' }); | ||
}).to.throw('New route /a/b/a{c} conflicts with existing /a/b/a{c}'); | ||
done(); | ||
}); | ||
it('throws on duplicate route (/a/{p}/{q*}, /a/{p*})', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/{p}/{q*}' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/a/{p*}' }); | ||
}).to.throw('New route /a/{p*} conflicts with existing /a/{p}/{q*}'); | ||
done(); | ||
}); | ||
it('throws on duplicate route (/a/{p*}, /a/{p}/{q*})', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/{p*}' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/a/{p}/{q*}' }); | ||
}).to.throw('New route /a/{p}/{q*} conflicts with existing /a/{p*}'); | ||
done(); | ||
}); | ||
it('allows route to differ in just case', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/test/a' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/test/A' }); | ||
}).to.not.throw(); | ||
done(); | ||
}); | ||
it('throws on duplicate route (different param name)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/test/{p}' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/test/{P}' }); | ||
}).to.throw('New route /test/{P} conflicts with existing /test/{p}'); | ||
done(); | ||
}); | ||
it('throws on duplicate parameter name', function (done) { | ||
var router = new Call.Router(); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/test/{p}/{p}' }); | ||
}).to.throw('Cannot repeat the same parameter name: p in: /test/{p}/{p}'); | ||
done(); | ||
}); | ||
it('throws on invalid path', function (done) { | ||
var router = new Call.Router(); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/%/%' }); | ||
}).to.throw('Invalid path: /%/%'); | ||
done(); | ||
}); | ||
it('throws on duplicate route (same vhost)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/b/{c}', vhost: 'example.com' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/a/b/{c}', vhost: 'example.com' }); | ||
}).to.throw('New route /a/b/{c} conflicts with existing /a/b/{c}'); | ||
done(); | ||
}); | ||
it('allows duplicate route (different vhost)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/b/{c}', vhost: 'one.example.com' }); | ||
expect(function () { | ||
router.add({ method: 'get', path: '/a/b/{c}', vhost: 'two.example.com' }); | ||
}).to.not.throw(); | ||
done(); | ||
}); | ||
}); | ||
@@ -237,6 +414,6 @@ | ||
}, | ||
'/path/{param*}': { | ||
'/path/{x*}': { | ||
'/a/b/c/d': false, | ||
'/path/a/b/to': { | ||
param: 'a/b/to' | ||
x: 'a/b/to' | ||
}, | ||
@@ -273,2 +450,10 @@ '/path/': {}, | ||
}, | ||
'/mixedCase/|false': { | ||
'/mixedcase/': true, | ||
'/mixedCase/': true | ||
}, | ||
'/mixedCase/|true': { | ||
'/mixedcase/': false, | ||
'/mixedCase/': true | ||
}, | ||
'/{p*}': { | ||
@@ -307,2 +492,62 @@ '/path/': { | ||
'/a/b/': false | ||
}, | ||
'/a/{b}/c|false': { | ||
'/a/1/c': { | ||
b: '1' | ||
}, | ||
'/A/1/c': { | ||
b: '1' | ||
} | ||
}, | ||
'/a/{B}/c|false': { | ||
'/a/1/c': { | ||
B: '1' | ||
}, | ||
'/A/1/c': { | ||
B: '1' | ||
} | ||
}, | ||
'/a/{b}/c|true': { | ||
'/a/1/c': { | ||
b: '1' | ||
}, | ||
'/A/1/c': false | ||
}, | ||
'/a/{B}/c|true': { | ||
'/a/1/c': { | ||
B: '1' | ||
}, | ||
'/A/1/c': false | ||
}, | ||
'/aB/{p}|true': { | ||
'/aB/4': { | ||
p: '4' | ||
}, | ||
'/ab/4': false | ||
}, | ||
'/aB/{p}|false': { | ||
'/aB/4': { | ||
p: '4' | ||
}, | ||
'/ab/4': { | ||
p: '4' | ||
} | ||
}, | ||
'/{a}b{c?}d{e}|true': { | ||
'/abcde': { | ||
a: 'a', | ||
c: 'c', | ||
e: 'e' | ||
}, | ||
'/abde': { | ||
a: 'a', | ||
e: 'e' | ||
}, | ||
'/abxyzde': { | ||
a: 'a', | ||
c: 'xyz', | ||
e: 'e' | ||
}, | ||
'/aBcde': false, | ||
'/bcde': false | ||
} | ||
@@ -418,2 +663,26 @@ }; | ||
}); | ||
it('fails to match bad request (mixed)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a{p}' }); | ||
expect(router.route('get', '/a%p').output.statusCode).to.equal(400); | ||
done(); | ||
}); | ||
it('fails to match bad request (wildcard)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/{p*}' }); | ||
expect(router.route('get', '/%p').output.statusCode).to.equal(400); | ||
done(); | ||
}); | ||
it('fails to match bad request (deep)', function (done) { | ||
var router = new Call.Router(); | ||
router.add({ method: 'get', path: '/a/{p}' }); | ||
expect(router.route('get', '/a/%p').output.statusCode).to.equal(400); | ||
done(); | ||
}); | ||
}); | ||
@@ -557,2 +826,2 @@ | ||
}); | ||
}); | ||
}); |
// Load modules | ||
var Code = require('code'); | ||
var Lab = require('lab'); | ||
@@ -17,3 +18,3 @@ var Regex = require('../lib/regex'); | ||
var it = lab.it; | ||
var expect = Lab.expect; | ||
var expect = Code.expect; | ||
@@ -74,4 +75,9 @@ | ||
'/d/a{p}b/e': true, | ||
'/a{p}.{x}': false, | ||
'/a{p}.{x}': true, | ||
'/{p}{x}': false, | ||
'/a{p}{x}': false, | ||
'/a{p}{x}b': false, | ||
'/{p}{x}b': false, | ||
'/{p?}{x}b': false, | ||
'/{a}b{c?}d{e}': true, | ||
'/a{p?}': true, | ||
@@ -78,0 +84,0 @@ '/{p*}d': false, |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
55102
1249
2
13