@loopback/rest
Version:
Expose controllers as REST endpoints and route REST API requests to controller methods
180 lines • 5.51 kB
JavaScript
// Copyright IBM Corp. and LoopBack contributors 2018,2019. All Rights Reserved.
// Node module: @loopback/rest
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.Trie = void 0;
const path_to_regexp_1 = require("path-to-regexp");
const openapi_path_1 = require("./openapi-path");
/**
* An implementation of trie for routes. The key hierarchy is built with parts
* of the route path delimited by `/`
*/
class Trie {
constructor() {
this.root = { key: '', children: {} };
}
/**
* Create a node for a given path template
* @param pathTemplate - The path template,
* @param value - Value of the route
*/
create(routeTemplate, value) {
const keys = routeTemplate.split('/').filter(Boolean);
return createNode(keys, 0, value, this.root);
}
/**
* Match a route path against the trie
* @param path - The route path, such as `/customers/c01`
*/
match(path) {
const keys = path.split('/').filter(Boolean);
const params = {};
const resolved = search(keys, 0, params, this.root);
if (resolved == null || !isNodeWithValue(resolved.node))
return undefined;
return {
node: resolved.node,
params: resolved.params,
};
}
/**
* List all nodes with value of the trie
*/
list() {
const nodes = [];
traverse(this.root, node => {
nodes.push(node);
});
return nodes;
}
}
exports.Trie = Trie;
function isNodeWithValue(node) {
return node.value != null;
}
/**
* Use depth-first preorder traversal to list all nodes with values
* @param root - Root node
* @param visitor - A function to process nodes with values
*/
function traverse(root, visitor) {
if (isNodeWithValue(root))
visitor(root);
for (const k in root.children) {
traverse(root.children[k], visitor);
}
}
/**
* Match the given key to one or more children of the parent node
* @param key - Key
* @param parent - Parent node
*/
function matchChildren(key, parent) {
const resolvedNodes = [];
// Match key literal first
let child = parent.children[key];
if (child) {
resolvedNodes.push({
node: child,
});
return resolvedNodes;
}
// Match templates
for (const k in parent.children) {
child = parent.children[k];
if (!child.names || !child.regexp)
continue;
const match = child.regexp.exec(key);
if (match) {
const resolved = { params: {}, node: child };
let i = 0;
for (const n of child.names) {
const val = match[++i];
resolved.params[n] = decodeURIComponent(val);
}
resolvedNodes.push(resolved);
}
}
return resolvedNodes;
}
/**
* Search a sub list of keys against the parent node
* @param keys - An array of keys
* @param index - Starting index of the key list
* @param params - An object to receive resolved parameter values
* @param parent - Parent node
*/
function search(keys, index,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params, parent) {
const key = keys[index];
const resolved = { node: parent, params };
if (key === undefined)
return resolved;
const children = matchChildren(key, parent);
if (children.length === 0)
return undefined;
// There might be multiple matches, such as `/users/{id}` and `/users/{userId}`
for (const child of children) {
const result = search(keys, index + 1, params, child.node);
if (result && isNodeWithValue(result.node)) {
Object.assign(params, child.params);
return result;
}
}
// no matches found
return undefined;
}
/**
* Create a node for a sub list of keys against the parent node
* @param keys - An array of keys
* @param index - Starting index of the key list
* @param value - Value of the node
* @param parent - Parent node
*/
function createNode(keys, index, value, parent) {
const key = keys[index];
if (key === undefined)
return parent;
const isLast = keys.length - 1 === index;
let child = parent.children[key];
if (child != null) {
// Found an existing node
if (isLast) {
if (child.value == null) {
child.value = value;
}
else {
if (child.value !== value) {
throw new Error('Duplicate key found with different value: ' + keys.join('/'));
}
}
}
return createNode(keys, index + 1, value, child);
}
/**
* Build a new node
*/
child = {
children: {},
key: key,
};
if (isLast) {
child.value = value;
}
// Check if the key has variables such as `{var}`
const path = (0, openapi_path_1.toExpressPath)(key);
const params = [];
const re = (0, path_to_regexp_1.pathToRegexp)(path, params);
if (params.length) {
child.names = params.map(p => `${p.name}`);
child.regexp = re;
}
// Add the node to the parent
parent.children[key] = child;
// Create nodes for rest of the keys
return createNode(keys, index + 1, value, child);
}
//# sourceMappingURL=trie.js.map
;