find-my-way
Advanced tools
Comparing version 7.5.0 to 7.6.0
@@ -13,4 +13,18 @@ 'use strict' | ||
constructor () { | ||
this.handlerStorage = new HandlerStorage() | ||
this.isLeafNode = false | ||
this.routes = null | ||
this.handlerStorage = null | ||
} | ||
addRoute (route, constrainer) { | ||
if (this.routes === null) { | ||
this.routes = [] | ||
} | ||
if (this.handlerStorage === null) { | ||
this.handlerStorage = new HandlerStorage() | ||
} | ||
this.isLeafNode = true | ||
this.routes.push(route) | ||
this.handlerStorage.addHandler(constrainer, route) | ||
} | ||
} | ||
@@ -65,3 +79,3 @@ | ||
createParametricChild (regex, staticSuffix) { | ||
createParametricChild (regex, staticSuffix, nodePath) { | ||
const regexpSource = regex && regex.source | ||
@@ -75,6 +89,7 @@ | ||
if (parametricChild) { | ||
parametricChild.nodePaths.add(nodePath) | ||
return parametricChild | ||
} | ||
parametricChild = new ParametricNode(regex, staticSuffix) | ||
parametricChild = new ParametricNode(regex, staticSuffix, nodePath) | ||
this.parametricChildren.push(parametricChild) | ||
@@ -168,3 +183,3 @@ this.parametricChildren.sort((child1, child2) => { | ||
class ParametricNode extends ParentNode { | ||
constructor (regex, staticSuffix) { | ||
constructor (regex, staticSuffix, nodePath) { | ||
super() | ||
@@ -175,2 +190,4 @@ this.isRegex = !!regex | ||
this.kind = NODE_TYPES.PARAMETRIC | ||
this.nodePaths = new Set([nodePath]) | ||
} | ||
@@ -177,0 +194,0 @@ |
'use strict' | ||
const httpMethodStrategy = require('./lib/strategies/http-method') | ||
class HandlerStorage { | ||
@@ -19,16 +21,20 @@ constructor () { | ||
addHandler (handler, params, store, constrainer, constraints) { | ||
addHandler (constrainer, route) { | ||
const params = route.params | ||
const constraints = route.opts.constraints || {} | ||
const handlerObject = { | ||
handler, | ||
params, | ||
constraints, | ||
store: store || null, | ||
handler: route.handler, | ||
store: route.store || null, | ||
_createParamsObject: this._compileCreateParamsObject(params) | ||
} | ||
if (Object.keys(constraints).length === 0) { | ||
const constraintsNames = Object.keys(constraints) | ||
if (constraintsNames.length === 0) { | ||
this.unconstrainedHandler = handlerObject | ||
} | ||
for (const constraint of Object.keys(constraints)) { | ||
for (const constraint of constraintsNames) { | ||
if (!this.constraints.includes(constraint)) { | ||
@@ -44,3 +50,4 @@ if (constraint === 'version') { | ||
if (this.handlers.length >= 32) { | ||
const isMergedTree = constraintsNames.includes(httpMethodStrategy.name) | ||
if (!isMergedTree && 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') | ||
@@ -53,3 +60,5 @@ } | ||
this._compileGetHandlerMatchingConstraints(constrainer, constraints) | ||
if (!isMergedTree) { | ||
this._compileGetHandlerMatchingConstraints(constrainer, constraints) | ||
} | ||
} | ||
@@ -56,0 +65,0 @@ |
@@ -164,3 +164,7 @@ import { IncomingMessage, ServerResponse } from 'http'; | ||
prettyPrint(): string; | ||
prettyPrint(opts: { commonPrefix?: boolean, includeMeta?: boolean | (string | symbol)[] }): string; | ||
prettyPrint(opts: { | ||
method?: HTTPMethod, | ||
commonPrefix?: boolean, | ||
includeMeta?: boolean | (string | symbol)[] | ||
}): string; | ||
@@ -167,0 +171,0 @@ hasConstraintStrategy(strategyName: string): boolean; |
122
index.js
@@ -32,6 +32,7 @@ 'use strict' | ||
const deepEqual = require('fast-deep-equal') | ||
const { flattenNode, compressFlattenedNode, prettyPrintFlattenedNode, prettyPrintRoutesArray } = require('./lib/pretty-print') | ||
const { prettyPrintTree } = require('./lib/pretty-print') | ||
const { StaticNode, NODE_TYPES } = require('./custom_node') | ||
const Constrainer = require('./lib/constrainer') | ||
const httpMethods = require('./lib/http-methods') | ||
const httpMethodStrategy = require('./lib/strategies/http-method') | ||
const { safeDecodeURI, safeDecodeURIComponent } = require('./lib/url-sanitizer') | ||
@@ -55,2 +56,3 @@ | ||
opts = opts || {} | ||
this._opts = opts | ||
@@ -90,7 +92,6 @@ if (opts.defaultRoute) { | ||
this.allowUnsafeRegex = opts.allowUnsafeRegex || false | ||
this.constrainer = new Constrainer(opts.constraints) | ||
this.routes = [] | ||
this.trees = {} | ||
this.constrainer = new Constrainer(opts.constraints) | ||
this._routesPatterns = {} | ||
} | ||
@@ -138,4 +139,5 @@ | ||
for (const method of methods) { | ||
assert(typeof method === 'string', 'Method should be a string') | ||
assert(httpMethods.includes(method), `Method '${method}' is not an http method.`) | ||
this._on(method, path, opts, handler, store, route) | ||
this.routes.push({ method, path, opts, handler, store }) | ||
} | ||
@@ -145,5 +147,2 @@ } | ||
Router.prototype._on = function _on (method, path, opts, handler, store) { | ||
assert(typeof method === 'string', 'Method should be a string') | ||
assert(httpMethods.includes(method), `Method '${method}' is not an http method.`) | ||
let constraints = {} | ||
@@ -164,6 +163,6 @@ if (opts.constraints !== undefined) { | ||
this.trees[method] = new StaticNode('/') | ||
this._routesPatterns[method] = [] | ||
} | ||
if (path === '*' && this.trees[method].prefix.length !== 0) { | ||
let pattern = path | ||
if (pattern === '*' && this.trees[method].prefix.length !== 0) { | ||
const currentRoot = this.trees[method] | ||
@@ -178,4 +177,4 @@ this.trees[method] = new StaticNode('') | ||
const params = [] | ||
for (let i = 0; i <= path.length; i++) { | ||
if (path.charCodeAt(i) === 58 && path.charCodeAt(i + 1) === 58) { | ||
for (let i = 0; i <= pattern.length; i++) { | ||
if (pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) === 58) { | ||
// It's a double colon | ||
@@ -186,7 +185,7 @@ i++ | ||
const isParametricNode = path.charCodeAt(i) === 58 && path.charCodeAt(i + 1) !== 58 | ||
const isWildcardNode = path.charCodeAt(i) === 42 | ||
const isParametricNode = pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) !== 58 | ||
const isWildcardNode = pattern.charCodeAt(i) === 42 | ||
if (isParametricNode || isWildcardNode || (i === path.length && i !== parentNodePathIndex)) { | ||
let staticNodePath = path.slice(parentNodePathIndex, i) | ||
if (isParametricNode || isWildcardNode || (i === pattern.length && i !== parentNodePathIndex)) { | ||
let staticNodePath = pattern.slice(parentNodePathIndex, i) | ||
if (!this.caseSensitive) { | ||
@@ -207,10 +206,10 @@ staticNodePath = staticNodePath.toLowerCase() | ||
for (let j = lastParamStartIndex; ; j++) { | ||
const charCode = path.charCodeAt(j) | ||
const charCode = pattern.charCodeAt(j) | ||
const isRegexParam = charCode === 40 | ||
const isStaticPart = charCode === 45 || charCode === 46 | ||
const isEndOfNode = charCode === 47 || j === path.length | ||
const isEndOfNode = charCode === 47 || j === pattern.length | ||
if (isRegexParam || isStaticPart || isEndOfNode) { | ||
const paramName = path.slice(lastParamStartIndex, j) | ||
const paramName = pattern.slice(lastParamStartIndex, j) | ||
params.push(paramName) | ||
@@ -221,4 +220,4 @@ | ||
if (isRegexParam) { | ||
const endOfRegexIndex = getClosingParenthensePosition(path, j) | ||
const regexString = path.slice(j, endOfRegexIndex + 1) | ||
const endOfRegexIndex = getClosingParenthensePosition(pattern, j) | ||
const regexString = pattern.slice(j, endOfRegexIndex + 1) | ||
@@ -237,7 +236,7 @@ if (!this.allowUnsafeRegex) { | ||
const staticPartStartIndex = j | ||
for (; j < path.length; j++) { | ||
const charCode = path.charCodeAt(j) | ||
for (; j < pattern.length; j++) { | ||
const charCode = pattern.charCodeAt(j) | ||
if (charCode === 47) break | ||
if (charCode === 58) { | ||
const nextCharCode = path.charCodeAt(j + 1) | ||
const nextCharCode = pattern.charCodeAt(j + 1) | ||
if (nextCharCode === 58) j++ | ||
@@ -248,3 +247,3 @@ else break | ||
let staticPart = path.slice(staticPartStartIndex, j) | ||
let staticPart = pattern.slice(staticPartStartIndex, j) | ||
if (staticPart) { | ||
@@ -258,10 +257,11 @@ staticPart = staticPart.split('::').join(':') | ||
if (isEndOfNode || path.charCodeAt(j) === 47 || j === path.length) { | ||
if (isEndOfNode || pattern.charCodeAt(j) === 47 || j === pattern.length) { | ||
const nodePattern = isRegexNode ? '()' + staticPart : staticPart | ||
const nodePath = pattern.slice(i, j) | ||
path = path.slice(0, i + 1) + nodePattern + path.slice(j) | ||
pattern = pattern.slice(0, i + 1) + nodePattern + pattern.slice(j) | ||
i += nodePattern.length | ||
const regex = isRegexNode ? new RegExp('^' + regexps.join('') + '$') : null | ||
currentNode = currentNode.createParametricChild(regex, staticPart || null) | ||
currentNode = currentNode.createParametricChild(regex, staticPart || null, nodePath) | ||
parentNodePathIndex = i + 1 | ||
@@ -278,3 +278,3 @@ break | ||
if (i !== path.length - 1) { | ||
if (i !== pattern.length - 1) { | ||
throw new Error('Wildcard must be the last character in the route') | ||
@@ -286,17 +286,23 @@ } | ||
if (!this.caseSensitive) { | ||
path = path.toLowerCase() | ||
pattern = pattern.toLowerCase() | ||
} | ||
if (path === '*') { | ||
path = '/*' | ||
if (pattern === '*') { | ||
pattern = '/*' | ||
} | ||
for (const existRoute of this._routesPatterns[method]) { | ||
if (existRoute.path === path && deepEqual(existRoute.constraints, constraints)) { | ||
throw new Error(`Method '${method}' already declared for route '${path}' with constraints '${JSON.stringify(constraints)}'`) | ||
for (const existRoute of this.routes) { | ||
const routeConstraints = existRoute.opts.constraints || {} | ||
if ( | ||
existRoute.method === method && | ||
existRoute.pattern === pattern && | ||
deepEqual(routeConstraints, constraints) | ||
) { | ||
throw new Error(`Method '${method}' already declared for route '${pattern}' with constraints '${JSON.stringify(constraints)}'`) | ||
} | ||
} | ||
this._routesPatterns[method].push({ path, params, constraints }) | ||
currentNode.handlerStorage.addHandler(handler, params, store, this.constrainer, constraints) | ||
const route = { method, path, pattern, params, opts, handler, store } | ||
this.routes.push(route) | ||
currentNode.addRoute(route, this.constrainer) | ||
} | ||
@@ -316,3 +322,2 @@ | ||
this.routes = [] | ||
this._routesPatterns = {} | ||
} | ||
@@ -460,5 +465,4 @@ | ||
while (true) { | ||
if (pathIndex === pathLen) { | ||
if (pathIndex === pathLen && currentNode.isLeafNode) { | ||
const handle = currentNode.handlerStorage.getMatchingHandler(derivedConstraints) | ||
if (handle !== null) { | ||
@@ -546,3 +550,2 @@ return { | ||
this._on(method, path, opts, handler, store) | ||
this.routes.push({ method, path, opts, handler, store }) | ||
} | ||
@@ -574,21 +577,26 @@ } | ||
Router.prototype.prettyPrint = function (opts = {}) { | ||
opts.commonPrefix = opts.commonPrefix === undefined ? true : opts.commonPrefix // default to original behaviour | ||
if (!opts.commonPrefix) return prettyPrintRoutesArray.call(this, this.routes, opts) | ||
const root = { | ||
prefix: '/', | ||
nodes: [], | ||
children: {} | ||
} | ||
Router.prototype.prettyPrint = function (options = {}) { | ||
const method = options.method | ||
for (const method in this.trees) { | ||
const node = this.trees[method] | ||
if (node) { | ||
flattenNode(root, node, method) | ||
} | ||
options.buildPrettyMeta = this.buildPrettyMeta.bind(this) | ||
if (method === undefined) { | ||
const { version, host, ...constraints } = this.constrainer.strategies | ||
constraints[httpMethodStrategy.name] = httpMethodStrategy | ||
const mergedRouter = new Router({ ...this._opts, constraints }) | ||
const mergedRoutes = this.routes.map(route => { | ||
const constraints = { | ||
...route.opts.constraints, | ||
[httpMethodStrategy.name]: route.method | ||
} | ||
return { ...route, method: 'MERGED', opts: { constraints } } | ||
}) | ||
mergedRouter._rebuild(mergedRoutes) | ||
return prettyPrintTree(mergedRouter.trees.MERGED, options) | ||
} | ||
compressFlattenedNode(root) | ||
return prettyPrintFlattenedNode.call(this, root, '', true, opts) | ||
const tree = this.trees[method] | ||
return prettyPrintTree(tree, options) | ||
} | ||
@@ -595,0 +603,0 @@ |
'use strict' | ||
/* eslint-disable no-multi-spaces */ | ||
const indent = ' ' | ||
const branchIndent = '│ ' | ||
const midBranchIndent = '├── ' | ||
const endBranchIndent = '└── ' | ||
const wildcardDelimiter = '*' | ||
const pathDelimiter = '/' | ||
const pathRegExp = /(?=\/)/ | ||
/* eslint-enable */ | ||
const deepEqual = require('fast-deep-equal') | ||
const httpMethodStrategy = require('./strategies/http-method') | ||
const treeDataSymbol = Symbol('treeData') | ||
function printObjectTree (obj, parentPrefix = '') { | ||
let tree = '' | ||
const keys = Object.keys(obj) | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i] | ||
const value = obj[key] | ||
const isLast = i === keys.length - 1 | ||
const nodePrefix = isLast ? '└── ' : '├── ' | ||
const childPrefix = isLast ? ' ' : '│ ' | ||
const nodeData = value[treeDataSymbol] || '' | ||
const prefixedNodeData = nodeData.split('\n').join('\n' + parentPrefix + childPrefix) | ||
tree += parentPrefix + nodePrefix + key + prefixedNodeData + '\n' | ||
tree += printObjectTree(value, parentPrefix + childPrefix) | ||
} | ||
return tree | ||
} | ||
function parseFunctionName (fn) { | ||
@@ -28,248 +43,101 @@ let fName = fn.name || '' | ||
function buildMetaObject (route, metaArray) { | ||
const out = {} | ||
const cleanMeta = this.buildPrettyMeta(route) | ||
if (!Array.isArray(metaArray)) metaArray = cleanMeta ? Reflect.ownKeys(cleanMeta) : [] | ||
metaArray.forEach(m => { | ||
const metaKey = typeof m === 'symbol' ? m.toString() : m | ||
if (cleanMeta && cleanMeta[m]) { | ||
out[metaKey] = parseMeta(cleanMeta[m]) | ||
} | ||
}) | ||
return out | ||
} | ||
function getRouteMetaData (route, options) { | ||
if (!options.includeMeta) return {} | ||
function prettyPrintRoutesArray (routeArray, opts = {}) { | ||
if (!this.buildPrettyMeta) throw new Error('buildPrettyMeta not defined') | ||
opts.includeMeta = opts.includeMeta || null // array of meta objects to display | ||
const mergedRouteArray = [] | ||
const metaDataObject = options.buildPrettyMeta(route) | ||
const filteredMetaData = {} | ||
let tree = '' | ||
let includeMetaKeys = options.includeMeta | ||
if (!Array.isArray(includeMetaKeys)) { | ||
includeMetaKeys = Reflect.ownKeys(metaDataObject) | ||
} | ||
routeArray.sort((a, b) => { | ||
if (!a.path || !b.path) return 0 | ||
return a.path.localeCompare(b.path) | ||
}) | ||
for (const metaKey of includeMetaKeys) { | ||
if (!Object.prototype.hasOwnProperty.call(metaDataObject, metaKey)) continue | ||
// merge alike paths | ||
for (let i = 0; i < routeArray.length; i++) { | ||
const route = routeArray[i] | ||
const pathExists = mergedRouteArray.find(r => route.path === r.path) | ||
if (pathExists) { | ||
// path already declared, add new method and break out of loop | ||
pathExists.handlers.push({ | ||
method: route.method, | ||
opts: route.opts.constraints || undefined, | ||
meta: opts.includeMeta ? buildMetaObject.call(this, route, opts.includeMeta) : null | ||
}) | ||
continue | ||
} | ||
const serializedKey = metaKey.toString() | ||
const metaValue = metaDataObject[metaKey] | ||
const routeHandler = { | ||
method: route.method, | ||
opts: route.opts.constraints || undefined, | ||
meta: opts.includeMeta ? buildMetaObject.call(this, route, opts.includeMeta) : null | ||
if (metaValue !== undefined && metaValue !== null) { | ||
const serializedValue = JSON.stringify(parseMeta(metaValue)) | ||
filteredMetaData[serializedKey] = serializedValue | ||
} | ||
mergedRouteArray.push({ | ||
path: route.path, | ||
methods: [route.method], | ||
opts: [route.opts], | ||
handlers: [routeHandler] | ||
}) | ||
} | ||
// insert root level path if none defined | ||
if (!mergedRouteArray.filter(r => r.path === pathDelimiter).length) { | ||
const rootPath = { | ||
path: pathDelimiter, | ||
truncatedPath: '', | ||
methods: [], | ||
opts: [], | ||
handlers: [{}] | ||
} | ||
return filteredMetaData | ||
} | ||
// if wildcard route exists, insert root level after wildcard | ||
if (mergedRouteArray.filter(r => r.path === wildcardDelimiter).length) { | ||
mergedRouteArray.splice(1, 0, rootPath) | ||
} else { | ||
mergedRouteArray.unshift(rootPath) | ||
} | ||
function serializeMetaData (metaData) { | ||
let serializedMetaData = '' | ||
for (const [key, value] of Object.entries(metaData)) { | ||
serializedMetaData += `\n• (${key}) ${value}` | ||
} | ||
return serializedMetaData | ||
} | ||
// build tree | ||
const routeTree = buildRouteTree(mergedRouteArray) | ||
// draw tree | ||
routeTree.forEach((rootBranch, idx) => { | ||
tree += drawBranch(rootBranch, null, idx === routeTree.length - 1, false, true) | ||
tree += '\n' // newline characters inserted at beginning of drawing function to allow for nested paths | ||
}) | ||
return tree | ||
// get original merged tree node route | ||
function normalizeRoute (route) { | ||
const constraints = { ...route.opts.constraints } | ||
const method = constraints[httpMethodStrategy.name] | ||
delete constraints[httpMethodStrategy.name] | ||
return { ...route, method, opts: { constraints } } | ||
} | ||
function buildRouteTree (mergedRouteArray) { | ||
const result = [] | ||
const temp = { result } | ||
mergedRouteArray.forEach((route, idx) => { | ||
let splitPath = route.path.split(pathRegExp) | ||
function serializeRoute (route) { | ||
let serializedRoute = ` (${route.method})` | ||
// add preceding slash for proper nesting | ||
if (splitPath[0] !== pathDelimiter) { | ||
// handle wildcard route | ||
if (splitPath[0] !== wildcardDelimiter) splitPath = [pathDelimiter, splitPath[0].slice(1), ...splitPath.slice(1)] | ||
} | ||
const constraints = route.opts.constraints || {} | ||
if (Object.keys(constraints).length !== 0) { | ||
serializedRoute += ' ' + JSON.stringify(constraints) | ||
} | ||
// build tree | ||
splitPath.reduce((acc, path, pidx) => { | ||
if (!acc[path]) { | ||
acc[path] = { result: [] } | ||
const pathSeg = { path, children: acc[path].result } | ||
serializedRoute += serializeMetaData(route.metaData) | ||
return serializedRoute | ||
} | ||
if (pidx === splitPath.length - 1) pathSeg.handlers = route.handlers | ||
acc.result.push(pathSeg) | ||
function mergeSimilarRoutes (routes) { | ||
return routes.reduce((mergedRoutes, route) => { | ||
for (const nodeRoute of mergedRoutes) { | ||
if ( | ||
deepEqual(route.opts.constraints, nodeRoute.opts.constraints) && | ||
deepEqual(route.metaData, nodeRoute.metaData) | ||
) { | ||
nodeRoute.method += ', ' + route.method | ||
return mergedRoutes | ||
} | ||
return acc[path] | ||
}, temp) | ||
}) | ||
// unfold root object from array | ||
return result | ||
} | ||
mergedRoutes.push(route) | ||
return mergedRoutes | ||
}, []) | ||
} | ||
function drawBranch (pathSeg, prefix, endBranch, noPrefix, rootBranch) { | ||
let branch = '' | ||
function serializeNode (node, prefix, options) { | ||
let routes = node.routes | ||
if (!noPrefix && !rootBranch) branch += '\n' | ||
if (!noPrefix) branch += `${prefix || ''}${endBranch ? endBranchIndent : midBranchIndent}` | ||
branch += `${pathSeg.path}` | ||
if (pathSeg.handlers) { | ||
const flatHandlers = pathSeg.handlers.reduce((acc, curr) => { | ||
const match = acc.findIndex(h => JSON.stringify(h.opts) === JSON.stringify(curr.opts)) | ||
if (match !== -1) { | ||
acc[match].method = [acc[match].method, curr.method].join(', ') | ||
} else { | ||
acc.push(curr) | ||
} | ||
return acc | ||
}, []) | ||
flatHandlers.forEach((handler, idx) => { | ||
if (idx > 0) branch += `${noPrefix ? '' : prefix || ''}${endBranch ? indent : branchIndent}${pathSeg.path}` | ||
branch += ` (${handler.method || '-'})` | ||
if (handler.opts && JSON.stringify(handler.opts) !== '{}') branch += ` ${JSON.stringify(handler.opts)}` | ||
if (handler.meta) { | ||
Reflect.ownKeys(handler.meta).forEach((m, hidx) => { | ||
branch += `\n${noPrefix ? '' : prefix || ''}${endBranch ? indent : branchIndent}` | ||
branch += `• (${m}) ${JSON.stringify(handler.meta[m])}` | ||
}) | ||
} | ||
if (flatHandlers.length > 1 && idx !== flatHandlers.length - 1) branch += '\n' | ||
}) | ||
} else { | ||
if (pathSeg.children.length > 1) branch += ' (-)' | ||
if (options.method === undefined) { | ||
routes = routes.map(normalizeRoute) | ||
} | ||
if (!noPrefix) prefix = `${prefix || ''}${endBranch ? indent : branchIndent}` | ||
pathSeg.children.forEach((child, idx) => { | ||
const endBranch = idx === pathSeg.children.length - 1 | ||
const skipPrefix = (!pathSeg.handlers && pathSeg.children.length === 1) | ||
branch += drawBranch(child, prefix, endBranch, skipPrefix) | ||
routes = routes.map(route => { | ||
route.metaData = getRouteMetaData(route, options) | ||
return route | ||
}) | ||
return branch | ||
} | ||
function prettyPrintFlattenedNode (flattenedNode, prefix, tail, opts) { | ||
if (!this.buildPrettyMeta) throw new Error('buildPrettyMeta not defined') | ||
opts.includeMeta = opts.includeMeta || null // array of meta items to display | ||
let paramName = '' | ||
const printHandlers = [] | ||
for (const { node, method } of flattenedNode.nodes) { | ||
for (const handler of node.handlerStorage.handlers) { | ||
printHandlers.push({ method, ...handler }) | ||
} | ||
if (options.method === undefined) { | ||
routes = mergeSimilarRoutes(routes) | ||
} | ||
if (printHandlers.length) { | ||
printHandlers.forEach((handler, index) => { | ||
let suffix = `(${handler.method || '-'})` | ||
if (Object.keys(handler.constraints).length > 0) { | ||
suffix += ' ' + JSON.stringify(handler.constraints) | ||
} | ||
let name = '' | ||
// find locations of parameters in prefix | ||
const paramIndices = flattenedNode.prefix.split('').map((ch, idx) => ch === ':' ? idx : null).filter(idx => idx !== null) | ||
if (paramIndices.length) { | ||
let prevLoc = 0 | ||
paramIndices.forEach((loc, idx) => { | ||
// find parameter in prefix | ||
name += flattenedNode.prefix.slice(prevLoc, loc + 1) | ||
// insert parameters | ||
name += handler.params[handler.params.length - paramIndices.length + idx] | ||
if (idx === paramIndices.length - 1) name += flattenedNode.prefix.slice(loc + 1) | ||
prevLoc = loc + 1 | ||
}) | ||
} else { | ||
// there are no parameters, return full object | ||
name = flattenedNode.prefix | ||
} | ||
if (index === 0) { | ||
paramName += `${name} ${suffix}` | ||
} else { | ||
paramName += `\n${prefix}${tail ? indent : branchIndent}${name} ${suffix}` | ||
} | ||
if (opts.includeMeta) { | ||
const meta = buildMetaObject.call(this, handler, opts.includeMeta) | ||
Object.keys(meta).forEach((m, hidx) => { | ||
paramName += `\n${prefix || ''}${tail ? indent : branchIndent}` | ||
paramName += `• (${m}) ${JSON.stringify(meta[m])}` | ||
}) | ||
} | ||
}) | ||
} else { | ||
paramName = flattenedNode.prefix | ||
} | ||
let tree = `${prefix}${tail ? endBranchIndent : midBranchIndent}${paramName}\n` | ||
prefix = `${prefix}${tail ? indent : branchIndent}` | ||
const labels = Object.keys(flattenedNode.children) | ||
for (let i = 0; i < labels.length; i++) { | ||
const child = flattenedNode.children[labels[i]] | ||
tree += prettyPrintFlattenedNode.call(this, child, prefix, i === (labels.length - 1), opts) | ||
} | ||
return tree | ||
return routes.map(serializeRoute).join(`\n${prefix}`) | ||
} | ||
function flattenNode (flattened, node, method) { | ||
if (node.handlerStorage.handlers.length !== 0) { | ||
flattened.nodes.push({ method, node }) | ||
} | ||
function buildObjectTree (node, tree, prefix, options) { | ||
if (node.isLeafNode || options.commonPrefix !== false) { | ||
prefix = prefix || '(empty root node)' | ||
tree = tree[prefix] = {} | ||
if (node.parametricChildren && node.parametricChildren[0]) { | ||
if (!flattened.children[':']) { | ||
flattened.children[':'] = { | ||
prefix: ':', | ||
nodes: [], | ||
children: {} | ||
} | ||
if (node.isLeafNode) { | ||
tree[treeDataSymbol] = serializeNode(node, prefix, options) | ||
} | ||
flattenNode(flattened.children[':'], node.parametricChildren[0], method) | ||
} | ||
if (node.wildcardChild) { | ||
if (!flattened.children['*']) { | ||
flattened.children['*'] = { | ||
prefix: '*', | ||
nodes: [], | ||
children: {} | ||
} | ||
} | ||
flattenNode(flattened.children['*'], node.wildcardChild, method) | ||
prefix = '' | ||
} | ||
@@ -279,43 +147,24 @@ | ||
for (const child of Object.values(node.staticChildren)) { | ||
// split on the slash separator but use a regex to lookahead and not actually match it, preserving it in the returned string segments | ||
const childPrefixSegments = child.prefix.split(pathRegExp) | ||
let cursor = flattened | ||
let parent | ||
for (const segment of childPrefixSegments) { | ||
parent = cursor | ||
cursor = cursor.children[segment] | ||
if (!cursor) { | ||
cursor = { | ||
prefix: segment, | ||
nodes: [], | ||
children: {} | ||
} | ||
parent.children[segment] = cursor | ||
} | ||
} | ||
flattenNode(cursor, child, method) | ||
buildObjectTree(child, tree, prefix + child.prefix, options) | ||
} | ||
} | ||
} | ||
function compressFlattenedNode (flattenedNode) { | ||
const childKeys = Object.keys(flattenedNode.children) | ||
if (flattenedNode.nodes.length === 0 && childKeys.length === 1) { | ||
const child = flattenedNode.children[childKeys[0]] | ||
if (child.nodes.length <= 1) { | ||
compressFlattenedNode(child) | ||
flattenedNode.nodes = child.nodes | ||
flattenedNode.prefix += child.prefix | ||
flattenedNode.children = child.children | ||
return flattenedNode | ||
if (node.parametricChildren) { | ||
for (const child of Object.values(node.parametricChildren)) { | ||
const childPrefix = Array.from(child.nodePaths).join('|') | ||
buildObjectTree(child, tree, prefix + childPrefix, options) | ||
} | ||
} | ||
for (const key of Object.keys(flattenedNode.children)) { | ||
compressFlattenedNode(flattenedNode.children[key]) | ||
if (node.wildcardChild) { | ||
buildObjectTree(node.wildcardChild, tree, '*', options) | ||
} | ||
} | ||
return flattenedNode | ||
function prettyPrintTree (root, options) { | ||
const objectTree = {} | ||
buildObjectTree(root, objectTree, root.prefix, options) | ||
return printObjectTree(objectTree) | ||
} | ||
module.exports = { flattenNode, compressFlattenedNode, prettyPrintFlattenedNode, prettyPrintRoutesArray } | ||
module.exports = { prettyPrintTree } |
{ | ||
"name": "find-my-way", | ||
"version": "7.5.0", | ||
"version": "7.6.0", | ||
"description": "Crazy fast http radix based router", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -27,3 +27,3 @@ # find-my-way | ||
- [find(method, path, [constraints])](#findmethod-path-constraints) | ||
- [prettyPrint([{ commonPrefix: false, includeMeta: true || [] }])](#prettyprint-commonprefix-false-includemeta-true---) | ||
- [prettyPrint([{ method: 'GET', commonPrefix: false, includeMeta: true || [] }])](#prettyprint-commonprefix-false-includemeta-true---) | ||
- [reset()](#reset) | ||
@@ -438,3 +438,5 @@ - [routes](#routes) | ||
#### prettyPrint([{ commonPrefix: false, includeMeta: true || [] }]) | ||
Prints the representation of the internal radix tree, useful for debugging. | ||
`find-my-way` builds a tree of routes for each HTTP method. If you call the `prettyPrint` | ||
without specifying an HTTP method, it will merge all the trees to one and print it. | ||
The merged tree does't represent the internal router structure. Don't use it for debugging. | ||
@@ -453,19 +455,43 @@ ```js | ||
// │ └── ing (GET) | ||
// │ └── /:param (GET) | ||
// │ └── / | ||
// │ └── :param (GET) | ||
// └── update (PUT) | ||
``` | ||
`prettyPrint` accepts an optional setting to use the internal routes array | ||
to render the tree. | ||
If you want to print the internal tree, you can specify the `method` param. | ||
Printed tree will represent the internal router structure. Use it for debugging. | ||
```js | ||
console.log(findMyWay.prettyPrint({ commonPrefix: false })) | ||
// └── / (-) | ||
// ├── test (GET) | ||
// │ └── /hello (GET) | ||
// ├── testing (GET) | ||
// │ └── /:param (GET) | ||
findMyWay.on('GET', '/test', () => {}) | ||
findMyWay.on('GET', '/test/hello', () => {}) | ||
findMyWay.on('GET', '/testing', () => {}) | ||
findMyWay.on('GET', '/testing/:param', () => {}) | ||
findMyWay.on('PUT', '/update', () => {}) | ||
console.log(findMyWay.prettyPrint({ method: 'GET' })) | ||
// └── / | ||
// └── test (GET) | ||
// ├── /hello (GET) | ||
// └── ing (GET) | ||
// └── / | ||
// └── :param (GET) | ||
console.log(findMyWay.prettyPrint({ method: 'PUT' })) | ||
// └── / | ||
// └── update (PUT) | ||
``` | ||
`prettyPrint` accepts an optional setting to print compressed routes. This is useful | ||
when you have a large number of routes with common prefixes. Doesn't represent the | ||
internal router structure. **Don't use it for debugging.** | ||
```js | ||
console.log(findMyWay.prettyPrint({ commonPrefix: false })) | ||
// ├── /test (GET) | ||
// │ ├── /hello (GET) | ||
// │ └── ing (GET) | ||
// │ └── /:param (GET) | ||
// └── /update (PUT) | ||
``` | ||
To include a display of the `store` data passed to individual routes, the | ||
@@ -479,3 +505,3 @@ option `includeMeta` may be passed. If set to `true` all items will be | ||
```js | ||
findMyWay.on('GET', '/test', () => {}, { onRequest: () => {}, authIDs => [1,2,3] }) | ||
findMyWay.on('GET', '/test', () => {}, { onRequest: () => {}, authIDs: [1, 2, 3] }) | ||
findMyWay.on('GET', '/test/hello', () => {}, { token: 'df123-4567' }) | ||
@@ -487,20 +513,18 @@ findMyWay.on('GET', '/testing', () => {}) | ||
console.log(findMyWay.prettyPrint({ commonPrefix: false, includeMeta: ['onRequest'] })) | ||
// └── / | ||
// ├── test (GET) | ||
// │ • (onRequest) "anonymous()" | ||
// │ ├── /hello (GET) | ||
// │ └── ing (GET) | ||
// │ └── /:param (GET) | ||
// └── update (PUT) | ||
// ├── /test (GET) | ||
// │ • (onRequest) "onRequest()" | ||
// │ ├── /hello (GET) | ||
// │ └── ing (GET) | ||
// │ └── /:param (GET) | ||
// └── /update (PUT) | ||
console.log(findMyWay.prettyPrint({ commonPrefix: true, includeMeta: true })) | ||
// └── / (-) | ||
// ├── test (GET) | ||
// │ • (onRequest) "anonymous()" | ||
// │ • (authIDs) [1,2,3] | ||
// │ └── /hello (GET) | ||
// │ • (token) "df123-4567" | ||
// ├── testing (GET) | ||
// │ └── /:param (GET) | ||
// └── update (PUT) | ||
console.log(findMyWay.prettyPrint({ commonPrefix: false, includeMeta: true })) | ||
// ├── /test (GET) | ||
// │ • (onRequest) "onRequest()" | ||
// │ • (authIDs) [1,2,3] | ||
// │ ├── /hello (GET) | ||
// │ │ • (token) "df123-4567" | ||
// │ └── ing (GET) | ||
// │ └── /:param (GET) | ||
// └── /update (PUT) | ||
``` | ||
@@ -507,0 +531,0 @@ |
@@ -16,4 +16,4 @@ 'use strict' | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `└── / | ||
const expected = `\ | ||
└── / | ||
├── test (GET) | ||
@@ -23,3 +23,2 @@ │ └── /hello (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
@@ -38,9 +37,38 @@ t.equal(tree, expected) | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `└── / | ||
const expected = `\ | ||
└── / | ||
├── test (GET) | ||
│ └── /:hello (GET) | ||
└── hello/:world (GET) | ||
│ └── / | ||
│ └── :hello (GET) | ||
└── hello/ | ||
└── :world (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
t.equal(tree, expected) | ||
}) | ||
test('pretty print - parametric routes', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.on('GET', '/static', () => {}) | ||
findMyWay.on('GET', '/static/:param/suffix1', () => {}) | ||
findMyWay.on('GET', '/static/:param(123)/suffix2', () => {}) | ||
findMyWay.on('GET', '/static/:param(123).end/suffix3', () => {}) | ||
findMyWay.on('GET', '/static/:param1(123).:param2(456)/suffix4', () => {}) | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `\ | ||
└── / | ||
└── static (GET) | ||
└── / | ||
├── :param(123).end | ||
│ └── /suffix3 (GET) | ||
├── :param(123) | ||
│ └── /suffix2 (GET) | ||
├── :param1(123).:param2(456) | ||
│ └── /suffix4 (GET) | ||
└── :param | ||
└── /suffix1 (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
@@ -50,2 +78,24 @@ t.equal(tree, expected) | ||
test('pretty print - parametric routes', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.on('GET', '/static', () => {}) | ||
findMyWay.on('GET', '/static/:param/suffix1', () => {}) | ||
findMyWay.on('GET', '/static/:param(123)/suffix2', () => {}) | ||
findMyWay.on('GET', '/static/:param(123).end/suffix3', () => {}) | ||
findMyWay.on('GET', '/static/:param1(123).:param2(456)/suffix4', () => {}) | ||
const tree = findMyWay.prettyPrint({ commonPrefix: false }) | ||
const expected = `\ | ||
└── /static (GET) | ||
├── /:param(123).end/suffix3 (GET) | ||
├── /:param(123)/suffix2 (GET) | ||
├── /:param1(123).:param2(456)/suffix4 (GET) | ||
└── /:param/suffix1 (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
t.equal(tree, expected) | ||
}) | ||
test('pretty print - mixed parametric routes', t => { | ||
@@ -61,10 +111,9 @@ t.plan(2) | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `└── /test (GET) | ||
└── / | ||
└── :hello (GET) | ||
:hello (POST) | ||
└── /world (GET) | ||
const expected = `\ | ||
└── / | ||
└── test (GET) | ||
└── / | ||
└── :hello (GET, POST) | ||
└── /world (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
@@ -83,9 +132,10 @@ t.equal(tree, expected) | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `└── / | ||
const expected = `\ | ||
└── / | ||
├── test (GET) | ||
│ └── /* (GET) | ||
└── hello/* (GET) | ||
│ └── / | ||
│ └── * (GET) | ||
└── hello/ | ||
└── * (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
@@ -105,11 +155,10 @@ t.equal(tree, expected) | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `└── /test (GET) | ||
└── /hello | ||
├── / | ||
│ └── :id (GET) | ||
│ :id (POST) | ||
└── world (GET) | ||
const expected = `\ | ||
└── / | ||
└── test (GET) | ||
└── /hello | ||
├── / | ||
│ └── :id (GET, POST) | ||
└── world (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
@@ -130,10 +179,32 @@ t.equal(tree, expected) | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `\ | ||
└── / | ||
└── test (GET) | ||
test (GET) {"host":"auth.fastify.io"} | ||
└── / | ||
└── :hello (GET) | ||
:hello (GET) {"version":"1.1.2"} | ||
:hello (GET) {"version":"2.0.0"} | ||
` | ||
t.equal(typeof tree, 'string') | ||
t.equal(tree, expected) | ||
}) | ||
const expected = `└── /test (GET) | ||
/test (GET) {"host":"auth.fastify.io"} | ||
└── /:hello (GET) | ||
/:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
test('pretty print - multiple parameters are drawn appropriately', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.on('GET', '/test', () => {}) | ||
// routes with a nested parameter (i.e. no handler for the /:param) were breaking the display | ||
findMyWay.on('GET', '/test/:hello/there/:ladies', () => {}) | ||
findMyWay.on('GET', '/test/:hello/there/:ladies/and/:gents', () => {}) | ||
findMyWay.on('GET', '/test/are/:you/:ready/to/:rock', () => {}) | ||
const tree = findMyWay.prettyPrint({ commonPrefix: false }) | ||
const expected = `\ | ||
└── /test (GET) | ||
├── /are/:you/:ready/to/:rock (GET) | ||
└── /:hello/there/:ladies (GET) | ||
└── /and/:gents (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
@@ -153,11 +224,9 @@ t.equal(tree, expected) | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `└── /test (GET) | ||
└── / | ||
├── :hello/there/:ladies (GET) | ||
│ └── /and/:gents (GET) | ||
└── are/:you/:ready/to/:rock (GET) | ||
const tree = findMyWay.prettyPrint({ commonPrefix: false }) | ||
const expected = `\ | ||
└── /test (GET) | ||
├── /are/:you/:ready/to/:rock (GET) | ||
└── /:hello/there/:ladies (GET) | ||
└── /and/:gents (GET) | ||
` | ||
t.equal(typeof tree, 'string') | ||
@@ -181,21 +250,24 @@ t.equal(tree, expected) | ||
const radixExpected = `└── / | ||
const radixExpected = `\ | ||
└── / | ||
├── test (GET) | ||
│ ├── /hello (GET) | ||
│ └── ing (GET) | ||
│ └── /:param (GET) | ||
│ └── / | ||
│ └── :param (GET) | ||
└── update (PUT) | ||
` | ||
const arrayExpected = `└── / (-) | ||
├── test (GET) | ||
│ └── /hello (GET) | ||
├── testing (GET) | ||
│ └── /:param (GET) | ||
└── update (PUT) | ||
const arrayExpected = `\ | ||
├── /test (GET) | ||
│ ├── /hello (GET) | ||
│ └── ing (GET) | ||
│ └── /:param (GET) | ||
└── /update (PUT) | ||
` | ||
t.equal(typeof radixTree, 'string') | ||
t.equal(radixTree, radixExpected) | ||
t.equal(typeof arrayTree, 'string') | ||
t.equal(radixTree, radixExpected) | ||
t.equal(arrayTree, arrayExpected) | ||
@@ -216,10 +288,9 @@ }) | ||
const arrayTree = findMyWay.prettyPrint({ commonPrefix: false }) | ||
const arrayExpected = `├── * (OPTIONS) | ||
└── / (-) | ||
├── test/hello (GET) | ||
├── testing (GET) | ||
│ └── /:param (GET) | ||
└── update (PUT) | ||
const arrayExpected = `\ | ||
├── /test/hello (GET) | ||
├── /testing (GET) | ||
│ └── /:param (GET) | ||
├── /update (PUT) | ||
└── * (OPTIONS) | ||
` | ||
t.equal(typeof arrayTree, 'string') | ||
@@ -229,2 +300,29 @@ t.equal(arrayTree, arrayExpected) | ||
test('pretty print commonPrefix - handle wildcard root', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.on('GET', '*', () => {}) | ||
findMyWay.on('GET', '/test/hello', () => {}) | ||
findMyWay.on('GET', '/testing', () => {}) | ||
findMyWay.on('GET', '/testing/:param', () => {}) | ||
findMyWay.on('PUT', '/update', () => {}) | ||
const radixTree = findMyWay.prettyPrint() | ||
const radixExpected = `\ | ||
└── (empty root node) | ||
├── / | ||
│ ├── test | ||
│ │ ├── /hello (GET) | ||
│ │ └── ing (GET) | ||
│ │ └── / | ||
│ │ └── :param (GET) | ||
│ └── update (PUT) | ||
└── * (GET) | ||
` | ||
t.equal(typeof radixTree, 'string') | ||
t.equal(radixTree, radixExpected) | ||
}) | ||
test('pretty print commonPrefix - handle constrained routes', t => { | ||
@@ -243,8 +341,8 @@ t.plan(2) | ||
const arrayTree = findMyWay.prettyPrint({ commonPrefix: false }) | ||
const arrayExpected = `└── / (-) | ||
└── test (GET) | ||
test (GET) {"host":"auth.fastify.io"} | ||
└── /:hello (GET, PUT) | ||
/:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
const arrayExpected = `\ | ||
└── /test (GET) | ||
/test (GET) {"host":"auth.fastify.io"} | ||
└── /:hello (GET, PUT) | ||
/:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
` | ||
@@ -255,2 +353,42 @@ t.equal(typeof arrayTree, 'string') | ||
test('pretty print commonPrefix - handle method constraint', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.addConstraintStrategy({ | ||
name: 'method', | ||
storage: function () { | ||
const handlers = {} | ||
return { | ||
get: (type) => { return handlers[type] || null }, | ||
set: (type, store) => { handlers[type] = store } | ||
} | ||
}, | ||
deriveConstraint: (req) => req.headers['x-method'], | ||
mustMatchWhenDerived: true | ||
}) | ||
findMyWay.on('GET', '/test', () => {}) | ||
findMyWay.on('GET', '/test', { constraints: { method: 'foo' } }, () => {}) | ||
findMyWay.on('GET', '/test/:hello', () => {}) | ||
findMyWay.on('PUT', '/test/:hello', () => {}) | ||
findMyWay.on('GET', '/test/:hello', { constraints: { method: 'bar' } }, () => {}) | ||
findMyWay.on('GET', '/test/:hello', { constraints: { method: 'baz' } }, () => {}) | ||
const arrayTree = findMyWay.prettyPrint({ | ||
commonPrefix: false, | ||
methodConstraintName: 'methodOverride' | ||
}) | ||
const arrayExpected = `\ | ||
└── /test (GET) | ||
/test (GET) {"method":"foo"} | ||
└── /:hello (GET, PUT) | ||
/:hello (GET) {"method":"bar"} | ||
/:hello (GET) {"method":"baz"} | ||
` | ||
t.equal(typeof arrayTree, 'string') | ||
t.equal(arrayTree, arrayExpected) | ||
}) | ||
test('pretty print includeMeta - commonPrefix: true', t => { | ||
@@ -280,30 +418,5 @@ t.plan(6) | ||
const radixTree = findMyWay.prettyPrint({ commonPrefix: true, includeMeta: true }) | ||
const radixTreeExpected = `└── / | ||
├── test (GET) | ||
│ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (genericMeta) "meta" | ||
│ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
│ • (functionMeta) "namedFunction()" | ||
│ • (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
│ test (GET) {"host":"auth.fastify.io"} | ||
│ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (genericMeta) "meta" | ||
│ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
│ • (functionMeta) "namedFunction()" | ||
│ • (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
│ ├── ing/:hello (GET) | ||
│ │ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ │ • (onTimeout) ["anonymous()"] | ||
│ │ • (genericMeta) "meta" | ||
│ │ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
│ │ • (objectMeta) {"one":"1","two":2} | ||
│ │ • (functionMeta) "namedFunction()" | ||
│ │ • (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
│ └── /:hello (GET) {"version":"1.1.2"} | ||
│ /:hello (GET) {"version":"2.0.0"} | ||
└── tested/:hello (PUT) | ||
const radixTreeExpected = `\ | ||
└── / | ||
└── test (GET) | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
@@ -316,31 +429,66 @@ • (onTimeout) ["anonymous()"] | ||
• (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
test (GET) {"host":"auth.fastify.io"} | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (onTimeout) ["anonymous()"] | ||
• (genericMeta) "meta" | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
• (objectMeta) {"one":"1","two":2} | ||
• (functionMeta) "namedFunction()" | ||
• (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
├── ing/ | ||
│ └── :hello (GET) | ||
│ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (genericMeta) "meta" | ||
│ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
│ • (functionMeta) "namedFunction()" | ||
│ • (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
├── ed/ | ||
│ └── :hello (PUT) | ||
│ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (genericMeta) "meta" | ||
│ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
│ • (functionMeta) "namedFunction()" | ||
│ • (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
└── / | ||
└── :hello (GET) {"version":"1.1.2"} | ||
:hello (GET) {"version":"2.0.0"} | ||
` | ||
const radixTreeSpecific = findMyWay.prettyPrint({ commonPrefix: true, includeMeta: ['onTimeout', 'objectMeta', 'nonExistent'] }) | ||
const radixTreeSpecificExpected = `└── / | ||
├── test (GET) | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
│ test (GET) {"host":"auth.fastify.io"} | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
│ ├── ing/:hello (GET) | ||
│ │ • (onTimeout) ["anonymous()"] | ||
│ │ • (objectMeta) {"one":"1","two":2} | ||
│ └── /:hello (GET) {"version":"1.1.2"} | ||
│ /:hello (GET) {"version":"2.0.0"} | ||
└── tested/:hello (PUT) | ||
const radixTreeSpecificExpected = `\ | ||
└── / | ||
└── test (GET) | ||
• (onTimeout) ["anonymous()"] | ||
• (objectMeta) {"one":"1","two":2} | ||
test (GET) {"host":"auth.fastify.io"} | ||
• (onTimeout) ["anonymous()"] | ||
• (objectMeta) {"one":"1","two":2} | ||
├── ing/ | ||
│ └── :hello (GET) | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
├── ed/ | ||
│ └── :hello (PUT) | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
└── / | ||
└── :hello (GET) {"version":"1.1.2"} | ||
:hello (GET) {"version":"2.0.0"} | ||
` | ||
const radixTreeNoMeta = findMyWay.prettyPrint({ commonPrefix: true, includeMeta: false }) | ||
const radixTreeNoMetaExpected = `└── / | ||
├── test (GET) | ||
│ test (GET) {"host":"auth.fastify.io"} | ||
│ ├── ing/:hello (GET) | ||
│ └── /:hello (GET) {"version":"1.1.2"} | ||
│ /:hello (GET) {"version":"2.0.0"} | ||
└── tested/:hello (PUT) | ||
const radixTreeNoMetaExpected = `\ | ||
└── / | ||
└── test (GET) | ||
test (GET) {"host":"auth.fastify.io"} | ||
├── ing/ | ||
│ └── :hello (GET) | ||
├── ed/ | ||
│ └── :hello (PUT) | ||
└── / | ||
└── :hello (GET) {"version":"1.1.2"} | ||
:hello (GET) {"version":"2.0.0"} | ||
` | ||
t.equal(typeof radixTree, 'string') | ||
@@ -364,2 +512,4 @@ t.equal(radixTree, radixTreeExpected) | ||
onTimeout: [() => {}], | ||
onError: null, | ||
onRegister: undefined, | ||
genericMeta: 'meta', | ||
@@ -375,4 +525,4 @@ mixedMeta: ['mixed items', { an: 'object' }], | ||
findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}, store) | ||
findMyWay.on('GET', '/test/:hello', () => {}, store) | ||
findMyWay.on('PUT', '/test/:hello', () => {}, store) | ||
findMyWay.on('GET', '/testing/:hello', () => {}, store) | ||
findMyWay.on('PUT', '/tested/:hello', () => {}, store) | ||
findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) | ||
@@ -382,52 +532,63 @@ findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) | ||
const arrayTree = findMyWay.prettyPrint({ commonPrefix: false, includeMeta: true }) | ||
const arrayExpected = `└── / (-) | ||
└── test (GET) | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (onTimeout) ["anonymous()"] | ||
• (genericMeta) "meta" | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
• (objectMeta) {"one":"1","two":2} | ||
• (functionMeta) "namedFunction()" | ||
• (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
test (GET) {"host":"auth.fastify.io"} | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (onTimeout) ["anonymous()"] | ||
• (genericMeta) "meta" | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
• (objectMeta) {"one":"1","two":2} | ||
• (functionMeta) "namedFunction()" | ||
• (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
└── /:hello (GET, PUT) | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (onTimeout) ["anonymous()"] | ||
• (genericMeta) "meta" | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
• (objectMeta) {"one":"1","two":2} | ||
• (functionMeta) "namedFunction()" | ||
• (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
/:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
const arrayExpected = `\ | ||
└── /test (GET) | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (onTimeout) ["anonymous()"] | ||
• (genericMeta) "meta" | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
• (objectMeta) {"one":"1","two":2} | ||
• (functionMeta) "namedFunction()" | ||
• (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
/test (GET) {"host":"auth.fastify.io"} | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (onTimeout) ["anonymous()"] | ||
• (genericMeta) "meta" | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
• (objectMeta) {"one":"1","two":2} | ||
• (functionMeta) "namedFunction()" | ||
• (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
├── ing/:hello (GET) | ||
│ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (genericMeta) "meta" | ||
│ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
│ • (functionMeta) "namedFunction()" | ||
│ • (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
├── ed/:hello (PUT) | ||
│ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ • (onTimeout) ["anonymous()"] | ||
│ • (genericMeta) "meta" | ||
│ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
│ • (objectMeta) {"one":"1","two":2} | ||
│ • (functionMeta) "namedFunction()" | ||
│ • (Symbol(symbolKey)) "Symbol(symbolValue)" | ||
└── /:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
` | ||
const arraySpecific = findMyWay.prettyPrint({ commonPrefix: false, includeMeta: ['onRequest', 'mixedMeta', 'nonExistent'] }) | ||
const arraySpecificExpected = `└── / (-) | ||
└── test (GET) | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
test (GET) {"host":"auth.fastify.io"} | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
└── /:hello (GET, PUT) | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
/:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
const arraySpecificExpected = `\ | ||
└── /test (GET) | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
/test (GET) {"host":"auth.fastify.io"} | ||
• (onRequest) ["anonymous()","namedFunction()"] | ||
• (mixedMeta) ["mixed items",{"an":"object"}] | ||
├── ing/:hello (GET) | ||
│ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
├── ed/:hello (PUT) | ||
│ • (onRequest) ["anonymous()","namedFunction()"] | ||
│ • (mixedMeta) ["mixed items",{"an":"object"}] | ||
└── /:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
` | ||
const arrayNoMeta = findMyWay.prettyPrint({ commonPrefix: false, includeMeta: false }) | ||
const arrayNoMetaExpected = `└── / (-) | ||
└── test (GET) | ||
test (GET) {"host":"auth.fastify.io"} | ||
└── /:hello (GET, PUT) | ||
/:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
const arrayNoMetaExpected = `\ | ||
└── /test (GET) | ||
/test (GET) {"host":"auth.fastify.io"} | ||
├── ing/:hello (GET) | ||
├── ed/:hello (PUT) | ||
└── /:hello (GET) {"version":"1.1.2"} | ||
/:hello (GET) {"version":"2.0.0"} | ||
` | ||
@@ -450,5 +611,3 @@ | ||
buildPrettyMeta: route => { | ||
// routes from radix tree do not contain a path element | ||
// returns 'no path' to avoid an undefined value | ||
return { metaKey: route.path || 'no path' } | ||
return { metaKey: route.method === 'PUT' ? 'Hide PUT route path' : route.path } | ||
} | ||
@@ -472,2 +631,3 @@ }) | ||
findMyWay.on('PUT', '/test/:hello', () => {}, store) | ||
findMyWay.on('POST', '/test/:hello', () => {}, store) | ||
findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) | ||
@@ -477,3 +637,19 @@ findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) | ||
const arrayTree = findMyWay.prettyPrint({ commonPrefix: false, includeMeta: true }) | ||
const arrayExpected = `└── / (-) | ||
const arrayExpected = `\ | ||
└── /test (GET) | ||
• (metaKey) "/test" | ||
/test (GET) {"host":"auth.fastify.io"} | ||
• (metaKey) "/test" | ||
└── /:hello (GET, POST) | ||
• (metaKey) "/test/:hello" | ||
/:hello (PUT) | ||
• (metaKey) "Hide PUT route path" | ||
/:hello (GET) {"version":"1.1.2"} | ||
• (metaKey) "/test/:hello" | ||
/:hello (GET) {"version":"2.0.0"} | ||
• (metaKey) "/test/:hello" | ||
` | ||
const radixTree = findMyWay.prettyPrint({ includeMeta: true }) | ||
const radixExpected = `\ | ||
└── / | ||
└── test (GET) | ||
@@ -483,24 +659,12 @@ • (metaKey) "/test" | ||
• (metaKey) "/test" | ||
└── /:hello (GET, PUT) | ||
• (metaKey) "/test/:hello" | ||
/:hello (GET) {"version":"1.1.2"} | ||
• (metaKey) "/test/:hello" | ||
/:hello (GET) {"version":"2.0.0"} | ||
• (metaKey) "/test/:hello" | ||
└── / | ||
└── :hello (GET, POST) | ||
• (metaKey) "/test/:hello" | ||
:hello (PUT) | ||
• (metaKey) "Hide PUT route path" | ||
:hello (GET) {"version":"1.1.2"} | ||
• (metaKey) "/test/:hello" | ||
:hello (GET) {"version":"2.0.0"} | ||
• (metaKey) "/test/:hello" | ||
` | ||
const radixTree = findMyWay.prettyPrint({ includeMeta: true }) | ||
const radixExpected = `└── /test (GET) | ||
• (metaKey) "no path" | ||
/test (GET) {"host":"auth.fastify.io"} | ||
• (metaKey) "no path" | ||
└── / | ||
└── :hello (GET) | ||
• (metaKey) "no path" | ||
:hello (GET) {"version":"1.1.2"} | ||
• (metaKey) "no path" | ||
:hello (GET) {"version":"2.0.0"} | ||
• (metaKey) "no path" | ||
:hello (PUT) | ||
• (metaKey) "no path" | ||
` | ||
t.equal(typeof arrayTree, 'string') | ||
@@ -512,1 +676,19 @@ t.equal(arrayTree, arrayExpected) | ||
}) | ||
test('pretty print - print all methods', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay() | ||
findMyWay.all('/test', () => {}) | ||
const tree = findMyWay.prettyPrint() | ||
const expected = `\ | ||
└── / | ||
└── test (ACL, BIND, CHECKOUT, CONNECT, COPY, DELETE, GET, HEAD, LINK, LOCK, \ | ||
M-SEARCH, MERGE, MKACTIVITY, MKCALENDAR, MKCOL, MOVE, NOTIFY, OPTIONS, PATCH, \ | ||
POST, PROPFIND, PROPPATCH, PURGE, PUT, REBIND, REPORT, SEARCH, SOURCE, SUBSCRIBE, \ | ||
TRACE, UNBIND, UNLINK, UNLOCK, UNSUBSCRIBE) | ||
` | ||
t.equal(typeof tree, 'string') | ||
t.equal(tree, expected) | ||
}) |
@@ -24,3 +24,3 @@ 'use strict' | ||
findMyWay.routes.map((route, idx) => { | ||
t.same(route, { | ||
t.match(route, { | ||
method: 'GET', | ||
@@ -27,0 +27,0 @@ path: '/test-route-' + idx, |
@@ -60,2 +60,3 @@ import { expectType } from 'tsd' | ||
expectType<string>(router.prettyPrint()) | ||
expectType<string>(router.prettyPrint({ method: 'GET' })) | ||
expectType<string>(router.prettyPrint({ commonPrefix: false })) | ||
@@ -62,0 +63,0 @@ expectType<string>(router.prettyPrint({ commonPrefix: true })) |
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
352922
88
8827
764
7