UNPKG

fets

Version:

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

214 lines (213 loc) • 9.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useAjv = void 0; const tslib_1 = require("tslib"); const ajv_1 = tslib_1.__importDefault(require("ajv")); const ajv_formats_1 = tslib_1.__importDefault(require("ajv-formats")); const fast_json_stringify_1 = tslib_1.__importDefault(require("@ardatan/fast-json-stringify")); const fetch_1 = require("@whatwg-node/fetch"); const Response_js_1 = require("../Response.js"); const types_js_1 = require("../zod/types.js"); const utils_js_1 = require("./utils.js"); function useAjv({ components = {}, } = {}) { const ajv = new ajv_1.default({ strict: false, strictSchema: false, validateSchema: false, allowUnionTypes: true, uriResolver: { parse(uri) { const url = new fetch_1.URL(uri); return { scheme: url.protocol, userinfo: url.username + (url.password ? ':' + url.password : ''), host: url.hostname, port: url.port, path: url.pathname, query: url.search, fragment: url.hash, }; }, resolve(base, ref) { return new fetch_1.URL(ref, base).toString(); }, serialize(components) { return (components.scheme + '://' + components.userinfo + components.host + components.port + components.path + components.query + components.fragment); }, }, }); (0, ajv_formats_1.default)(ajv); // Required for fast-json-stringify ajv.addKeyword({ keyword: 'fjs_type', type: 'object', errors: false, validate: (_type, date) => { return date instanceof Date; }, }); const serializersByPath = new Map(); return { onRoute({ path, schemas, handlers }) { const validationMiddlewares = new Map(); if (schemas?.request?.headers && !(0, types_js_1.isZodSchema)(schemas.request.headers)) { const validateFn = ajv.compile({ ...schemas.request.headers, components, }); validationMiddlewares.set('headers', request => { const headersObj = (0, utils_js_1.getHeadersObj)(request.headers); const isValid = validateFn(headersObj); if (!isValid) { return validateFn.errors; } return []; }); } if (schemas?.request?.params && !(0, types_js_1.isZodSchema)(schemas.request.params)) { const validateFn = ajv.compile({ ...schemas.request.params, components, }); validationMiddlewares.set('params', request => { const isValid = validateFn(request.params); if (!isValid) { return validateFn.errors; } return []; }); } if (schemas?.request?.query && !(0, types_js_1.isZodSchema)(schemas.request.query)) { const validateFn = ajv.compile({ ...schemas.request.query, components, }); validationMiddlewares.set('query', request => { const isValid = validateFn(request.query); if (!isValid) { return validateFn.errors; } return []; }); } if (schemas?.request?.json && !(0, types_js_1.isZodSchema)(schemas.request.json)) { const validateFn = ajv.compile({ ...schemas.request.json, components, }); validationMiddlewares.set('json', async (request) => { const contentType = request.headers.get('content-type'); if (contentType?.includes('json')) { const jsonObj = await request.json(); Object.defineProperty(request, 'json', { value: async () => jsonObj, configurable: true, }); const isValid = validateFn(jsonObj); if (!isValid) { return validateFn.errors; } } return []; }); } if (schemas?.request?.formData && !(0, types_js_1.isZodSchema)(schemas.request.formData)) { const validateFn = ajv.compile({ ...schemas.request.formData, components, }); validationMiddlewares.set('formData', async (request) => { const contentType = request.headers.get('content-type'); if (contentType?.includes('multipart/form-data') || contentType?.includes('application/x-www-form-urlencoded')) { const formData = await request.formData(); const formDataObj = {}; const jobs = []; formData.forEach((value, key) => { if (typeof value === 'string') { formDataObj[key] = value; } else { jobs.push(value.arrayBuffer().then(buffer => { const typedArray = new Uint8Array(buffer); const binaryStrParts = []; typedArray.forEach((byte, index) => { binaryStrParts[index] = String.fromCharCode(byte); }); formDataObj[key] = binaryStrParts.join(''); })); } }); await Promise.all(jobs); Object.defineProperty(request, 'formData', { value: async () => formData, configurable: true, }); const isValid = validateFn(formDataObj); if (!isValid) { return validateFn.errors; } } return []; }); } if (fast_json_stringify_1.default && schemas?.responses) { const serializerByStatusCode = new Map(); for (const statusCode in schemas.responses) { const schema = schemas.responses[statusCode]; if (!(0, types_js_1.isZodSchema)(schema)) { const serializer = (0, fast_json_stringify_1.default)({ ...schema, components, }, { ajv, }); serializerByStatusCode.set(Number(statusCode), serializer); } } serializersByPath.set(path, serializerByStatusCode); } if (validationMiddlewares.size > 0) { handlers.unshift(async (request) => { const validationErrorsNonFlat = await Promise.all([...validationMiddlewares.entries()].map(async ([name, fn]) => { const errors = await fn(request); if (errors.length > 0) { return errors.map(error => ({ name, ...error, })); } })); const validationErrors = validationErrorsNonFlat.flat().filter(Boolean); if (validationErrors.length > 0) { return Response_js_1.Response.json({ errors: validationErrors, }, { status: 400, headers: { 'x-error-type': 'validation', }, }); } }); } }, onSerializeResponse({ path, lazyResponse }) { const serializers = serializersByPath.get(path); if (serializers) { const serializer = serializers.get(lazyResponse.init?.status || 200); if (serializer) { lazyResponse.resolveWithSerializer(serializer); } } }, }; } exports.useAjv = useAjv;