import { setImmediate } from "../../deps.ts";import { Route } from "./route.ts";import { Layer } from "./layer.ts";import { merge } from "../utils/merge.ts";import { parseUrl } from "../utils/parseUrl.ts";import { methods } from "../methods.ts";import type { NextFunction, OpineRequest, OpineResponse, Router as IRouter, RouterConstructor,} from "../types.ts";
const objectRegExp = /^\[object (\S+)\]$/;const setPrototypeOf = Object.setPrototypeOf;
export const Router: RouterConstructor = function (options: any = {}): any { function router( req: OpineRequest, res: OpineResponse, next: NextFunction, ): void { (router as any).handle(req, res, next); }
setPrototypeOf(router, Router);
router.params = {}; router._params = [] as any[]; router.caseSensitive = options.caseSensitive; router.mergeParams = options.mergeParams; router.strict = options.strict; router.stack = [] as any[];
return router as IRouter;} as any;
Router.param = function param(name, fn) { var params = this._params; var len = params.length; var ret;
if (typeof name !== "string") { throw new Error( "invalid param() call for " + name + ", value must be a string", ); }
for (var i = 0; i < len; ++i) { if (ret = params[i](name, fn)) { fn = ret; } }
if ("function" !== typeof fn) { throw new Error("invalid param() call for " + name + ", got " + fn); }
(this.params[name] = this.params[name] || []).push(fn); return this;};
Router.handle = function handle( req: OpineRequest, res: OpineResponse, out: NextFunction = () => {},) { const self: any = this;
let idx = 0; let protohost = getProtohost(req.url) || ""; let removed = ""; let slashAdded = false; let paramcalled = {};
let options: any[] = [];
let stack = self.stack;
let parentParams = req.params; let parentUrl = req.baseUrl || ""; let done = (restore as any)(out, req, "baseUrl", "next", "params");
req.next = next;
if (req.method === "OPTIONS") { done = wrap(done, function (old: any, err?: any): void { if (err || options.length === 0) { return old(err); } sendOptionsResponse(res, options, old); }); }
req.baseUrl = parentUrl; req.originalUrl = req.originalUrl || req.url;
next();
function next(err?: any) { let layerError = err === "route" ? null : err;
if (slashAdded) { req.url = req.url.substr(1); slashAdded = false; }
if (removed.length !== 0) { req.baseUrl = parentUrl; req.url = protohost + removed + req.url.substr(protohost.length); removed = ""; }
if (layerError === "router") { setImmediate(done, null); return; }
if (idx >= stack.length) { setImmediate(done, layerError); return; }
let path = (parseUrl(req) || {}).pathname;
if (path == null) { return done(layerError); }
let layer: any; let match: any; let route: any;
while (match !== true && idx < stack.length) { layer = stack[idx++]; match = matchLayer(layer, path); route = layer.route;
if (typeof match !== "boolean") { layerError = layerError || match; }
if (match !== true) { continue; }
if (!route) { continue; }
if (layerError) { match = false; continue; }
let method = req.method; let has_method = route._handles_method(method);
if (!has_method && method === "OPTIONS") { appendMethods(options, route._options()); }
if (!has_method && method !== "HEAD") { match = false; continue; } }
if (match !== true) { return done(layerError); }
if (route) { req.route = route; }
req.params = self.mergeParams ? mergeParams(layer.params, parentParams) : layer.params;
let layerPath = layer.path;
self.process_params( layer, paramcalled, req, res, function (err?: any) { if (err) { return next(layerError || err); }
if (route) { return layer.handle_request(req, res, next); }
trim_prefix(layer, layerError, layerPath, path); }, ); }
function trim_prefix( layer: any, layerError: any, layerPath: any, path: any, ) { if (layerPath.length !== 0) { let c = path[layerPath.length];
if (c && c !== "/" && c !== ".") { return next(layerError); }
removed = layerPath; req.url = protohost + req.url.substr(protohost.length + removed.length);
if (!protohost && req.url[0] !== "/") { req.url = "/" + req.url; slashAdded = true; }
req.baseUrl = parentUrl + (removed[removed.length - 1] === "/" ? removed.substring(0, removed.length - 1) : removed); }
if (layerError) { layer.handle_error(layerError, req, res, next); } else { layer.handle_request(req, res, next); } }};
Router.process_params = function process_params( layer: any, called: any, req: OpineRequest, res: OpineResponse, done: NextFunction,) { let params = this.params;
let keys = layer.keys;
if (!keys || keys.length === 0) { return done(); }
let i = 0; let name; let paramIndex = 0; let key: any; let paramVal: any; let paramCallbacks: any; let paramCalled: any;
function param(err?: any): any { if (err) { return done(err); }
if (i >= keys.length) { return done(); }
paramIndex = 0; key = keys[i++]; name = key.name; paramVal = req.params[name]; paramCallbacks = params[name]; paramCalled = called[name];
if (paramVal === undefined || !paramCallbacks) { return param(); }
if ( paramCalled && (paramCalled.match === paramVal || (paramCalled.error && paramCalled.error !== "route")) ) { req.params[name] = paramCalled.value;
return param(paramCalled.error); }
called[name] = paramCalled = { error: null, match: paramVal, value: paramVal, };
paramCallback(); }
function paramCallback(err?: any) { let fn = paramCallbacks[paramIndex++];
paramCalled.value = req.params[key.name];
if (err) { paramCalled.error = err; param(err); return; }
if (!fn) return param();
try { fn(req, res, paramCallback, paramVal, key.name); } catch (e) { paramCallback(e); } }
param();};
Router.use = function use(fn: any) { let offset = 0; let path = "/";
if (typeof fn !== "function") { let arg: any = fn;
while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; }
if (typeof arg !== "function") { offset = 1; path = fn; } }
let callbacks = Array.prototype.slice.call(arguments, offset).flat(1);
if (callbacks.length === 0) { throw new TypeError("Router.use() requires a middleware function"); }
for (let i = 0; i < callbacks.length; i++) { let fn = callbacks[i];
if (typeof fn !== "function") { throw new TypeError( "Router.use() requires a middleware function but got a " + gettype(fn), ); }
let layer = new (Layer as any)(path, { sensitive: this.caseSensitive, strict: false, end: false, }, fn);
layer.route = undefined;
this.stack.push(layer); }
return this;};
Router.route = function route(path: string) { let route = new Route(path);
let layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true, }, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer); return route;};
methods.concat("all").forEach(function (method) { (Router as any)[method] = function (path: string) { let route = this.route(path); route[method].apply(route, Array.prototype.slice.call(arguments, 1)); return this; };});
function appendMethods(list: any, addition: any) { for (let i = 0; i < addition.length; i++) { let method = addition[i]; if (list.indexOf(method) === -1) { list.push(method); } }}
function getProtohost(url: string) { if (typeof url !== "string" || url.length === 0 || url[0] === "/") { return undefined; }
let searchIndex = url.indexOf("?"); let pathLength = searchIndex !== -1 ? searchIndex : url.length; let fqdnIndex = url.substr(0, pathLength).indexOf("://");
return fqdnIndex !== -1 ? url.substr(0, url.indexOf("/", 3 + fqdnIndex)) : undefined;}
function gettype(obj: any) { let type = typeof obj;
if (type !== "object") { return type; }
return Object.prototype.toString.call(obj) .replace(objectRegExp, "$1");}
function matchLayer(layer: any, path: string) { try { return layer.match(path); } catch (err) { return err; }}
function mergeParams(params: any, parent: any) { if (typeof parent !== "object" || !parent) { return params; }
let obj = merge({}, parent);
if (!(0 in params) || !(0 in parent)) { return merge(obj, params); }
let i = 0; let o = 0;
while (i in params) { i++; }
while (o in parent) { o++; }
for (i--; i >= 0; i--) { params[i + o] = params[i];
if (i < o) { delete params[i]; } }
return merge(obj, params);}
function restore(fn: Function, obj: any) { let props = new Array(arguments.length - 2); let vals = new Array(arguments.length - 2);
for (let i = 0; i < props.length; i++) { props[i] = arguments[i + 2]; vals[i] = obj[props[i]]; }
return function (this: any) { for (let i = 0; i < props.length; i++) { obj[props[i]] = vals[i]; }
return fn.apply(this, arguments); };}
function sendOptionsResponse( res: OpineResponse, options: any, next: NextFunction,) { try { let body = options.join(","); res.set("Allow", body); res.send(body); } catch (err) { next(err); }}
function wrap(old: any, fn: any) { return function proxy(this: any) { let args = new Array(arguments.length + 1);
args[0] = old; for (let i = 0, len = arguments.length; i < len; i++) { args[i + 1] = arguments[i]; }
fn.apply(this, args); };}