@fedify/fedify
Version:
An ActivityPub server framework
94 lines (93 loc) • 2.94 kB
JavaScript
// @ts-ignore TS7016
import { Router as InnerRouter } from "uri-template-router";
import { parseTemplate } from "url-template";
/**
* URL router and constructor based on URI Template
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
*/
export class Router {
#router;
#templates;
#templateStrings;
#trailingSlashInsensitive;
/**
* Create a new {@link Router}.
* @param options Options for the router.
*/
constructor(options = {}) {
this.#router = new InnerRouter();
this.#templates = {};
this.#templateStrings = {};
this.#trailingSlashInsensitive = options.trailingSlashInsensitive ?? false;
}
/**
* Checks if a path name exists in the router.
* @param name The name of the path.
* @returns `true` if the path name exists, otherwise `false`.
*/
has(name) {
return name in this.#templates;
}
/**
* Adds a new path rule to the router.
* @param template The path pattern.
* @param name The name of the path.
* @returns The names of the variables in the path pattern.
*/
add(template, name) {
if (!template.startsWith("/")) {
throw new RouterError("Path must start with a slash.");
}
const rule = this.#router.addTemplate(template, {}, name);
this.#templates[name] = parseTemplate(template);
this.#templateStrings[name] = template;
return new Set(rule.variables.map((v) => v.varname));
}
/**
* Resolves a path name and values from a URL, if any match.
* @param url The URL to resolve.
* @returns The name of the path and its values, if any match. Otherwise,
* `null`.
*/
route(url) {
let match = this.#router.resolveURI(url);
if (match == null) {
if (!this.#trailingSlashInsensitive)
return null;
url = url.endsWith("/") ? url.replace(/\/+$/, "") : `${url}/`;
match = this.#router.resolveURI(url);
if (match == null)
return null;
}
return {
name: match.matchValue,
template: this.#templateStrings[match.matchValue],
values: match.params,
};
}
/**
* Constructs a URL/path from a path name and values.
* @param name The name of the path.
* @param values The values to expand the path with.
* @returns The URL/path, if the name exists. Otherwise, `null`.
*/
build(name, values) {
if (name in this.#templates) {
return this.#templates[name].expand(values);
}
return null;
}
}
/**
* An error thrown by the {@link Router}.
*/
export class RouterError extends Error {
/**
* Create a new {@link RouterError}.
* @param message The error message.
*/
constructor(message) {
super(message);
this.name = "RouterError";
}
}