UNPKG

@assassinonz/exzodus-router

Version:

Express router wrapper with end to end type safety

81 lines (80 loc) 3.58 kB
import express from "express"; import { z } from "zod"; import { METHODS } from "../core/index.js"; export { express, z }; export class ExZodusRouter { constructor() { } static new(apiDef, config) { const router = express.Router(); for (const method of METHODS) { const originalHandlerRegistrar = router[method].bind(router); router[method] = (path, ...handlers) => { const routeDescription = apiDef[path]?.[method] ?? undefined; if (!routeDescription) { return originalHandlerRegistrar(path, handlers); } const requestValidator = (req, res, next) => { try { if (routeDescription?.parameters?.path) { req.params = routeDescription.parameters.path.parse(req.params); } else { req.params = z.object({}).parse(req.params); } if (routeDescription?.parameters?.query) { for (const key of Object.keys(req.query)) { if (req.query[key] === "true") { req.query[key] = true; } else if (req.query[key] === "false") { req.query[key] = false; } } req.query = routeDescription.parameters.query.parse(req.query); } else { req.query = z.object({}).parse(req.query); } if (!config.skipRequestBodyValidation) { if (routeDescription?.request) { req.body = routeDescription.request.parse(req.body); } else { req.body = z.object({}).optional().parse(req.body); } } return next(); } catch (err) { config.errorHandler(err, req, res); } }; const responseValidator = (_req, res, next) => { const originalJson = res.json; res.json = function (body) { try { const parsedBody = routeDescription.responses?.[res.statusCode]?.parse(body) ?? body; return originalJson.call(this, parsedBody); } catch { res.status(500); return originalJson.call(this, { status: "Internal server error", message: "Server generated response is out of API spec" }); } }; return next(); }; if (config.attachResponseValidator) { return originalHandlerRegistrar(path, requestValidator, responseValidator, handlers); } else { return originalHandlerRegistrar(path, requestValidator, handlers); } }; } return router; } }