UNPKG

@tunframework/tun-rest-router

Version:
147 lines (146 loc) 5.17 kB
// @ts-check import { HttpError, HttpMethod } from '@tunframework/tun'; import { matchRoute, pathToRegexp } from './route-utils.js'; export class RestifyRouter { _routes = []; // temp prefix, consumed in addRoute() _prefix = ''; _methods; constructor({ prefix = '', methods } = {}) { this.prefix(prefix); this._methods = methods; } prefix(_prefix) { this._prefix = _prefix || ''; return this; } routes() { return (ctx, next) => { let route = matchRoute(ctx.req.method, ctx.req.path, this._routes); if (!route) { return next(); } ctx.state.matchedRoute = route; if (ctx.res.status === 404) { ctx.res.status = 200; } let slugs = route.slugNames.reduce((p, n, nIndex) => { p[n] = route.slugValues[nIndex]; return p; }, {}); ctx.req.slugs = slugs; return route.handler.call(null, ctx, next); }; } addRoute(methods, pathname, handler) { this._routes.push({ // ...pathToRegexp(pathname), ...pathToRegexp((this._prefix || '') + pathname), methods: Array.isArray(methods) ? methods : [methods], slugValues: [], handler }); return this; } get(pathname, handler) { return this.addRoute('GET', pathname, handler); } head(pathname, handler) { return this.addRoute('HEAD', pathname, handler); } post(pathname, handler) { return this.addRoute('POST', pathname, handler); } put(pathname, handler) { return this.addRoute('PUT', pathname, handler); } delete(pathname, handler) { return this.addRoute('DELETE', pathname, handler); } // connect(pathname: string, handler: TunComposable<TunContext>) { // return this.addRoute('CONNECT', pathname, handler); // } options(pathname, handler) { return this.addRoute('OPTIONS', pathname, handler); } // trace(pathname: string, handler: TunComposable<TunContext>) { // return this.addRoute('TRACE', pathname, handler); // } patch(pathname, handler) { return this.addRoute('PATCH', pathname, handler); } /** * Fufill response header if all middleware resolved and `ctx.status` is empty or 404(not_found). */ allowedMethods(options = {}) { const implemented = this._methods || Object.keys(HttpMethod); return async function allowedMethods(ctx, next) { await next(); if (ctx.res.status || ctx.res.status !== 404) { return; } const allowed = {}; const matchedRoute = ctx.state.matchedRoute; if (matchedRoute) { matchedRoute.methods.forEach((method) => { allowed[method] = method; }); } const allowedArr = Object.keys(allowed); if (implemented.indexOf(ctx.req.method) > -1) { if (options.throw) { let throwable = null; if (typeof options.notImplemented === 'function') { throwable = options.notImplemented(); } else { throwable = new HttpError({ status: 501 }); } throw throwable; } else { ctx.res.status = 501; ctx.res.set('Allow', allowedArr); } } else if (allowedArr.length) { if (ctx.req.method === 'OPTIONS') { ctx.res.status = 200; ctx.body = ''; ctx.res.set('Allow', allowedArr); } else if (!allowed[ctx.req.method]) { if (options.throw) { let throwable = null; if (typeof options.notImplemented === 'function') { throwable = options.notImplemented(); } else { throwable = new HttpError({ status: 405 }); } throw throwable; } else { ctx.res.status = 405; ctx.res.set('Allow', allowedArr); } } } }; } } export let loadRestifyRoutes = async (pathname) => { let M = await import(pathname); let list = Object.keys(M) .filter((k) => M[k] instanceof RestifyRouter) .map((k) => M[k]); if (list.length === 1) { return list[0]; } // compose let r = new RestifyRouter(); for (const item of list) { r._routes.push(...item._routes); } return r; };