Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use internal trees for prettyPrint #321

Merged
merged 5 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 54 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Do you need a real-world example that uses this router? Check out [Fastify](http
- [off(methods, path, [constraints])](#offmethods-path-constraints)
- [lookup(request, response, [context], [done])](#lookuprequest-response-context)
- [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)
- [routes](#routes)
- [Caveats](#caveats)
Expand Down Expand Up @@ -434,7 +434,9 @@ router.find('GET', '/example', { host: 'fastify.io', version: '1.x' })

<a name="pretty-print"></a>
#### 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.

```js
findMyWay.on('GET', '/test', () => {})
Expand All @@ -448,23 +450,47 @@ console.log(findMyWay.prettyPrint())
// ├── test (GET)
// │ ├── /hello (GET)
// │ └── 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
option `includeMeta` may be passed. If set to `true` all items will be
displayed, this can also be set to an array specifying which keys (if
Expand All @@ -473,31 +499,29 @@ by specifying a `buildPrettyMeta` function which consumes and returns
an object.

```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' })
findMyWay.on('GET', '/testing', () => {})
findMyWay.on('GET', '/testing/:param', () => {})
findMyWay.on('PUT', '/update', () => {})

console.log(findMyWay.prettyPrint({ commonPrefix: false, includeMeta: ['onRequest'] }))
// └── /
// ├── test (GET)
// │ • (onRequest) "anonymous()"
// │ ├── /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)
// ├── /test (GET)
// │ • (onRequest) "onRequest()"
// │ ├── /hello (GET)
// │ └── ing (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)
```

<a name="reset"></a>
Expand Down
25 changes: 21 additions & 4 deletions custom_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,21 @@ const NODE_TYPES = {

class Node {
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)
}
}

Expand Down Expand Up @@ -61,7 +75,7 @@ class StaticNode extends ParentNode {
this._compilePrefixMatch()
}

createParametricChild (regex, staticSuffix) {
createParametricChild (regex, staticSuffix, nodePath) {
const regexpSource = regex && regex.source

let parametricChild = this.parametricChildren.find(child => {
Expand All @@ -70,10 +84,11 @@ class StaticNode extends ParentNode {
})

if (parametricChild) {
parametricChild.nodePaths.add(nodePath)
return parametricChild
}

parametricChild = new ParametricNode(regex, staticSuffix)
parametricChild = new ParametricNode(regex, staticSuffix, nodePath)
this.parametricChildren.push(parametricChild)
this.parametricChildren.sort((child1, child2) => {
if (!child1.isRegex) return 1
Expand Down Expand Up @@ -162,12 +177,14 @@ class StaticNode extends ParentNode {
}

class ParametricNode extends ParentNode {
constructor (regex, staticSuffix) {
constructor (regex, staticSuffix, nodePath) {
super()
this.isRegex = !!regex
this.regex = regex || null
this.staticSuffix = staticSuffix || null
this.kind = NODE_TYPES.PARAMETRIC

this.nodePaths = new Set([nodePath])
}

getNextNode (path, pathIndex) {
Expand Down
23 changes: 16 additions & 7 deletions handler_storage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'

const httpMethodStrategy = require('./lib/strategies/http-method')

class HandlerStorage {
constructor () {
this.unconstrainedHandler = null // optimized reference to the handler that will match most of the time
Expand All @@ -16,20 +18,24 @@ class HandlerStorage {
return this._getHandlerMatchingConstraints(derivedConstraints)
}

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)) {
if (constraint === 'version') {
// always check the version constraint first as it is the most selective
Expand All @@ -40,15 +46,18 @@ class HandlerStorage {
}
}

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')
}

this.handlers.push(handlerObject)
// Sort the most constrained handlers to the front of the list of handlers so they are tested first.
this.handlers.sort((a, b) => Object.keys(a.constraints).length - Object.keys(b.constraints).length)

this._compileGetHandlerMatchingConstraints(constrainer, constraints)
if (!isMergedTree) {
this._compileGetHandlerMatchingConstraints(constrainer, constraints)
}
}

_compileCreateParamsObject (params) {
Expand Down
6 changes: 5 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ declare namespace Router {

reset(): void;
prettyPrint(): string;
prettyPrint(opts: { commonPrefix?: boolean, includeMeta?: boolean | (string | symbol)[] }): string;
prettyPrint(opts: {
method?: HTTPMethod,
commonPrefix?: boolean,
includeMeta?: boolean | (string | symbol)[]
}): string;

hasConstraintStrategy(strategyName: string): boolean;
addConstraintStrategy(constraintStrategy: ConstraintStrategy<V>): void;
Expand Down
Loading