find-my-way
Advanced tools
Comparing version 5.6.0 to 6.0.0
'use strict' | ||
const assert = require('assert') | ||
const deepEqual = require('fast-deep-equal') | ||
@@ -8,38 +7,48 @@ | ||
constructor () { | ||
this.unconstrainedHandler = null // optimized reference to the handler that will match most of the time | ||
this.constraints = [] | ||
this.handlers = [] // unoptimized list of handler objects for which the fast matcher function will be compiled | ||
this.hasConstraints = false | ||
this.compiledHandler = null | ||
this.unconstrainedHandler = null // optimized reference to the handler that will match most of the time | ||
this.constrainedHandlerStores = null | ||
} | ||
getHandler (constraints) { | ||
return this.handlers.filter(handler => deepEqual(constraints, handler.constraints))[0] | ||
hasHandler (constraints) { | ||
return this.handlers.find(handler => deepEqual(constraints, handler.constraints)) !== undefined | ||
} | ||
// This is the hot path for node handler finding -- change with care! | ||
getMatchingHandler (constrainer, derivedConstraints) { | ||
getMatchingHandler (derivedConstraints) { | ||
if (derivedConstraints === undefined) { | ||
return this.unconstrainedHandler | ||
} | ||
return this._getHandlerMatchingConstraints(derivedConstraints) | ||
} | ||
if (this.hasConstraints) { | ||
// This node is constrained, use the performant precompiled constraint matcher | ||
return this._getHandlerMatchingConstraints(constrainer, derivedConstraints) | ||
addHandler (handler, params, store, constrainer, constraints) { | ||
const handlerObject = { | ||
handler, | ||
params, | ||
constraints, | ||
store: store || null, | ||
_createParamsObject: this._compileCreateParamsObject(params) | ||
} | ||
// This node doesn't have any handlers that are constrained, so it's handlers probably match. Some requests have constraint values that *must* match however, like version, so check for those before returning it. | ||
if (!derivedConstraints.__hasMustMatchValues) { | ||
return this.unconstrainedHandler | ||
if (Object.keys(constraints).length === 0) { | ||
this.unconstrainedHandler = handlerObject | ||
} | ||
return null | ||
} | ||
for (const constraint of Object.keys(constraints)) { | ||
if (!this.constraints.includes(constraint)) { | ||
if (constraint === 'version') { | ||
// always check the version constraint first as it is the most selective | ||
this.constraints.unshift(constraint) | ||
} else { | ||
this.constraints.push(constraint) | ||
} | ||
} | ||
} | ||
addHandler (handler, params, store, constraints) { | ||
if (!handler) return | ||
assert(!this.getHandler(constraints), `There is already a handler with constraints '${JSON.stringify(constraints)}' and method '${this.method}'`) | ||
if (this.handlers.length >= 32) { | ||
throw new Error('find-my-way supports a maximum of 32 route handlers per node when there are constraints, limit reached') | ||
} | ||
const handlerObject = { handler, params, constraints, store: store || null, paramsLength: params.length } | ||
this.handlers.push(handlerObject) | ||
@@ -49,23 +58,15 @@ // Sort the most constrained handlers to the front of the list of handlers so they are tested first. | ||
if (Object.keys(constraints).length > 0) { | ||
this.hasConstraints = true | ||
} else { | ||
this.unconstrainedHandler = handlerObject | ||
} | ||
this._compileGetHandlerMatchingConstraints(constrainer, constraints) | ||
} | ||
if (this.hasConstraints && this.handlers.length > 32) { | ||
throw new Error('find-my-way supports a maximum of 32 route handlers per node when there are constraints, limit reached') | ||
_compileCreateParamsObject (params) { | ||
const lines = [] | ||
for (let i = 0; i < params.length; i++) { | ||
lines.push(`'${params[i]}': paramsArray[${i}]`) | ||
} | ||
// Note that the fancy constraint handler matcher needs to be recompiled now that the list of handlers has changed | ||
// This lazy compilation means we don't do the compile until the first time the route match is tried, which doesn't waste time re-compiling every time a new handler is added | ||
this.compiledHandler = null | ||
return new Function('paramsArray', `return {${lines.join(',')}}`) // eslint-disable-line | ||
} | ||
// Slot for the compiled constraint matching function | ||
_getHandlerMatchingConstraints (constrainer, derivedConstraints) { | ||
if (this.compiledHandler === null) { | ||
this.compiledHandler = this._compileGetHandlerMatchingConstraints(constrainer) | ||
} | ||
return this.compiledHandler(derivedConstraints) | ||
_getHandlerMatchingConstraints () { | ||
return null | ||
} | ||
@@ -76,19 +77,12 @@ | ||
// The store's implementation comes from the strategies provided to the Router. | ||
_buildConstraintStore (constrainer, constraint) { | ||
const store = constrainer.newStoreForConstraint(constraint) | ||
_buildConstraintStore (store, constraint) { | ||
for (let i = 0; i < this.handlers.length; i++) { | ||
const handler = this.handlers[i] | ||
const mustMatchValue = handler.constraints[constraint] | ||
if (typeof mustMatchValue !== 'undefined') { | ||
let indexes = store.get(mustMatchValue) | ||
if (!indexes) { | ||
indexes = 0 | ||
} | ||
const constraintValue = handler.constraints[constraint] | ||
if (constraintValue !== undefined) { | ||
let indexes = store.get(constraintValue) || 0 | ||
indexes |= 1 << i // set the i-th bit for the mask because this handler is constrained by this value https://stackoverflow.com/questions/1436438/how-do-you-set-clear-and-toggle-a-single-bit-in-javascrip | ||
store.set(mustMatchValue, indexes) | ||
store.set(constraintValue, indexes) | ||
} | ||
} | ||
return store | ||
} | ||
@@ -98,6 +92,7 @@ | ||
_constrainedIndexBitmask (constraint) { | ||
let mask = 0b0 | ||
let mask = 0 | ||
for (let i = 0; i < this.handlers.length; i++) { | ||
const handler = this.handlers[i] | ||
if (handler.constraints && constraint in handler.constraints) { | ||
const constraintValue = handler.constraints[constraint] | ||
if (constraintValue !== undefined) { | ||
mask |= 1 << i | ||
@@ -117,23 +112,16 @@ } | ||
this.constrainedHandlerStores = {} | ||
let constraints = new Set() | ||
for (const handler of this.handlers) { | ||
for (const key of Object.keys(handler.constraints)) { | ||
constraints.add(key) | ||
} | ||
} | ||
constraints = Array.from(constraints) | ||
const lines = [] | ||
// always check the version constraint first as it is the most selective | ||
constraints.sort((a, b) => a === 'version' ? 1 : 0) | ||
for (const constraint of this.constraints) { | ||
const store = constrainer.newStoreForConstraint(constraint) | ||
this.constrainedHandlerStores[constraint] = store | ||
for (const constraint of constraints) { | ||
this.constrainedHandlerStores[constraint] = this._buildConstraintStore(constrainer, constraint) | ||
this._buildConstraintStore(store, constraint) | ||
} | ||
const lines = [] | ||
lines.push(` | ||
let candidates = 0b${'1'.repeat(this.handlers.length)} | ||
let candidates = ${(1 << this.handlers.length) - 1} | ||
let mask, matches | ||
`) | ||
for (const constraint of constraints) { | ||
for (const constraint of this.constraints) { | ||
// Setup the mask for indexes this constraint applies to. The mask bits are set to 1 for each position if the constraint applies. | ||
@@ -147,8 +135,11 @@ lines.push(` | ||
// If there is a constraint value, get the matching indexes bitmap from the store, and mask it down to only the indexes this constraint applies to, and then bitwise and with the candidates list to leave only matching candidates left. | ||
const strategy = constrainer.strategies[constraint] | ||
const matchMask = strategy.mustMatchWhenDerived ? 'matches' : '(matches | mask)' | ||
lines.push(` | ||
if (typeof value === "undefined") { | ||
if (value === undefined) { | ||
candidates &= mask | ||
} else { | ||
matches = this.constrainedHandlerStores.${constraint}.get(value) || 0 | ||
candidates &= (matches | mask) | ||
candidates &= ${matchMask} | ||
} | ||
@@ -158,12 +149,17 @@ if (candidates === 0) return null; | ||
} | ||
// Return the first handler who's bit is set in the candidates https://stackoverflow.com/questions/18134985/how-to-find-index-of-first-set-bit | ||
lines.push(` | ||
const handler = this.handlers[Math.floor(Math.log2(candidates))] | ||
if (handler && derivedConstraints.__hasMustMatchValues && handler === this.unconstrainedHandler) { | ||
return null; | ||
// There are some constraints that can be derived and marked as "must match", where if they are derived, they only match routes that actually have a constraint on the value, like the SemVer version constraint. | ||
// An example: a request comes in for version 1.x, and this node has a handler that matches the path, but there's no version constraint. For SemVer, the find-my-way semantics do not match this handler to that request. | ||
// This function is used by Nodes with handlers to match when they don't have any constrained routes to exclude request that do have must match derived constraints present. | ||
for (const constraint in constrainer.strategies) { | ||
const strategy = constrainer.strategies[constraint] | ||
if (strategy.mustMatchWhenDerived && !this.constraints.includes(constraint)) { | ||
lines.push(`if (derivedConstraints.${constraint} !== undefined) return null`) | ||
} | ||
} | ||
return handler; | ||
`) | ||
return new Function('derivedConstraints', lines.join('\n')) // eslint-disable-line | ||
// Return the first handler who's bit is set in the candidates https://stackoverflow.com/questions/18134985/how-to-find-index-of-first-set-bit | ||
lines.push('return this.handlers[Math.floor(Math.log2(candidates))]') | ||
this._getHandlerMatchingConstraints = new Function('derivedConstraints', lines.join('\n')) // eslint-disable-line | ||
} | ||
@@ -170,0 +166,0 @@ } |
@@ -57,4 +57,5 @@ import { IncomingMessage, ServerResponse } from 'http'; | ||
params: { [k: string]: string | undefined }, | ||
store: any | ||
) => void; | ||
store: any, | ||
searchParams: { [k: string]: string } | ||
) => any; | ||
@@ -114,2 +115,3 @@ interface ConstraintStrategy<V extends HTTPVersion, T = string> { | ||
store: any; | ||
searchParams: { [k: string]: string }; | ||
} | ||
@@ -142,3 +144,2 @@ | ||
): void; | ||
off( | ||
@@ -154,3 +155,3 @@ method: HTTPMethod | HTTPMethod[], | ||
ctx?: Context | ||
): void; | ||
): any; | ||
@@ -167,2 +168,5 @@ find( | ||
hasConstraintStrategy(strategyName: string): boolean; | ||
addConstraintStrategy(constraintStrategy: ConstraintStrategy<V>): void; | ||
all: ShortHandRoute<V>; | ||
@@ -169,0 +173,0 @@ |
117
index.js
@@ -30,2 +30,3 @@ 'use strict' | ||
const http = require('http') | ||
const querystring = require('querystring') | ||
const isRegexSafe = require('safe-regex2') | ||
@@ -36,3 +37,3 @@ const deepEqual = require('fast-deep-equal') | ||
const Constrainer = require('./lib/constrainer') | ||
const sanitizeUrl = require('./lib/url-sanitizer') | ||
const { safeDecodeURI, safeDecodeURIComponent } = require('./lib/url-sanitizer') | ||
@@ -78,5 +79,7 @@ const httpMethods = http.METHODS | ||
if (opts.decodeUriParameters) { | ||
assert(typeof opts.decodeUriParameters === 'function', 'decodeUriParameters must be a function') | ||
this.decodeUriParameters = opts.decodeUriParameters | ||
if (opts.querystringParser) { | ||
assert(typeof opts.querystringParser === 'function', 'querystringParser must be a function') | ||
this.querystringParser = opts.querystringParser | ||
} else { | ||
this.querystringParser = (query) => query === '' ? {} : querystring.parse(query) | ||
} | ||
@@ -173,2 +176,8 @@ | ||
if (path.charCodeAt(i) === 37) { | ||
// We need to encode % char to prevent double decoding | ||
path = path.slice(0, i + 1) + '25' + path.slice(i + 1) | ||
continue | ||
} | ||
const isParametricNode = path.charCodeAt(i) === 58 | ||
@@ -260,6 +269,15 @@ const isWildcardNode = path.charCodeAt(i) === 42 | ||
assert(!currentNode.handlerStorage.getHandler(constraints), `Method '${method}' already declared for route '${path}' with constraints '${JSON.stringify(constraints)}'`) | ||
currentNode.handlerStorage.addHandler(handler, params, store, constraints) | ||
assert(!currentNode.handlerStorage.hasHandler(constraints), `Method '${method}' already declared for route '${path}' with constraints '${JSON.stringify(constraints)}'`) | ||
currentNode.handlerStorage.addHandler(handler, params, store, this.constrainer, constraints) | ||
} | ||
Router.prototype.hasConstraintStrategy = function (strategyName) { | ||
return this.constrainer.hasConstraintStrategy(strategyName) | ||
} | ||
Router.prototype.addConstraintStrategy = function (constraints) { | ||
this.constrainer.addConstraintStrategy(constraints) | ||
this._rebuild(this.routes) | ||
} | ||
Router.prototype.reset = function reset () { | ||
@@ -312,9 +330,3 @@ this.trees = {} | ||
const newRoutes = this.routes.filter((route) => method !== route.method || path !== route.path || !matcher(route.opts.constraints)) | ||
this.reset() | ||
for (const route of newRoutes) { | ||
const { method, path, opts, handler, store } = route | ||
this._on(method, path, opts, handler, store) | ||
this.routes.push({ method, path, opts, handler, store }) | ||
} | ||
this._rebuild(newRoutes) | ||
} | ||
@@ -326,4 +338,4 @@ | ||
return ctx === undefined | ||
? handle.handler(req, res, handle.params, handle.store) | ||
: handle.handler.call(ctx, req, res, handle.params, handle.store) | ||
? handle.handler(req, res, handle.params, handle.store, handle.searchParams) | ||
: handle.handler.call(ctx, req, res, handle.params, handle.store, handle.searchParams) | ||
} | ||
@@ -340,5 +352,8 @@ | ||
let sanitizedUrl | ||
let querystring | ||
try { | ||
sanitizedUrl = sanitizeUrl(path, this.decodeUriParameters) | ||
sanitizedUrl = safeDecodeURI(path) | ||
path = sanitizedUrl.path | ||
querystring = sanitizedUrl.querystring | ||
} catch (error) { | ||
@@ -352,2 +367,4 @@ return this._onBadUrl(path) | ||
const originPath = path | ||
if (this.caseSensitive === false) { | ||
@@ -367,18 +384,10 @@ path = path.toLowerCase() | ||
if (pathIndex === pathLen) { | ||
const handle = currentNode.handlerStorage.getMatchingHandler(this.constrainer, derivedConstraints) | ||
const handle = currentNode.handlerStorage.getMatchingHandler(derivedConstraints) | ||
if (handle !== null && handle !== undefined) { | ||
const paramsObj = {} | ||
if (handle.paramsLength > 0) { | ||
const paramNames = handle.params | ||
for (let i = 0; i < handle.paramsLength; i++) { | ||
paramsObj[paramNames[i]] = params[i] | ||
} | ||
} | ||
if (handle !== null) { | ||
return { | ||
handler: handle.handler, | ||
params: paramsObj, | ||
store: handle.store | ||
store: handle.store, | ||
params: handle._createParamsObject(params), | ||
searchParams: this.querystringParser(querystring) | ||
} | ||
@@ -451,12 +460,14 @@ } | ||
if (currentNode.kind === NODE_TYPES.WILDCARD) { | ||
const decoded = sanitizedUrl.sliceParameter(pathIndex) | ||
if (decoded === null) { | ||
return this._onBadUrl(path.slice(pathIndex)) | ||
let param = originPath.slice(pathIndex) | ||
const firstPercentIndex = param.indexOf('%') | ||
if (firstPercentIndex !== -1) { | ||
param = safeDecodeURIComponent(param, firstPercentIndex) | ||
} | ||
if (decoded.length > maxParamLength) { | ||
if (param.length > maxParamLength) { | ||
return null | ||
} | ||
params.push(decoded) | ||
params.push(param) | ||
pathIndex = pathLen | ||
@@ -468,31 +479,33 @@ continue | ||
let paramEndIndex = pathIndex | ||
let firstPercentIndex = -1 | ||
for (; paramEndIndex < pathLen; paramEndIndex++) { | ||
if (path.charCodeAt(paramEndIndex) === 47) { | ||
const charCode = path.charCodeAt(paramEndIndex) | ||
if (charCode === 47) { | ||
break | ||
} else if (firstPercentIndex === -1 && charCode === 37) { | ||
firstPercentIndex = paramEndIndex - pathIndex | ||
} | ||
} | ||
const decoded = sanitizedUrl.sliceParameter(pathIndex, paramEndIndex) | ||
if (decoded === null) { | ||
return this._onBadUrl(path.slice(pathIndex, paramEndIndex)) | ||
let param = originPath.slice(pathIndex, paramEndIndex) | ||
if (firstPercentIndex !== -1) { | ||
param = safeDecodeURIComponent(param, firstPercentIndex) | ||
} | ||
if (currentNode.isRegex) { | ||
const matchedParameters = currentNode.regex.exec(decoded) | ||
if (matchedParameters === null) { | ||
return null | ||
} | ||
const matchedParameters = currentNode.regex.exec(param) | ||
if (matchedParameters === null) continue | ||
for (let i = 1; i < matchedParameters.length; i++) { | ||
const param = matchedParameters[i] | ||
if (param.length > maxParamLength) { | ||
const matchedParam = matchedParameters[i] | ||
if (matchedParam.length > maxParamLength) { | ||
return null | ||
} | ||
params.push(param) | ||
params.push(matchedParam) | ||
} | ||
} else { | ||
if (decoded.length > maxParamLength) { | ||
if (param.length > maxParamLength) { | ||
return null | ||
} | ||
params.push(decoded) | ||
params.push(param) | ||
} | ||
@@ -505,2 +518,12 @@ | ||
Router.prototype._rebuild = function (routes) { | ||
this.reset() | ||
for (const route of routes) { | ||
const { method, path, opts, handler, store } = route | ||
this._on(method, path, opts, handler, store) | ||
this.routes.push({ method, path, opts, handler, store }) | ||
} | ||
} | ||
Router.prototype._defaultRoute = function (req, res, ctx) { | ||
@@ -507,0 +530,0 @@ if (this.defaultRoute !== null) { |
@@ -18,15 +18,4 @@ 'use strict' | ||
if (customStrategies) { | ||
var kCustomStrategies = Object.keys(customStrategies) | ||
var strategy | ||
for (var i = 0; i < kCustomStrategies.length; i++) { | ||
strategy = customStrategies[kCustomStrategies[i]] | ||
assert(typeof strategy.name === 'string' && strategy.name !== '', 'strategy.name is required.') | ||
assert(strategy.storage && typeof strategy.storage === 'function', 'strategy.storage function is required.') | ||
assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', 'strategy.deriveConstraint function is required.') | ||
strategy.isCustom = true | ||
this.strategies[strategy.name] = strategy | ||
if (strategy.mustMatchWhenDerived) { | ||
this.noteUsage({ [kCustomStrategies[i]]: strategy }) | ||
} | ||
for (const strategy of Object.values(customStrategies)) { | ||
this.addConstraintStrategy(strategy) | ||
} | ||
@@ -36,2 +25,31 @@ } | ||
hasConstraintStrategy (strategyName) { | ||
const customConstraintStrategy = this.strategies[strategyName] | ||
if (customConstraintStrategy !== undefined) { | ||
return customConstraintStrategy.isCustom || this.strategiesInUse.has(strategyName) | ||
} | ||
return false | ||
} | ||
addConstraintStrategy (strategy) { | ||
assert(typeof strategy.name === 'string' && strategy.name !== '', 'strategy.name is required.') | ||
assert(strategy.storage && typeof strategy.storage === 'function', 'strategy.storage function is required.') | ||
assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', 'strategy.deriveConstraint function is required.') | ||
if (this.strategies[strategy.name] && this.strategies[strategy.name].isCustom) { | ||
throw new Error(`There already exists a custom constraint with the name ${strategy.name}.`) | ||
} | ||
if (this.strategiesInUse.has(strategy.name)) { | ||
throw new Error(`There already exists a route with ${strategy.name} constraint.`) | ||
} | ||
strategy.isCustom = true | ||
this.strategies[strategy.name] = strategy | ||
if (strategy.mustMatchWhenDerived) { | ||
this.noteUsage({ [strategy.name]: strategy }) | ||
} | ||
} | ||
deriveConstraints (req, ctx) { | ||
@@ -83,9 +101,4 @@ return undefined | ||
const lines = [` | ||
const derivedConstraints = { | ||
__hasMustMatchValues: false, | ||
`] | ||
const lines = ['return {'] | ||
const mustMatchKeys = [] | ||
for (const key of this.strategiesInUse) { | ||
@@ -105,6 +118,2 @@ const strategy = this.strategies[key] | ||
} | ||
if (strategy.mustMatchWhenDerived) { | ||
mustMatchKeys.push(key) | ||
} | ||
} | ||
@@ -114,10 +123,2 @@ | ||
// There are some constraints that can be derived and marked as "must match", where if they are derived, they only match routes that actually have a constraint on the value, like the SemVer version constraint. | ||
// An example: a request comes in for version 1.x, and this node has a handler that matches the path, but there's no version constraint. For SemVer, the find-my-way semantics do not match this handler to that request. | ||
// This function is used by Nodes with handlers to match when they don't have any constrained routes to exclude request that do have must match derived constraints present. | ||
if (mustMatchKeys.length > 0) { | ||
lines.push(`derivedConstraints.__hasMustMatchValues = !!(${(mustMatchKeys.map(key => `derivedConstraints.${key}`).join(' || '))})`) | ||
} | ||
lines.push('return derivedConstraints') | ||
this.deriveConstraints = new Function('req', 'ctx', lines.join('\n')).bind(this) // eslint-disable-line | ||
@@ -124,0 +125,0 @@ } |
@@ -252,3 +252,3 @@ 'use strict' | ||
function flattenNode (flattened, node, method) { | ||
if (node.handlerStorage.handlers.length > 0) { | ||
if (node.handlerStorage.handlers.length !== 0) { | ||
flattened.nodes.push({ method, node }) | ||
@@ -255,0 +255,0 @@ } |
'use strict' | ||
const fastDecode = require('fast-decode-uri-component') | ||
// It must spot all the chars where decodeURIComponent(x) !== decodeURI(x) | ||
// The chars are: # $ & + , / : ; = ? @ | ||
const uriComponentsCharMap = new Array(53).fill(0x00) | ||
uriComponentsCharMap[50] = new Array(103).fill(0x00) | ||
uriComponentsCharMap[50][51] = true // # '%23' | ||
uriComponentsCharMap[50][52] = true // $ '%24' | ||
uriComponentsCharMap[50][54] = true // & '%26' | ||
uriComponentsCharMap[50][66] = true // + '%2B' | ||
uriComponentsCharMap[50][98] = true // + '%2b' | ||
uriComponentsCharMap[50][67] = true // , '%2C' | ||
uriComponentsCharMap[50][99] = true // , '%2c' | ||
uriComponentsCharMap[50][70] = true // / '%2F' | ||
uriComponentsCharMap[50][102] = true // / '%2f' | ||
function decodeComponentChar (highCharCode, lowCharCode) { | ||
if (highCharCode === 50) { | ||
if (lowCharCode === 53) return '%' | ||
uriComponentsCharMap[51] = new Array(103).fill(0x00) | ||
uriComponentsCharMap[51][65] = true // : '%3A' | ||
uriComponentsCharMap[51][97] = true // : '%3a' | ||
uriComponentsCharMap[51][66] = true // ; '%3B' | ||
uriComponentsCharMap[51][98] = true // ; '%3b' | ||
uriComponentsCharMap[51][68] = true // = '%3D' | ||
uriComponentsCharMap[51][100] = true // = '%3d' | ||
uriComponentsCharMap[51][70] = true // ? '%3F' | ||
uriComponentsCharMap[51][102] = true // ? '%3f' | ||
if (lowCharCode === 51) return '#' | ||
if (lowCharCode === 52) return '$' | ||
if (lowCharCode === 54) return '&' | ||
if (lowCharCode === 66) return '+' | ||
if (lowCharCode === 98) return '+' | ||
if (lowCharCode === 67) return ',' | ||
if (lowCharCode === 99) return ',' | ||
if (lowCharCode === 70) return '/' | ||
if (lowCharCode === 102) return '/' | ||
return null | ||
} | ||
if (highCharCode === 51) { | ||
if (lowCharCode === 65) return ':' | ||
if (lowCharCode === 97) return ':' | ||
if (lowCharCode === 66) return ';' | ||
if (lowCharCode === 98) return ';' | ||
if (lowCharCode === 68) return '=' | ||
if (lowCharCode === 100) return '=' | ||
if (lowCharCode === 70) return '?' | ||
if (lowCharCode === 102) return '?' | ||
return null | ||
} | ||
if (highCharCode === 52 && lowCharCode === 48) { | ||
return '@' | ||
} | ||
return null | ||
} | ||
uriComponentsCharMap[52] = new Array(49).fill(0x00) | ||
uriComponentsCharMap[52][48] = true // @ '%40' | ||
function sanitizeUrl (url, customDecoder) { | ||
let originPath = url | ||
function safeDecodeURI (path) { | ||
let shouldDecode = false | ||
let containsEncodedComponents = false | ||
let highChar | ||
let lowChar | ||
for (var i = 0, len = url.length; i < len; i++) { | ||
var charCode = url.charCodeAt(i) | ||
let querystring = '' | ||
if (shouldDecode && !containsEncodedComponents) { | ||
if (highChar === 0 && uriComponentsCharMap[charCode]) { | ||
highChar = charCode | ||
lowChar = 0x00 | ||
} else if (highChar && lowChar === 0 && uriComponentsCharMap[highChar][charCode]) { | ||
containsEncodedComponents = true | ||
for (let i = 1; i < path.length; i++) { | ||
const charCode = path.charCodeAt(i) | ||
if (charCode === 37) { | ||
const highCharCode = path.charCodeAt(i + 1) | ||
const lowCharCode = path.charCodeAt(i + 2) | ||
if (decodeComponentChar(highCharCode, lowCharCode) === null) { | ||
shouldDecode = true | ||
} else { | ||
highChar = undefined | ||
lowChar = undefined | ||
// %25 - encoded % char. We need to encode one more time to prevent double decoding | ||
if (highCharCode === 50 && lowCharCode === 53) { | ||
shouldDecode = true | ||
path = path.slice(0, i + 1) + '25' + path.slice(i + 1) | ||
i += 2 | ||
} | ||
i += 2 | ||
} | ||
} | ||
// Some systems do not follow RFC and separate the path and query | ||
// string with a `;` character (code 59), e.g. `/foo;jsessionid=123456`. | ||
// Thus, we need to split on `;` as well as `?` and `#`. | ||
if (charCode === 63 || charCode === 59 || charCode === 35) { | ||
originPath = url.slice(0, i) | ||
} else if (charCode === 63 || charCode === 59 || charCode === 35) { | ||
querystring = path.slice(i + 1) | ||
path = path.slice(0, i) | ||
break | ||
} else if (charCode === 37) { | ||
shouldDecode = true | ||
highChar = 0x00 | ||
} | ||
} | ||
const decoded = shouldDecode ? decodeURI(originPath) : originPath | ||
const decodedPath = shouldDecode ? decodeURI(path) : path | ||
return { path: decodedPath, querystring } | ||
} | ||
return { | ||
path: decoded, | ||
originPath, | ||
sliceParameter: containsEncodedComponents | ||
? sliceAndDecode | ||
: slicePath | ||
} | ||
function safeDecodeURIComponent (uriComponent, startIndex) { | ||
let decoded = '' | ||
let lastIndex = startIndex | ||
function sliceAndDecode (from, to) { | ||
return (customDecoder || fastDecode)(this.originPath.slice(from, to)) | ||
for (let i = startIndex; i < uriComponent.length; i++) { | ||
if (uriComponent.charCodeAt(i) === 37) { | ||
const highCharCode = uriComponent.charCodeAt(i + 1) | ||
const lowCharCode = uriComponent.charCodeAt(i + 2) | ||
const decodedChar = decodeComponentChar(highCharCode, lowCharCode) | ||
decoded += uriComponent.slice(lastIndex, i) + decodedChar | ||
lastIndex = i + 3 | ||
} | ||
} | ||
return uriComponent.slice(0, startIndex) + decoded + uriComponent.slice(lastIndex) | ||
} | ||
module.exports = sanitizeUrl | ||
function slicePath (from, to) { | ||
return this.path.slice(from, to) | ||
} | ||
module.exports = { safeDecodeURI, safeDecodeURIComponent } |
{ | ||
"name": "find-my-way", | ||
"version": "5.6.0", | ||
"version": "6.0.0", | ||
"description": "Crazy fast http radix based router", | ||
@@ -50,3 +50,2 @@ "main": "index.js", | ||
"dependencies": { | ||
"fast-decode-uri-component": "^1.0.1", | ||
"fast-deep-equal": "^3.1.3", | ||
@@ -53,0 +52,0 @@ "safe-regex2": "^2.0.0" |
@@ -99,2 +99,17 @@ # find-my-way | ||
The default query string parser that find-my-way uses is the Node.js's core querystring module. You can change this default setting by passing the option querystringParser and use a custom one, such as [qs](https://www.npmjs.com/package/qs). | ||
```js | ||
const qs = require('qs') | ||
const router = require('find-my-way')({ | ||
querystringParser: str => qs.parse(str) | ||
}) | ||
router.on('GET', '/', (req, res, params, store, searchParams) => { | ||
assert.equal(searchParams, { foo: 'bar', baz: 'faz' }) | ||
}) | ||
router.lookup({ method: 'GET', url: '/?foo=bar&baz=faz' }, null) | ||
``` | ||
You can assign a `buildPrettyMeta` function to sanitize a route's `store` object to use with the `prettyPrint` functions. This function should accept a single object and return an object. | ||
@@ -154,4 +169,6 @@ | ||
Custom constraining strategies can be added and are matched against incoming requests while trying to maintain `find-my-way`'s high performance. To register a new type of constraint, you must add a new constraint strategy that knows how to match values to handlers, and that knows how to get the constraint value from a request. Register strategies when constructing a router: | ||
Custom constraining strategies can be added and are matched against incoming requests while trying to maintain `find-my-way`'s high performance. To register a new type of constraint, you must add a new constraint strategy that knows how to match values to handlers, and that knows how to get the constraint value from a request. Register strategies when constructing a router or use the addConstraintStrategy method. | ||
Add a custom constrain strategy when constructing a router: | ||
```js | ||
@@ -180,2 +197,28 @@ const customResponseTypeStrategy = { | ||
Add a custom constraint strategy using the addConstraintStrategy method: | ||
```js | ||
const customResponseTypeStrategy = { | ||
// strategy name for referencing in the route handler `constraints` options | ||
name: 'accept', | ||
// storage factory for storing routes in the find-my-way route tree | ||
storage: function () { | ||
let handlers = {} | ||
return { | ||
get: (type) => { return handlers[type] || null }, | ||
set: (type, store) => { handlers[type] = store } | ||
} | ||
}, | ||
// function to get the value of the constraint from each incoming request | ||
deriveConstraint: (req, ctx) => { | ||
return req.headers['accept'] | ||
}, | ||
// optional flag marking if handlers without constraints can match requests that have a value for this constraint | ||
mustMatchWhenDerived: true | ||
} | ||
const router = FindMyWay(); | ||
router.addConstraintStrategy(customResponseTypeStrategy); | ||
``` | ||
Once a custom constraint strategy is registered, routes can be added that are constrained using it: | ||
@@ -233,3 +276,3 @@ | ||
```js | ||
router.on('GET', '/example', (req, res, params) => { | ||
router.on('GET', '/example', (req, res, params, store, searchParams) => { | ||
// your code | ||
@@ -326,16 +369,2 @@ }) | ||
If your routes' parameters are not Basic Latin charaters, you may face a performance drop. To avoid it, you can customize the way the parameters are parsed by passing a custom decoding function. Read more about it [here](https://github.com/delvedor/find-my-way/pull/211). | ||
The parsing function is called when the request URL contains one or more encoded special characters: `# $ & + , / : ; = ? @`. | ||
```js | ||
const router = require('find-my-way')({ | ||
decodeUriParameters: (stringToDecode) => { | ||
// called when the request URL contains st least one special character: `# $ & + , / : ; = ? @` | ||
return decodeURIComponent(stringToDecode) | ||
} | ||
}) | ||
``` | ||
By default, this module relies on [fast-decode-uri-component`](https://www.npmjs.com/package/fast-decode-uri-component) to parse the encoded path parameters. | ||
<a name="match-order"></a> | ||
@@ -342,0 +371,0 @@ ##### Match order |
@@ -40,2 +40,19 @@ 'use strict' | ||
test('A route could support multiple versions (find) / 1 (add strategy outside constructor)', t => { | ||
t.plan(5) | ||
const findMyWay = FindMyWay() | ||
findMyWay.addConstraintStrategy(customVersioning) | ||
findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, noop) | ||
findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, noop) | ||
t.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=2' })) | ||
t.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=3' })) | ||
t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=4' })) | ||
t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=5' })) | ||
t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=6' })) | ||
}) | ||
test('Overriding default strategies uses the custom deriveConstraint function', t => { | ||
@@ -65,1 +82,52 @@ t.plan(2) | ||
}) | ||
test('Overriding default strategies uses the custom deriveConstraint function (add strategy outside constructor)', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.addConstraintStrategy(customVersioning) | ||
findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, (req, res, params) => { | ||
t.equal(req.headers.accept, 'application/vnd.example.api+json;version=2') | ||
}) | ||
findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, (req, res, params) => { | ||
t.equal(req.headers.accept, 'application/vnd.example.api+json;version=3') | ||
}) | ||
findMyWay.lookup({ | ||
method: 'GET', | ||
url: '/', | ||
headers: { accept: 'application/vnd.example.api+json;version=2' } | ||
}) | ||
findMyWay.lookup({ | ||
method: 'GET', | ||
url: '/', | ||
headers: { accept: 'application/vnd.example.api+json;version=3' } | ||
}) | ||
}) | ||
test('Overriding custom strategies throws as error (add strategy outside constructor)', t => { | ||
t.plan(1) | ||
const findMyWay = FindMyWay() | ||
findMyWay.addConstraintStrategy(customVersioning) | ||
t.throws(() => findMyWay.addConstraintStrategy(customVersioning), | ||
'There already exists a custom constraint with the name version.' | ||
) | ||
}) | ||
test('Overriding default strategies after defining a route with constraint', t => { | ||
t.plan(1) | ||
const findMyWay = FindMyWay() | ||
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, () => {}) | ||
t.throws(() => findMyWay.addConstraintStrategy(customVersioning), | ||
'There already exists a route with version constraint.' | ||
) | ||
}) |
@@ -40,2 +40,17 @@ 'use strict' | ||
test('A route could support a custom constraint strategy (add strategy outside constructor)', t => { | ||
t.plan(3) | ||
const findMyWay = FindMyWay() | ||
findMyWay.addConstraintStrategy(customHeaderConstraint) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl' } }, alpha) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget' } }, beta) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl' }).handler, alpha) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'wget' }).handler, beta) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome' })) | ||
}) | ||
test('A route could support a custom constraint strategy while versioned', t => { | ||
@@ -63,2 +78,26 @@ t.plan(8) | ||
test('A route could support a custom constraint strategy while versioned (add strategy outside constructor)', t => { | ||
t.plan(8) | ||
const findMyWay = FindMyWay() | ||
findMyWay.addConstraintStrategy(customHeaderConstraint) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '1.0.0' } }, alpha) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0' } }, beta) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget', version: '2.0.0' } }, gamma) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget', version: '3.0.0' } }, delta) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x' }).handler, alpha) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x' }).handler, beta) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '2.x' }).handler, gamma) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '3.x' }).handler, delta) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome' })) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome', version: '1.x' })) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '3.x' })) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '1.x' })) | ||
}) | ||
test('A route could support a custom constraint strategy while versioned and host constrained', t => { | ||
@@ -85,2 +124,25 @@ t.plan(9) | ||
test('A route could support a custom constraint strategy while versioned and host constrained (add strategy outside constructor)', t => { | ||
t.plan(9) | ||
const findMyWay = FindMyWay() | ||
findMyWay.addConstraintStrategy(customHeaderConstraint) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '1.0.0', host: 'fastify.io' } }, alpha) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0', host: 'fastify.io' } }, beta) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0', host: 'example.io' } }, delta) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x', host: 'fastify.io' }).handler, alpha) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x', host: 'fastify.io' }).handler, beta) | ||
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x', host: 'example.io' }).handler, delta) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome' })) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome', version: '1.x' })) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x' })) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x' })) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '3.x', host: 'fastify.io' })) | ||
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x', host: 'example.io' })) | ||
}) | ||
test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived and there are no other routes', t => { | ||
@@ -106,2 +168,21 @@ t.plan(1) | ||
test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived and there are no other routes (add strategy outside constructor)', t => { | ||
t.plan(1) | ||
const findMyWay = FindMyWay({ | ||
defaultRoute (req, res) { | ||
t.pass() | ||
} | ||
}) | ||
findMyWay.addConstraintStrategy({ | ||
...customHeaderConstraint, | ||
mustMatchWhenDerived: true | ||
}) | ||
findMyWay.on('GET', '/', {}, () => t.fail()) | ||
findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) | ||
}) | ||
test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived when there are constrained routes', t => { | ||
@@ -129,2 +210,23 @@ t.plan(1) | ||
test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived when there are constrained routes (add strategy outside constructor)', t => { | ||
t.plan(1) | ||
const findMyWay = FindMyWay({ | ||
defaultRoute (req, res) { | ||
t.pass() | ||
} | ||
}) | ||
findMyWay.addConstraintStrategy({ | ||
...customHeaderConstraint, | ||
mustMatchWhenDerived: true | ||
}) | ||
findMyWay.on('GET', '/', {}, () => t.fail()) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl' } }, () => t.fail()) | ||
findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget' } }, () => t.fail()) | ||
findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) | ||
}) | ||
test('Custom constraint strategies can set mustMatchWhenDerived flag to false which allows matches to unconstrained routes when a constraint is derived', t => { | ||
@@ -149,1 +251,30 @@ t.plan(1) | ||
}) | ||
test('Custom constraint strategies can set mustMatchWhenDerived flag to false which allows matches to unconstrained routes when a constraint is derived (add strategy outside constructor)', t => { | ||
t.plan(1) | ||
const findMyWay = FindMyWay({ | ||
defaultRoute (req, res) { | ||
t.pass() | ||
} | ||
}) | ||
findMyWay.addConstraintStrategy({ | ||
...customHeaderConstraint, | ||
mustMatchWhenDerived: true | ||
}) | ||
findMyWay.on('GET', '/', {}, () => t.pass()) | ||
findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) | ||
}) | ||
test('Has constraint strategy method test', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
t.same(findMyWay.hasConstraintStrategy(customHeaderConstraint.name), false) | ||
findMyWay.addConstraintStrategy(customHeaderConstraint) | ||
t.same(findMyWay.hasConstraintStrategy(customHeaderConstraint.name), true) | ||
}) |
@@ -99,3 +99,3 @@ 'use strict' | ||
t.notOk(findMyWay.find('GET', '/', { version: '1.x', __hasMustMatchValues: true })) | ||
t.notOk(findMyWay.find('GET', '/', { version: '1.x' })) | ||
}) | ||
@@ -102,0 +102,0 @@ |
@@ -62,3 +62,3 @@ 'use strict' | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta) | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha) | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null) | ||
}) | ||
@@ -76,3 +76,3 @@ | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta) | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha) | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null) | ||
}) | ||
@@ -90,4 +90,23 @@ | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta) | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha) | ||
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null) | ||
t.equal(findMyWay.find('GET', '/', { host: 'example.io' }).handler, gamma) | ||
}) | ||
test('Has constraint strategy method test', t => { | ||
t.plan(6) | ||
const findMyWay = FindMyWay() | ||
t.same(findMyWay.hasConstraintStrategy('version'), false) | ||
t.same(findMyWay.hasConstraintStrategy('host'), false) | ||
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, () => {}) | ||
t.same(findMyWay.hasConstraintStrategy('version'), false) | ||
t.same(findMyWay.hasConstraintStrategy('host'), true) | ||
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, () => {}) | ||
t.same(findMyWay.hasConstraintStrategy('version'), true) | ||
t.same(findMyWay.hasConstraintStrategy('host'), true) | ||
}) |
@@ -29,5 +29,5 @@ 'use strict' | ||
router1.on('GET', '/', { constraints: { secret: 'alpha' } }, () => {}) | ||
router1.find('GET', '/', { constraints: { secret: 'alpha' } }) | ||
router1.find('GET', '/', { secret: 'alpha' }) | ||
t.pass('constraints is not overrided') | ||
}) |
@@ -438,3 +438,3 @@ 'use strict' | ||
findMyWay.find('GET', '/test'), | ||
{ handler: fn, params: {}, store: null } | ||
{ handler: fn, params: {}, store: null, searchParams: {} } | ||
) | ||
@@ -452,3 +452,3 @@ }) | ||
findMyWay.find('GET', '/test/hello'), | ||
{ handler: fn, params: { id: 'hello' }, store: null } | ||
{ handler: fn, params: { id: 'hello' }, store: null, searchParams: {} } | ||
) | ||
@@ -476,3 +476,3 @@ }) | ||
findMyWay.find('GET', '/test/he%2Fllo'), | ||
{ handler: fn, params: { id: 'he/llo' }, store: null } | ||
{ handler: fn, params: { id: 'he/llo' }, store: null, searchParams: {} } | ||
) | ||
@@ -490,3 +490,3 @@ }) | ||
findMyWay.find('GET', '/test/he%2Fllo'), | ||
{ handler: fn, params: { '*': 'he/llo' }, store: null } | ||
{ handler: fn, params: { '*': 'he/llo' }, store: null, searchParams: {} } | ||
) | ||
@@ -493,0 +493,0 @@ }) |
@@ -8,6 +8,7 @@ 'use strict' | ||
test('should sanitize the url - query', t => { | ||
t.plan(1) | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.on('GET', '/test', (req, res, params) => { | ||
findMyWay.on('GET', '/test', (req, res, params, store, query) => { | ||
t.same(query, { hello: 'world' }) | ||
t.ok('inside the handler') | ||
@@ -20,6 +21,7 @@ }) | ||
test('should sanitize the url - hash', t => { | ||
t.plan(1) | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.on('GET', '/test', (req, res, params) => { | ||
findMyWay.on('GET', '/test', (req, res, params, store, query) => { | ||
t.same(query, { hello: '' }) | ||
t.ok('inside the handler') | ||
@@ -32,6 +34,7 @@ }) | ||
test('handles path and query separated by ;', t => { | ||
t.plan(1) | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.on('GET', '/test', (req, res, params) => { | ||
findMyWay.on('GET', '/test', (req, res, params, store, query) => { | ||
t.same(query, { jsessionid: '123456' }) | ||
t.ok('inside the handler') | ||
@@ -38,0 +41,0 @@ }) |
@@ -28,3 +28,4 @@ 'use strict' | ||
params: {}, | ||
store: { hello: 'world' } | ||
store: { hello: 'world' }, | ||
searchParams: {} | ||
}) | ||
@@ -31,0 +32,0 @@ }) |
@@ -54,3 +54,3 @@ import { expectType } from 'tsd' | ||
expectType<void>(router.lookup(http1Req, http1Res)) | ||
expectType<any>(router.lookup(http1Req, http1Res)) | ||
expectType<Router.FindResult<Router.HTTPVersion.V1> | null>(router.find('GET', '/')) | ||
@@ -70,2 +70,19 @@ expectType<Router.FindResult<Router.HTTPVersion.V1> | null>(router.find('GET', '/', {})) | ||
{ | ||
const constraints: { [key: string]: Router.ConstraintStrategy<Router.HTTPVersion.V2, string> } = { | ||
foo: { | ||
name: 'foo', | ||
mustMatchWhenDerived: true, | ||
storage () { | ||
return { | ||
get (version) { return handler }, | ||
set (version, handler) {}, | ||
del (version) {}, | ||
empty () {} | ||
} | ||
}, | ||
deriveConstraint(req) { return '1.0.0' }, | ||
validate(value) { if (typeof value === "string") { throw new Error("invalid")} } | ||
} | ||
} | ||
let handler: Router.Handler<Router.HTTPVersion.V2> | ||
@@ -79,18 +96,3 @@ const router = Router<Router.HTTPVersion.V2>({ | ||
onBadUrl (path, http1Req, http1Res) {}, | ||
constraints: { | ||
foo: { | ||
name: 'foo', | ||
mustMatchWhenDerived: true, | ||
storage () { | ||
return { | ||
get (version) { return handler }, | ||
set (version, handler) {}, | ||
del (version) {}, | ||
empty () {} | ||
} | ||
}, | ||
deriveConstraint(req) { return '1.0.0' }, | ||
validate(value) { if (typeof value === "string") { throw new Error("invalid")} } | ||
} | ||
} | ||
constraints | ||
}) | ||
@@ -105,2 +107,4 @@ expectType<Router.Instance<Router.HTTPVersion.V2>>(router) | ||
expectType<void>(router.addConstraintStrategy(constraints.foo)) | ||
expectType<void>(router.get('/', () => {})) | ||
@@ -114,3 +118,3 @@ expectType<void>(router.get('/', { constraints: { version: '1.0.0' }}, () => {})) | ||
expectType<void>(router.lookup(http2Req, http2Res)) | ||
expectType<any>(router.lookup(http2Req, http2Res)) | ||
expectType<Router.FindResult<Router.HTTPVersion.V2> | null>(router.find('GET', '/', {})) | ||
@@ -148,2 +152,3 @@ expectType<Router.FindResult<Router.HTTPVersion.V2> | null>(router.find('GET', '/', {version: '1.0.0', host: 'fastify.io'})) | ||
} | ||
const storageWithObject = customConstraintWithObject.storage() | ||
@@ -150,0 +155,0 @@ const acceptAndContentType: AcceptAndContentType = { accept: 'application/json', contentType: 'application/xml' } |
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
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
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
293558
2
79
7343
611
10
- Removedfast-decode-uri-component@^1.0.1
- Removedfast-decode-uri-component@1.0.1(transitive)