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

'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