UNPKG

5.51 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. and LoopBack contributors 2018,2019. All Rights Reserved.
3// Node module: @loopback/rest
4// This file is licensed under the MIT License.
5// License text available at https://opensource.org/licenses/MIT
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.Trie = void 0;
8const path_to_regexp_1 = require("path-to-regexp");
9const openapi_path_1 = require("./openapi-path");
10/**
11 * An implementation of trie for routes. The key hierarchy is built with parts
12 * of the route path delimited by `/`
13 */
14class Trie {
15 constructor() {
16 this.root = { key: '', children: {} };
17 }
18 /**
19 * Create a node for a given path template
20 * @param pathTemplate - The path template,
21 * @param value - Value of the route
22 */
23 create(routeTemplate, value) {
24 const keys = routeTemplate.split('/').filter(Boolean);
25 return createNode(keys, 0, value, this.root);
26 }
27 /**
28 * Match a route path against the trie
29 * @param path - The route path, such as `/customers/c01`
30 */
31 match(path) {
32 const keys = path.split('/').filter(Boolean);
33 const params = {};
34 const resolved = search(keys, 0, params, this.root);
35 if (resolved == null || !isNodeWithValue(resolved.node))
36 return undefined;
37 return {
38 node: resolved.node,
39 params: resolved.params,
40 };
41 }
42 /**
43 * List all nodes with value of the trie
44 */
45 list() {
46 const nodes = [];
47 traverse(this.root, node => {
48 nodes.push(node);
49 });
50 return nodes;
51 }
52}
53exports.Trie = Trie;
54function isNodeWithValue(node) {
55 return node.value != null;
56}
57/**
58 * Use depth-first preorder traversal to list all nodes with values
59 * @param root - Root node
60 * @param visitor - A function to process nodes with values
61 */
62function traverse(root, visitor) {
63 if (isNodeWithValue(root))
64 visitor(root);
65 for (const k in root.children) {
66 traverse(root.children[k], visitor);
67 }
68}
69/**
70 * Match the given key to one or more children of the parent node
71 * @param key - Key
72 * @param parent - Parent node
73 */
74function matchChildren(key, parent) {
75 const resolvedNodes = [];
76 // Match key literal first
77 let child = parent.children[key];
78 if (child) {
79 resolvedNodes.push({
80 node: child,
81 });
82 return resolvedNodes;
83 }
84 // Match templates
85 for (const k in parent.children) {
86 child = parent.children[k];
87 if (!child.names || !child.regexp)
88 continue;
89 const match = child.regexp.exec(key);
90 if (match) {
91 const resolved = { params: {}, node: child };
92 let i = 0;
93 for (const n of child.names) {
94 const val = match[++i];
95 resolved.params[n] = decodeURIComponent(val);
96 }
97 resolvedNodes.push(resolved);
98 }
99 }
100 return resolvedNodes;
101}
102/**
103 * Search a sub list of keys against the parent node
104 * @param keys - An array of keys
105 * @param index - Starting index of the key list
106 * @param params - An object to receive resolved parameter values
107 * @param parent - Parent node
108 */
109function search(keys, index,
110// eslint-disable-next-line @typescript-eslint/no-explicit-any
111params, parent) {
112 const key = keys[index];
113 const resolved = { node: parent, params };
114 if (key === undefined)
115 return resolved;
116 const children = matchChildren(key, parent);
117 if (children.length === 0)
118 return undefined;
119 // There might be multiple matches, such as `/users/{id}` and `/users/{userId}`
120 for (const child of children) {
121 const result = search(keys, index + 1, params, child.node);
122 if (result && isNodeWithValue(result.node)) {
123 Object.assign(params, child.params);
124 return result;
125 }
126 }
127 // no matches found
128 return undefined;
129}
130/**
131 * Create a node for a sub list of keys against the parent node
132 * @param keys - An array of keys
133 * @param index - Starting index of the key list
134 * @param value - Value of the node
135 * @param parent - Parent node
136 */
137function createNode(keys, index, value, parent) {
138 const key = keys[index];
139 if (key === undefined)
140 return parent;
141 const isLast = keys.length - 1 === index;
142 let child = parent.children[key];
143 if (child != null) {
144 // Found an existing node
145 if (isLast) {
146 if (child.value == null) {
147 child.value = value;
148 }
149 else {
150 if (child.value !== value) {
151 throw new Error('Duplicate key found with different value: ' + keys.join('/'));
152 }
153 }
154 }
155 return createNode(keys, index + 1, value, child);
156 }
157 /**
158 * Build a new node
159 */
160 child = {
161 children: {},
162 key: key,
163 };
164 if (isLast) {
165 child.value = value;
166 }
167 // Check if the key has variables such as `{var}`
168 const path = (0, openapi_path_1.toExpressPath)(key);
169 const params = [];
170 const re = (0, path_to_regexp_1.pathToRegexp)(path, params);
171 if (params.length) {
172 child.names = params.map(p => `${p.name}`);
173 child.regexp = re;
174 }
175 // Add the node to the parent
176 parent.children[key] = child;
177 // Create nodes for rest of the keys
178 return createNode(keys, index + 1, value, child);
179}
180//# sourceMappingURL=trie.js.map
\No newline at end of file