You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
6.4 KiB
252 lines
6.4 KiB
4 years ago
|
'use strict'
|
||
|
|
||
|
const assert = require('assert')
|
||
|
const http = require('http')
|
||
|
const Handlers = buildHandlers()
|
||
|
|
||
|
const types = {
|
||
|
STATIC: 0,
|
||
|
PARAM: 1,
|
||
|
MATCH_ALL: 2,
|
||
|
REGEX: 3,
|
||
|
// It's used for a parameter, that is followed by another parameter in the same part
|
||
|
MULTI_PARAM: 4
|
||
|
}
|
||
|
|
||
|
function Node (options) {
|
||
|
// former arguments order: prefix, children, kind, handlers, regex, versions
|
||
|
options = options || {}
|
||
|
this.prefix = options.prefix || '/'
|
||
|
this.label = this.prefix[0]
|
||
|
this.children = options.children || {}
|
||
|
this.numberOfChildren = Object.keys(this.children).length
|
||
|
this.kind = options.kind || this.types.STATIC
|
||
|
this.handlers = new Handlers(options.handlers)
|
||
|
this.regex = options.regex || null
|
||
|
this.wildcardChild = null
|
||
|
this.parametricBrother = null
|
||
|
this.versions = options.versions
|
||
|
}
|
||
|
|
||
|
Object.defineProperty(Node.prototype, 'types', {
|
||
|
value: types
|
||
|
})
|
||
|
|
||
|
Node.prototype.getLabel = function () {
|
||
|
return this.prefix[0]
|
||
|
}
|
||
|
|
||
|
Node.prototype.addChild = function (node) {
|
||
|
var label = ''
|
||
|
switch (node.kind) {
|
||
|
case this.types.STATIC:
|
||
|
label = node.getLabel()
|
||
|
break
|
||
|
case this.types.PARAM:
|
||
|
case this.types.REGEX:
|
||
|
case this.types.MULTI_PARAM:
|
||
|
label = ':'
|
||
|
break
|
||
|
case this.types.MATCH_ALL:
|
||
|
this.wildcardChild = node
|
||
|
label = '*'
|
||
|
break
|
||
|
default:
|
||
|
throw new Error(`Unknown node kind: ${node.kind}`)
|
||
|
}
|
||
|
|
||
|
assert(
|
||
|
this.children[label] === undefined,
|
||
|
`There is already a child with label '${label}'`
|
||
|
)
|
||
|
|
||
|
this.children[label] = node
|
||
|
this.numberOfChildren = Object.keys(this.children).length
|
||
|
|
||
|
const labels = Object.keys(this.children)
|
||
|
var parametricBrother = this.parametricBrother
|
||
|
for (var i = 0; i < labels.length; i++) {
|
||
|
const child = this.children[labels[i]]
|
||
|
if (child.label === ':') {
|
||
|
parametricBrother = child
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Save the parametric brother inside static children
|
||
|
const iterate = (node) => {
|
||
|
if (!node) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if (node.kind !== this.types.STATIC) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if (node !== this) {
|
||
|
node.parametricBrother = parametricBrother || node.parametricBrother
|
||
|
}
|
||
|
|
||
|
const labels = Object.keys(node.children)
|
||
|
for (var i = 0; i < labels.length; i++) {
|
||
|
iterate(node.children[labels[i]])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
iterate(this)
|
||
|
|
||
|
return this
|
||
|
}
|
||
|
|
||
|
Node.prototype.reset = function (prefix, versions) {
|
||
|
this.prefix = prefix
|
||
|
this.children = {}
|
||
|
this.kind = this.types.STATIC
|
||
|
this.handlers = new Handlers()
|
||
|
this.numberOfChildren = 0
|
||
|
this.regex = null
|
||
|
this.wildcardChild = null
|
||
|
this.versions = versions
|
||
|
return this
|
||
|
}
|
||
|
|
||
|
Node.prototype.findByLabel = function (path) {
|
||
|
return this.children[path[0]]
|
||
|
}
|
||
|
|
||
|
Node.prototype.findChild = function (path, method) {
|
||
|
var child = this.children[path[0]]
|
||
|
if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) {
|
||
|
if (path.slice(0, child.prefix.length) === child.prefix) {
|
||
|
return child
|
||
|
}
|
||
|
}
|
||
|
|
||
|
child = this.children[':']
|
||
|
if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) {
|
||
|
return child
|
||
|
}
|
||
|
|
||
|
child = this.children['*']
|
||
|
if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) {
|
||
|
return child
|
||
|
}
|
||
|
|
||
|
return null
|
||
|
}
|
||
|
|
||
|
Node.prototype.findVersionChild = function (version, path, method) {
|
||
|
var child = this.children[path[0]]
|
||
|
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) {
|
||
|
if (path.slice(0, child.prefix.length) === child.prefix) {
|
||
|
return child
|
||
|
}
|
||
|
}
|
||
|
|
||
|
child = this.children[':']
|
||
|
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) {
|
||
|
return child
|
||
|
}
|
||
|
|
||
|
child = this.children['*']
|
||
|
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) {
|
||
|
return child
|
||
|
}
|
||
|
|
||
|
return null
|
||
|
}
|
||
|
|
||
|
Node.prototype.setHandler = function (method, handler, params, store) {
|
||
|
if (!handler) return
|
||
|
|
||
|
assert(
|
||
|
this.handlers[method] !== undefined,
|
||
|
`There is already an handler with method '${method}'`
|
||
|
)
|
||
|
|
||
|
this.handlers[method] = {
|
||
|
handler: handler,
|
||
|
params: params,
|
||
|
store: store || null,
|
||
|
paramsLength: params.length
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Node.prototype.setVersionHandler = function (version, method, handler, params, store) {
|
||
|
if (!handler) return
|
||
|
|
||
|
const handlers = this.versions.get(version) || new Handlers()
|
||
|
assert(
|
||
|
handlers[method] === null,
|
||
|
`There is already an handler with version '${version}' and method '${method}'`
|
||
|
)
|
||
|
|
||
|
handlers[method] = {
|
||
|
handler: handler,
|
||
|
params: params,
|
||
|
store: store || null,
|
||
|
paramsLength: params.length
|
||
|
}
|
||
|
this.versions.set(version, handlers)
|
||
|
}
|
||
|
|
||
|
Node.prototype.getHandler = function (method) {
|
||
|
return this.handlers[method]
|
||
|
}
|
||
|
|
||
|
Node.prototype.getVersionHandler = function (version, method) {
|
||
|
var handlers = this.versions.get(version)
|
||
|
return handlers === null ? handlers : handlers[method]
|
||
|
}
|
||
|
|
||
|
Node.prototype.prettyPrint = function (prefix, tail) {
|
||
|
var paramName = ''
|
||
|
var handlers = this.handlers || {}
|
||
|
var methods = Object.keys(handlers).filter(method => handlers[method] && handlers[method].handler)
|
||
|
|
||
|
if (this.prefix === ':') {
|
||
|
methods.forEach((method, index) => {
|
||
|
var params = this.handlers[method].params
|
||
|
var param = params[params.length - 1]
|
||
|
if (methods.length > 1) {
|
||
|
if (index === 0) {
|
||
|
paramName += param + ` (${method})\n`
|
||
|
return
|
||
|
}
|
||
|
paramName += prefix + ' :' + param + ` (${method})`
|
||
|
paramName += (index === methods.length - 1 ? '' : '\n')
|
||
|
} else {
|
||
|
paramName = params[params.length - 1] + ` (${method})`
|
||
|
}
|
||
|
})
|
||
|
} else if (methods.length) {
|
||
|
paramName = ` (${methods.join('|')})`
|
||
|
}
|
||
|
|
||
|
var tree = `${prefix}${tail ? '└── ' : '├── '}${this.prefix}${paramName}\n`
|
||
|
|
||
|
prefix = `${prefix}${tail ? ' ' : '│ '}`
|
||
|
const labels = Object.keys(this.children)
|
||
|
for (var i = 0; i < labels.length - 1; i++) {
|
||
|
tree += this.children[labels[i]].prettyPrint(prefix, false)
|
||
|
}
|
||
|
if (labels.length > 0) {
|
||
|
tree += this.children[labels[labels.length - 1]].prettyPrint(prefix, true)
|
||
|
}
|
||
|
return tree
|
||
|
}
|
||
|
|
||
|
function buildHandlers (handlers) {
|
||
|
var code = `handlers = handlers || {}
|
||
|
`
|
||
|
for (var i = 0; i < http.METHODS.length; i++) {
|
||
|
var m = http.METHODS[i]
|
||
|
code += `this['${m}'] = handlers['${m}'] || null
|
||
|
`
|
||
|
}
|
||
|
return new Function('handlers', code) // eslint-disable-line
|
||
|
}
|
||
|
|
||
|
module.exports = Node
|
||
|
module.exports.Handlers = Handlers
|