@routejs/router
Version:
Fast and lightweight http routing engine for nodejs
271 lines (248 loc) • 7.67 kB
JavaScript
const supportedMethod = require("./supported-method.cjs");
module.exports = class Route {
host = null;
hostRegexp = null;
method = null;
path = null;
pathRegexp = null;
group = null;
name = null;
params = null;
subdomains = null;
callbacks = null;
caseSensitive = false;
constructor({ host, method, path, name, group, callbacks, caseSensitive }) {
if (host && !(typeof host === "string" || host instanceof String)) {
throw new TypeError(
"Error: route host accepts only string as an argument"
);
}
if (method) {
if (Array.isArray(method)) {
method = method.map((e) => {
if (!(typeof e === "string" || e instanceof String)) {
throw new TypeError(
"Error: route method accepts only string or array of string as an argument"
);
}
if (!supportedMethod.includes(e.toUpperCase())) {
throw new TypeError(
`Error: ${e.toUpperCase()} method is not supported`
);
}
return e.toUpperCase();
});
} else if (typeof method === "string" || method instanceof String) {
if (!supportedMethod.includes(method.toUpperCase())) {
throw new TypeError(
`Error: ${method.toUpperCase()} method is not supported`
);
}
method = method.toUpperCase();
} else {
throw new TypeError(
"Error: route method accepts only string or array of string as an argument"
);
}
}
if (path && !(typeof path === "string" || path instanceof String)) {
throw new TypeError(
"Error: route path accepts only string as an argument"
);
}
if (Array.isArray(callbacks) === false && typeof callbacks !== "function") {
throw new TypeError(
"Error: route callback accepts only function as an argument"
);
}
this.caseSensitive = caseSensitive ?? false;
this.host = host;
this.hostRegexp = host ? this.#compileHostRegExp(host) : undefined;
this.method = method;
this.path = path;
this.pathRegexp = path
? this.#compileRouteRegExp(path)
: group
? this.#compileMiddlewareRegExp(group)
: this.#compileMiddlewareRegExp("/");
this.group = group;
this.name = name;
this.params = path ? this.#getParams(path) : this.#getParams(group);
this.subdomains = this.#getParams(host);
this.callbacks = Array.isArray(callbacks)
? callbacks.map((callback) => {
if (typeof callback !== "function") {
throw new TypeError(
`Error: ${
path ? "route" : "middleware"
} callback accepts only function as an argument`
);
}
return callback;
})
: [callbacks];
}
setName(name) {
this.name = name;
return this;
}
match({ host, method, path }) {
if (host && !(typeof host === "string" || host instanceof String)) {
throw new TypeError(
"Error: request host accepts only string as an argument"
);
}
if (!method) {
throw new TypeError("Error: request method is required");
}
if (!(typeof method === "string" || method instanceof String)) {
throw new TypeError(
"Error: request method accepts only string as an argument"
);
}
if (!path) {
throw new TypeError("Error: request path is required");
}
if (!(typeof path === "string" || path instanceof String)) {
throw new TypeError(
"Error: request path accepts only string as an argument"
);
}
if (this.pathRegexp === null) {
return false;
}
const route = {
host: this.host,
method: this.method,
path: this.path,
callbacks: this.callbacks,
params: {},
subdomains: {},
};
if (this.hostRegexp) {
const match = this.hostRegexp.exec(host);
if (match === null) {
return false;
}
if (match.length > 1) {
let index = 0;
for (let i = 1; i < match.length ?? 0; i++) {
if (this.subdomains && this.subdomains.hasOwnProperty(index)) {
route.subdomains[this.subdomains[index]] = match[i];
}
index++;
}
}
}
if (this.method) {
if (Array.isArray(this.method)) {
if (!this.method.includes(method.toUpperCase())) {
return false;
}
} else if (method.toUpperCase() !== this.method) {
return false;
}
}
const match = this.pathRegexp.exec(path);
if (match === null) {
return false;
}
if (match.length > 1) {
let index = 0;
for (let i = 1; i < match.length ?? 0; i++) {
if (this.params && this.params.hasOwnProperty(index)) {
route.params[this.params[index]] = match[i];
}
index++;
}
}
return route;
}
#compileHostRegExp(host) {
try {
let regexp = host
? host
// Esacep regex special char except inside {}
.replace(/[.*+?^${}()|[\]\\](?![^{]*})/g, "\\$&")
// Add user defined regex
.replace(/\{([^\\}]+)\:/g, "")
.replace(/\)\\}/g, ")")
// Named regex
.replace(/\{(.*?)\\}/g, "([^.]+?)")
: "";
if (this.caseSensitive === true) {
return regexp ? new RegExp(`^${regexp}$`) : null;
}
return regexp ? new RegExp(`^${regexp}$`, "i") : null;
} catch (err) {
throw new TypeError(`Error: ${host} invalid regular expression`);
}
}
#compileRouteRegExp(path) {
try {
let regexp = path
? path
// Esacep regex special char except inside {}
.replace(/[.*+?^${}()|[\]\\](?![^{]*})/g, "\\$&")
// Ignore trailing slashes
.replace(/^\/?|\/?$/g, "")
// Add user defined regex
.replace(/\{([^\\}]+)\:/g, "")
.replace(/\)\\}/g, ")")
// Named regex
.replace(/\{(.*?)\\}/g, "([^/]+?)")
: "";
if (this.caseSensitive === true) {
return regexp
? new RegExp(`^/?${regexp}/?$`)
: regexp === ""
? new RegExp("^/?$")
: null;
}
return regexp
? new RegExp(`^/?${regexp}/?$`, "i")
: regexp === ""
? new RegExp("^/?$", "i")
: null;
} catch (err) {
throw new TypeError(`Error: ${path} invalid regular expression`);
}
}
#compileMiddlewareRegExp(path) {
try {
let regexp = path
? path
// Esacep regex special char except inside {}
.replace(/[.*+?^${}()|[\]\\](?![^{]*})/g, "\\$&")
// Ignore trailing slashes
.replace(/^\/?|\/?$/g, "")
// Add user defined regex
.replace(/\{([^\\}]+)\:/g, "")
.replace(/\)\\}/g, ")")
// Named regex
.replace(/\{(.*?)\\}/g, "([^/]+?)")
: "";
if (this.caseSensitive === true) {
return regexp
? new RegExp(`^/?${regexp}/?(?=\/|$)`)
: regexp === ""
? new RegExp("^/?(?=/|$)")
: null;
}
return regexp
? new RegExp(`^/?${regexp}/?(?=\/|$)`, "i")
: regexp === ""
? new RegExp("^/?(?=/|$)", "i")
: null;
} catch (err) {
throw new TypeError(`Error: ${path} invalid regular expression`);
}
}
#getParams(path) {
let params = path ? path.match(/(?<=\{).+?(?=\})/g) : null;
if (!params) {
return undefined;
}
return params.map((e) => e.replace(/\:\((.*)?/, ""));
}
};