UNPKG

@chubbyts/chubbyts-framework

Version:

A minimal, highly performant middleware PSR-15 inspired function based micro framework built with as little complexity as possible, aimed primarily at those developers who want to understand all the vendors they use.

218 lines (191 loc) 6.4 kB
import { STATUS_CODES } from 'node:http'; import { createLogger } from '@chubbyts/chubbyts-log-types/dist/log'; import { throwableToError } from '@chubbyts/chubbyts-throwable-to-error/dist/throwable-to-error'; import { isHttpError, mapToHttpError as defaultMapToHttpError } from '@chubbyts/chubbyts-http-error/dist/http-error'; import { Response } from '@chubbyts/chubbyts-undici-server/dist/server'; const htmlTemplate = String.raw `<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>__TITLE__</title> <style> html { font-family: Helvetica, Arial, Verdana, sans-serif; line-height: 1.5; tab-size: 4; } body { margin: 0; } * { border-width: 0; border-style: solid; } .container { width: 100% } .mx-auto { margin-left: auto; margin-right: auto; } .mt-12 { margin-top: 3rem; } .mb-12 { margin-bottom: 3rem; } .text-gray-400 { --tw-text-opacity: 1; color: rgba(156, 163, 175, var(--tw-text-opacity)); } .text-5xl { font-size: 3rem; line-height: 1; } .text-right { text-align: right; } .tracking-tighter { letter-spacing: -.05em; } .flex { display: flex; } .flex-row { flex-direction: row; } .basis-2/12 { flex-basis: 16.666667%; } .basis-10/12 { flex-basis: 83.333333%; } .space-x-8>:not([hidden])~:not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(2rem * var(--tw-space-x-reverse)); margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse))) } .gap-x-4 { column-gap: 1rem; } .gap-y-1.5 { row-gap: 0.375rem; } .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } .grid { display: grid; } @media (min-width:640px) { .container { max-width: 640px } } @media (min-width:768px) { .container { max-width: 768px } .md:grid-cols-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); } .md:col-span-7 { grid-column: span 7/span 7 } } @media (min-width:1024px) { .container { max-width: 1024px } } @media (min-width:1280px) { .container { max-width: 1280px } } @media (min-width:1536px) { .container { max-width: 1536px } } </style> </head> <body> <div class="container mx-auto tracking-tighter mt-12"> <div class="flex flex-row space-x-8"> <div class="basis-1/12 text-5xl text-gray-400 text-right">__STATUS__</div> <div class="basis-11/12"> <span class="text-5xl">__TITLE__</span>__BODY__ </div> </div> </div> </body> </html>`; const errorToData = (error) => { return { name: error.name, message: error.message, stack: error.stack, }; }; const errorToDataArray = (e) => { const errors = []; do { // eslint-disable-next-line functional/immutable-data errors.push(errorToData(throwableToError(e))); // eslint-disable-next-line no-param-reassign } while ((e = e && e.cause)); return errors; }; const addDebugToBody = (errors) => { return `<div class="mt-12"> ${errors .map((error) => `<div class="mb-12 grid grid-cols-1 md:grid-cols-8 gap-4"> ${Object.entries(error) .map(([key, value]) => `<div><strong>${key}</strong></div><div class="md:col-span-7">${typeof value === 'string' ? value.replaceAll('\n', '<br>\n') : value}</div>`) .join('')} </div>`) .join('\n')} </div>`; }; const handleHttpError = (logger, httpError, debug) => { const { status, title, detail, instance } = httpError; const errors = errorToDataArray(httpError); const isClientError = status < 500; logger[isClientError ? 'info' : 'error']('Http Error', { data: { ...httpError }, errors }); return new Response(htmlTemplate .replaceAll('__STATUS__', status.toString()) .replaceAll('__TITLE__', title) .replaceAll('__BODY__', [ ...(detail ? [`<p>${detail}</p>`] : []), ...(instance ? [`<p>${instance}</p>`] : []), ...(debug ? [addDebugToBody(errors)] : []), ].join('')), { status, statusText: STATUS_CODES[status], headers: { 'content-type': 'text/html' } }); }; const createHttpErrorFromError = (e, mapToHttpError) => { try { return mapToHttpError(e); } catch { return defaultMapToHttpError(e); } }; /** * ```ts * import type { Middleware } from '@chubbyts/chubbyts-undici-for-server/dist/middleware'; * import { createErrorMiddleware } from '@chubbyts/chubbyts-framework/dist/middleware/error-middleware'; * * const errorMiddleware: Middleware = createErrorMiddleware(); * ``` */ export const createErrorMiddleware = (debug = false, logger = createLogger(), mapToHttpError = defaultMapToHttpError) => { return async (serverRequest, handler) => { try { return await handler(serverRequest); } catch (e) { const httpError = isHttpError(e) ? e : createHttpErrorFromError(e, mapToHttpError); return handleHttpError(logger, httpError, debug); } }; };