2023-10-15 23:39:37 +00:00
|
|
|
import type { Params } from '../../router.ts'
|
2022-07-02 06:09:45 +00:00
|
|
|
import { METHOD_NAME_ALL } from '../../router.ts'
|
|
|
|
import type { Pattern } from '../../utils/url.ts'
|
2023-01-08 09:45:46 +00:00
|
|
|
import { splitPath, splitRoutingPath, getPattern } from '../../utils/url.ts'
|
2022-07-02 06:09:45 +00:00
|
|
|
|
|
|
|
type HandlerSet<T> = {
|
|
|
|
handler: T
|
2023-10-15 23:39:37 +00:00
|
|
|
params: Record<string, string>
|
|
|
|
possibleKeys: string[]
|
2022-07-02 06:09:45 +00:00
|
|
|
score: number
|
|
|
|
name: string // For debug
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Node<T> {
|
|
|
|
methods: Record<string, HandlerSet<T>>[]
|
|
|
|
|
|
|
|
children: Record<string, Node<T>>
|
|
|
|
patterns: Pattern[]
|
|
|
|
order: number = 0
|
|
|
|
name: string
|
2023-10-15 23:39:37 +00:00
|
|
|
params: Record<string, string> = {}
|
2022-07-02 06:09:45 +00:00
|
|
|
|
|
|
|
constructor(method?: string, handler?: T, children?: Record<string, Node<T>>) {
|
|
|
|
this.children = children || {}
|
|
|
|
this.methods = []
|
|
|
|
this.name = ''
|
|
|
|
if (method && handler) {
|
|
|
|
const m: Record<string, HandlerSet<T>> = {}
|
2023-10-15 23:39:37 +00:00
|
|
|
m[method] = { handler, params: {}, possibleKeys: [], score: 0, name: this.name }
|
2022-07-02 06:09:45 +00:00
|
|
|
this.methods = [m]
|
|
|
|
}
|
|
|
|
this.patterns = []
|
|
|
|
}
|
|
|
|
|
|
|
|
insert(method: string, path: string, handler: T): Node<T> {
|
|
|
|
this.name = `${method} ${path}`
|
|
|
|
this.order = ++this.order
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
|
|
let curNode: Node<T> = this
|
2023-01-08 09:45:46 +00:00
|
|
|
const parts = splitRoutingPath(path)
|
2022-07-02 06:09:45 +00:00
|
|
|
|
2023-10-15 23:39:37 +00:00
|
|
|
const possibleKeys: string[] = []
|
2022-07-02 06:09:45 +00:00
|
|
|
const parentPatterns: Pattern[] = []
|
|
|
|
|
|
|
|
for (let i = 0, len = parts.length; i < len; i++) {
|
|
|
|
const p: string = parts[i]
|
|
|
|
|
|
|
|
if (Object.keys(curNode.children).includes(p)) {
|
|
|
|
parentPatterns.push(...curNode.patterns)
|
|
|
|
curNode = curNode.children[p]
|
2023-10-15 23:39:37 +00:00
|
|
|
const pattern = getPattern(p)
|
|
|
|
if (pattern) possibleKeys.push(pattern[1])
|
2022-07-02 06:09:45 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
curNode.children[p] = new Node()
|
|
|
|
|
|
|
|
const pattern = getPattern(p)
|
|
|
|
if (pattern) {
|
|
|
|
curNode.patterns.push(pattern)
|
|
|
|
parentPatterns.push(...curNode.patterns)
|
2023-10-15 23:39:37 +00:00
|
|
|
possibleKeys.push(pattern[1])
|
2022-07-02 06:09:45 +00:00
|
|
|
}
|
|
|
|
parentPatterns.push(...curNode.patterns)
|
|
|
|
curNode = curNode.children[p]
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!curNode.methods.length) {
|
|
|
|
curNode.methods = []
|
|
|
|
}
|
|
|
|
|
|
|
|
const m: Record<string, HandlerSet<T>> = {}
|
|
|
|
|
2023-10-15 23:39:37 +00:00
|
|
|
const handlerSet: HandlerSet<T> = {
|
|
|
|
handler,
|
|
|
|
params: {},
|
|
|
|
possibleKeys,
|
|
|
|
name: this.name,
|
|
|
|
score: this.order,
|
|
|
|
}
|
2022-07-02 06:09:45 +00:00
|
|
|
|
|
|
|
m[method] = handlerSet
|
|
|
|
curNode.methods.push(m)
|
|
|
|
|
|
|
|
return curNode
|
|
|
|
}
|
|
|
|
|
2023-04-30 12:07:00 +00:00
|
|
|
// getHandlerSets
|
2023-10-15 23:39:37 +00:00
|
|
|
private gHSets(node: Node<T>, method: string, params: Record<string, string>): HandlerSet<T>[] {
|
|
|
|
const handlerSets: HandlerSet<T>[] = []
|
|
|
|
for (let i = 0, len = node.methods.length; i < len; i++) {
|
|
|
|
const m = node.methods[i]
|
|
|
|
const handlerSet = m[method] || m[METHOD_NAME_ALL]
|
|
|
|
if (handlerSet !== undefined) {
|
|
|
|
handlerSet.possibleKeys.map((key) => {
|
|
|
|
handlerSet.params[key] = params[key]
|
|
|
|
})
|
|
|
|
handlerSets.push(handlerSet)
|
2022-08-21 01:11:53 +00:00
|
|
|
}
|
2023-10-15 23:39:37 +00:00
|
|
|
}
|
|
|
|
return handlerSets
|
2022-07-02 06:09:45 +00:00
|
|
|
}
|
|
|
|
|
2023-10-15 23:39:37 +00:00
|
|
|
search(method: string, path: string): [[T, Params][]] {
|
2022-07-02 06:09:45 +00:00
|
|
|
const handlerSets: HandlerSet<T>[] = []
|
2023-10-15 23:39:37 +00:00
|
|
|
|
2022-07-02 06:09:45 +00:00
|
|
|
const params: Record<string, string> = {}
|
2023-10-15 23:39:37 +00:00
|
|
|
this.params = {}
|
2022-07-02 06:09:45 +00:00
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
|
|
const curNode: Node<T> = this
|
|
|
|
let curNodes = [curNode]
|
|
|
|
const parts = splitPath(path)
|
|
|
|
|
|
|
|
for (let i = 0, len = parts.length; i < len; i++) {
|
|
|
|
const part: string = parts[i]
|
|
|
|
const isLast = i === len - 1
|
|
|
|
const tempNodes: Node<T>[] = []
|
|
|
|
|
|
|
|
for (let j = 0, len2 = curNodes.length; j < len2; j++) {
|
|
|
|
const node = curNodes[j]
|
2022-07-24 02:03:04 +00:00
|
|
|
const nextNode = node.children[part]
|
|
|
|
|
|
|
|
if (nextNode) {
|
|
|
|
if (isLast === true) {
|
|
|
|
// '/hello/*' => match '/hello'
|
|
|
|
if (nextNode.children['*']) {
|
2023-10-15 23:39:37 +00:00
|
|
|
handlerSets.push(
|
|
|
|
...this.gHSets(nextNode.children['*'], method, { ...params, ...node.params })
|
|
|
|
)
|
2022-07-24 02:03:04 +00:00
|
|
|
}
|
2023-10-15 23:39:37 +00:00
|
|
|
handlerSets.push(...this.gHSets(nextNode, method, { ...params, ...node.params }))
|
2022-08-21 01:11:53 +00:00
|
|
|
} else {
|
|
|
|
tempNodes.push(nextNode)
|
2022-07-24 02:03:04 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-02 06:09:45 +00:00
|
|
|
|
|
|
|
for (let k = 0, len3 = node.patterns.length; k < len3; k++) {
|
|
|
|
const pattern = node.patterns[k]
|
|
|
|
|
|
|
|
// Wildcard
|
|
|
|
// '/hello/*/foo' => match /hello/bar/foo
|
|
|
|
if (pattern === '*') {
|
|
|
|
const astNode = node.children['*']
|
|
|
|
if (astNode) {
|
2023-10-15 23:39:37 +00:00
|
|
|
handlerSets.push(...this.gHSets(astNode, method, { ...params, ...node.params }))
|
2022-07-02 06:09:45 +00:00
|
|
|
tempNodes.push(astNode)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (part === '') continue
|
|
|
|
|
|
|
|
const [key, name, matcher] = pattern
|
2023-01-08 08:22:44 +00:00
|
|
|
|
2023-07-20 07:48:38 +00:00
|
|
|
const child = node.children[key]
|
|
|
|
|
2023-01-08 08:22:44 +00:00
|
|
|
// `/js/:filename{[a-z]+.js}` => match /js/chunk/123.js
|
2023-01-08 09:45:46 +00:00
|
|
|
const restPathString = parts.slice(i).join('/')
|
2023-01-08 08:22:44 +00:00
|
|
|
if (matcher instanceof RegExp && matcher.test(restPathString)) {
|
|
|
|
params[name] = restPathString
|
2023-10-15 23:39:37 +00:00
|
|
|
handlerSets.push(...this.gHSets(child, method, { ...params, ...node.params }))
|
2023-01-08 08:22:44 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-07-02 06:09:45 +00:00
|
|
|
if (matcher === true || (matcher instanceof RegExp && matcher.test(part))) {
|
|
|
|
if (typeof key === 'string') {
|
2023-10-15 23:39:37 +00:00
|
|
|
params[name] = part
|
2022-07-02 06:09:45 +00:00
|
|
|
if (isLast === true) {
|
2023-10-15 23:39:37 +00:00
|
|
|
handlerSets.push(...this.gHSets(child, method, { ...params, ...node.params }))
|
2023-07-20 07:48:38 +00:00
|
|
|
if (child.children['*']) {
|
2023-10-15 23:39:37 +00:00
|
|
|
handlerSets.push(
|
|
|
|
...this.gHSets(child.children['*'], method, { ...params, ...node.params })
|
|
|
|
)
|
2023-07-20 07:48:38 +00:00
|
|
|
}
|
2022-08-21 01:11:53 +00:00
|
|
|
} else {
|
2023-10-15 23:39:37 +00:00
|
|
|
child.params = { ...params }
|
2023-07-20 07:48:38 +00:00
|
|
|
tempNodes.push(child)
|
2022-07-02 06:09:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
curNodes = tempNodes
|
|
|
|
}
|
2023-10-15 23:39:37 +00:00
|
|
|
const results = handlerSets.sort((a, b) => {
|
|
|
|
return a.score - b.score
|
|
|
|
})
|
2022-07-02 06:09:45 +00:00
|
|
|
|
2023-10-15 23:39:37 +00:00
|
|
|
return [results.map(({ handler, params }) => [handler, params] as [T, Params])]
|
2022-07-02 06:09:45 +00:00
|
|
|
}
|
|
|
|
}
|