UNPKG

fets

Version:

TypeScript HTTP Framework focusing on e2e type-safety, easy setup, performance & great developer experience

144 lines (143 loc) 6.3 kB
import { TypeGuard } from '@sinclair/typebox'; import { TypeCompiler } from '@sinclair/typebox/compiler'; import { Value } from '@sinclair/typebox/value'; import { HTTPError } from '@whatwg-node/server'; import { getHeadersObj } from './utils.js'; function createValidateFn(schema) { try { const validator = TypeCompiler.Compile(schema); return function getErrors(data) { return validator.Errors(data); }; } catch (e) { return function getErrors(data) { return Value.Errors(schema, data); }; } } function sanitizeError({ schema, type, ...error }, name) { return { ...error, name, }; } export function useTypeBox({ components = {}, } = {}) { const validateFnBySchema = new WeakMap(); function getValidateFn(schema) { let validateFn = validateFnBySchema.get(schema); if (!validateFn) { validateFn = createValidateFn({ ...schema, components, }); validateFnBySchema.set(schema, validateFn); } return validateFn; } return { onRouteHandle({ route: { schemas }, request }) { if (schemas?.request?.headers && TypeGuard.IsSchema(schemas.request.headers)) { const validateFn = getValidateFn(schemas.request.headers); const headersObj = getHeadersObj(request.headers); const errors = [...validateFn(headersObj)].map(error => sanitizeError(error, 'headers')); if (errors.length) { throw new HTTPError(400, 'Bad Request', { 'x-error-type': 'validation', }, { errors, }); } } if (schemas?.request?.params && TypeGuard.IsSchema(schemas.request.params)) { const validateFn = getValidateFn(schemas.request.params); const errors = [...validateFn(request.params)].map(error => sanitizeError(error, 'params')); if (errors.length) { throw new HTTPError(400, 'Bad Request', { 'x-error-type': 'validation', }, { errors, }); } } if (schemas?.request?.query && TypeGuard.IsSchema(schemas.request.query)) { const validateFn = getValidateFn(schemas.request.query); const errors = [...validateFn(request.query)].map(error => sanitizeError(error, 'query')); if (errors.length) { throw new HTTPError(400, 'Bad Request', { 'x-error-type': 'validation', }, { errors, }); } } if (schemas?.request?.json && TypeGuard.IsSchema(schemas.request.json)) { const validateFn = getValidateFn(schemas.request.json); const origReqJsonMethod = request.json.bind(request); Object.defineProperty(request, 'json', { value: () => origReqJsonMethod().then(jsonObj => { const errors = [...validateFn(jsonObj)].map(({ schema, type, ...error }) => ({ name: 'json', ...error, })); if (errors.length) { throw new HTTPError(400, 'Bad Request', { 'x-error-type': 'validation', }, { errors, }); } return jsonObj; }), configurable: true, }); } if (schemas?.request?.formData && TypeGuard.IsSchema(schemas.request.formData)) { const validateFn = getValidateFn(schemas.request.formData); const origMethod = request.formData.bind(request); Object.defineProperty(request, 'formData', { configurable: true, value: () => origMethod().then(formData => { const formDataObj = {}; const jobs = []; formData.forEach((value, key) => { if (value != null) { if (typeof value === 'string') { formDataObj[key] = value; } else { jobs.push(value.arrayBuffer().then(buffer => { const typedArray = isByteArray(buffer) ? buffer : new Uint8Array(buffer); const binaryStrParts = []; typedArray.forEach((byte, index) => { binaryStrParts[index] = String.fromCharCode(byte); }); formDataObj[key] = binaryStrParts.join(''); })); } } }); function handleErrors() { const errors = [...validateFn(formDataObj)].map(error => sanitizeError(error, 'formData')); if (errors.length) { throw new HTTPError(400, 'Bad Request', { 'x-error-type': 'validation', }, { errors, }); } return formDataObj; } if (jobs.length) { return Promise.all(jobs).then(handleErrors); } return handleErrors(); }), }); } }, }; } function isByteArray(value) { return value?.forEach != null; }