UNPKG

@iopa/router

Version:

Lightweight and fast router for IOPA applications

131 lines (112 loc) 3.13 kB
/* eslint-disable @rushstack/typedef-var */ const LABEL_REG_EXP_STR = '[^/]+' const ONLY_WILDCARD_REG_EXP_STR = '.*' const TAIL_WILDCARD_REG_EXP_STR = '(?:|/.*)' export type ParamMap = Array<[string, number]> export interface IRouterContext { varIndex: number } /** * Sort order: * 1. literal * 2. special pattern (e.g. :label{[0-9]+}) * 3. common label pattern (e.g. :label) * 4. wildcard */ function compareKey(a: string, b: string): number { if (a.length === 1) { return b.length === 1 ? (a < b ? -1 : 1) : -1 } if (b.length === 1) { return 1 } // wildcard if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) { return 1 } else if ( b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR ) { return -1 } // label if (a === LABEL_REG_EXP_STR) { return 1 } else if (b === LABEL_REG_EXP_STR) { return -1 } return a.length === b.length ? (a < b ? -1 : 1) : b.length - a.length } export class Node { public index?: number public varIndex?: number public children: Record<string, Node> = {} public reverse: boolean public constructor({ reverse }: Partial<Node> = { reverse: false }) { this.reverse = reverse as boolean } public newChildNode(): Node { return new Node({ reverse: this.reverse }) } public insert( tokens: readonly string[], index: number, paramMap: ParamMap, context: IRouterContext ): void { if (tokens.length === 0) { this.index = index return } const [token, ...restTokens] = tokens const pattern = token === '*' ? restTokens.length === 0 ? ['', '', ONLY_WILDCARD_REG_EXP_STR] // '*' matches to all the trailing paths : ['', '', LABEL_REG_EXP_STR] : token === '/*' ? ['', '', TAIL_WILDCARD_REG_EXP_STR] // '/path/to/*' is /\/path\/to(?:|/.*)$ : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/) let node if (pattern) { const name = pattern[1] const regexpStr = pattern[2] || LABEL_REG_EXP_STR node = this.children[regexpStr] if (!node) { node = this.children[regexpStr] = this.newChildNode() if (name !== '') { node.varIndex = context.varIndex++ } } if (name !== '') { paramMap.push([name, node.varIndex as number]) } } else { node = this.children[token] ||= this.newChildNode() } node.insert(restTokens, index, paramMap, context) } public buildRegExpStr(): string { let childKeys = Object.keys(this.children).sort(compareKey) if (this.reverse) { childKeys = childKeys.reverse() } const strList = childKeys.map((k) => { const c = this.children[k] return ( (typeof c.varIndex === 'number' ? `(${k})@${c.varIndex}` : k) + c.buildRegExpStr() ) }) if (typeof this.index === 'number') { strList.unshift(`#${this.index}`) } if (strList.length === 0) { return '' } if (strList.length === 1) { return strList[0] } return '(?:' + strList.join('|') + ')' } }