UNPKG

@eggjs/router

Version:

Router middleware for egg/koa. Provides RESTful resource routing.

230 lines 15.9 kB
import { debuglog } from 'node:util'; import pathToRegExp from 'path-to-regexp'; import URI from 'urijs'; import { decodeURIComponent as safeDecodeURIComponent } from 'utility'; import { isGeneratorFunction } from 'is-type-of'; const debug = debuglog('@eggjs/router:Layer'); export class Layer { opts; name; methods = []; stack; path; regexp; paramNames = []; /** * Initialize a new routing Layer with given `method`, `path`, and `middleware`. * * @param {String|RegExp} path Path string or regular expression. * @param {Array} methods Array of HTTP verbs. * @param {Array|Function} middlewares Layer callback/middleware or series of. * @param {Object=} opts optional params * @param {String=} opts.name route name * @param {String=} opts.sensitive case sensitive (default: false) * @param {String=} opts.strict require the trailing slash (default: false) * @private */ constructor(path, methods, middlewares, opts) { if (typeof opts === 'string') { // new Layer(path, methods, middlewares, name); opts = { name: opts }; } this.opts = opts ?? {}; this.opts.prefix = this.opts.prefix ?? ''; this.name = this.opts.name; this.stack = Array.isArray(middlewares) ? middlewares : [middlewares]; for (const method of methods) { const l = this.methods.push(method.toUpperCase()); if (this.methods[l - 1] === 'GET') { this.methods.unshift('HEAD'); } } // ensure middleware is a function this.stack.forEach(fn => { const type = typeof fn; if (type !== 'function') { throw new TypeError(methods.toString() + ' `' + (this.opts.name || path) + '`: `middleware` ' + 'must be a function, not `' + type + '`'); } if (isGeneratorFunction(fn)) { throw new TypeError(methods.toString() + ' `' + (this.opts.name || path) + '`: Please use async function instead of generator function'); } }); this.path = path; this.regexp = pathToRegExp(path, this.paramNames, this.opts); debug('defined route %s %s', this.methods, this.opts.prefix + this.path); } /** * Returns whether request `path` matches route. * * @param {String} path path string * @return {Boolean} matched or not * @private */ match(path) { return this.regexp.test(path); } /** * Returns map of URL parameters for given `path` and `paramNames`. * * @param {String} _path path string * @param {Array.<String>} captures captures strings * @param {Object=} [existingParams] existing params * @return {Object} params object * @private */ params(_path, captures, existingParams) { const params = existingParams ?? {}; for (let len = captures.length, i = 0; i < len; i++) { const paramName = this.paramNames[i]; if (paramName) { const c = captures[i]; params[paramName.name] = c ? safeDecodeURIComponent(c) : c; } } return params; } /** * Returns array of regexp url path captures. * * @param {String} path path string * @return {Array.<String>} captures strings * @private */ captures(path) { if (this.opts.ignoreCaptures) return []; const m = path.match(this.regexp); return m ? m.slice(1) : []; } /** * Generate URL for route using given `params`. * * @example * * ```javascript * var route = new Layer(['GET'], '/users/:id', fn); * * route.url(123); // => "/users/123" * route.url('123'); // => "/users/123" * route.url({ id: 123 }); // => "/users/123" * ``` * * @param {Object} params url parameters * @param {Object} paramsOrOptions optional parameters * @return {String} url string * @private */ url(params, ...paramsOrOptions) { let args = params; const url = this.path.replace(/\(\.\*\)/g, ''); const toPath = pathToRegExp.compile(url); let options; if (params !== undefined && typeof params !== 'object') { args = [params, ...paramsOrOptions]; // route.url(stringOrNumber, params1, ..., options); if (Array.isArray(args)) { const lastIndex = args.length - 1; if (typeof args[lastIndex] === 'object') { options = args[lastIndex]; args = args.slice(0, lastIndex); } } } else if (typeof params === 'object') { if (typeof paramsOrOptions[0] === 'object' && 'query' in paramsOrOptions[0]) { // route.url(param, options); options = paramsOrOptions[0]; } } const tokens = pathToRegExp.parse(url); let replace = {}; if (Array.isArray(args)) { for (let len = tokens.length, i = 0, j = 0; i < len; i++) { const token = tokens[i]; if (typeof token === 'object' && token.name) { replace[token.name] = args[j++]; } } } else if (tokens.some(token => typeof token === 'object' && token.name)) { // route.url(params); replace = params; } else { // route.url(options); options = params; } const replaced = toPath(replace); if (options?.query) { const urlObject = new URI(replaced); urlObject.search(options.query); return urlObject.toString(); } return replaced; } /** * Run validations on route named parameters. * * @example * * ```javascript * router * .param('user', function (id, ctx, next) { * ctx.user = users[id]; * if (!user) return ctx.status = 404; * next(); * }) * .get('/users/:user', function (ctx, next) { * ctx.body = ctx.user; * }); * ``` * * @param {String} param param string * @param {Function} fn middleware function * @return {Layer} layer instance * @private */ param(param, fn) { const stack = this.stack; const params = this.paramNames; const middleware = function (ctx, next) { return fn.call(this, ctx.params[param], ctx, next); }; middleware.param = param; const names = params.map(p => { return p.name; }); const x = names.indexOf(param); if (x > -1) { // iterate through the stack, to figure out where to place the handler fn stack.some(function (fn, i) { // param handlers are always first, so when we find an fn w/o a param property, stop here // if the param handler at this part of the stack comes after the one we are adding, stop here if (!fn.param || names.indexOf(fn.param) > x) { // inject this param handler right before the current item stack.splice(i, 0, middleware); return true; // then break the loop } return false; }); } return this; } /** * Prefix route path. * * @param {String} prefix prefix string * @return {Layer} layer instance * @private */ setPrefix(prefix) { if (this.path) { this.path = prefix + this.path; this.paramNames = []; this.regexp = pathToRegExp(this.path, this.paramNames, this.opts); } return this; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTGF5ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvTGF5ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNyQyxPQUFPLFlBQTBCLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEQsT0FBTyxHQUFHLE1BQU0sT0FBTyxDQUFDO0FBQ3hCLE9BQU8sRUFBRSxrQkFBa0IsSUFBSSxzQkFBc0IsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUN2RSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFPakQsTUFBTSxLQUFLLEdBQUcsUUFBUSxDQUFDLHFCQUFxQixDQUFDLENBQUM7QUFrQjlDLE1BQU0sT0FBTyxLQUFLO0lBQ1AsSUFBSSxDQUFlO0lBQ25CLElBQUksQ0FBVTtJQUNkLE9BQU8sR0FBYSxFQUFFLENBQUM7SUFDdkIsS0FBSyxDQUFvQztJQUNsRCxJQUFJLENBQWtCO0lBQ3RCLE1BQU0sQ0FBUztJQUNmLFVBQVUsR0FBVSxFQUFFLENBQUM7SUFFdkI7Ozs7Ozs7Ozs7O09BV0c7SUFDSCxZQUFZLElBQXFCLEVBQUUsT0FBaUIsRUFBRSxXQUE4QyxFQUNsRyxJQUE0QjtRQUM1QixJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQzdCLCtDQUErQztZQUMvQyxJQUFJLEdBQUcsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDeEIsQ0FBQztRQUNELElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUN2QixJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUM7UUFDMUMsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQztRQUMzQixJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FB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