UNPKG

fets

Version:

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

199 lines (198 loc) • 8.56 kB
import { Response } from '../Response.js'; import swaggerUiHtml from '../swagger-ui-html.js'; const OPTIONAL_KIND = Symbol.for('TypeBox.Optional'); function isOptionalSchema(schema) { return schema[OPTIONAL_KIND] === 'Optional'; } const requestValidationErrorSchema = { title: 'RequestValidationError', type: 'object', properties: { path: { type: 'string', }, value: { anyOf: [ { type: 'string', }, { type: 'number', }, { type: 'boolean', }, { type: 'array', }, { type: 'object', }, ], }, message: { type: 'string', }, name: { type: 'string', }, }, additionalProperties: false, }; export function useOpenAPI({ oasEndpoint, swaggerUIEndpoint, swaggerUIOpts, includeValidationErrors, }) { let paths; return { onRouterInit(router) { if (includeValidationErrors) { const components = (router.openAPIDocument.components = router.openAPIDocument.components || {}); const schemas = (components.schemas = components.schemas || {}); schemas.RequestValidationError = requestValidationErrorSchema; } paths = router.openAPIDocument.paths = router.openAPIDocument.paths || {}; if (oasEndpoint) { router.route({ method: 'GET', path: oasEndpoint, internal: true, handler: () => Response.json(router.openAPIDocument), }); } if (swaggerUIEndpoint) { router.route({ method: 'GET', path: swaggerUIEndpoint, internal: true, handler: () => new Response(swaggerUiHtml.replace('__SWAGGER_UI_OPTIONS__', JSON.stringify({ spec: router.openAPIDocument, dom_id: '#swagger-ui', displayOperationId: true, tryItOutEnabled: true, requestSnippetsEnabled: true, displayRequestDuration: true, defaultModelRendering: 'model', defaultModelExpandDepth: 3, defaultModelsExpandDepth: 3, ...swaggerUIOpts, })), { headers: { 'Content-Type': 'text/html', }, }), }); } }, onRoute({ route: { method, path, operationId, description, tags, schemas, security } }) { if (schemas) { let pathForOAS = path.replace(/:([^/]+)/g, '{$1}'); if (!pathForOAS.startsWith('/')) { pathForOAS = `/${pathForOAS}`; } const pathObj = (paths[pathForOAS] = paths[pathForOAS] || {}); const lowerCasedMethod = method.toLowerCase(); pathObj[lowerCasedMethod] = pathObj[lowerCasedMethod] || {}; const operation = pathObj[lowerCasedMethod]; operation.operationId = operationId; operation.description = description; operation.tags = tags; operation.security = security; let isRequestValidated = false; if (schemas.request?.headers) { isRequestValidated = true; const headersSchema = schemas.request.headers; for (const headerName in headersSchema.properties) { const headerSchema = headersSchema.properties[headerName]; operation.parameters = operation.parameters || []; operation.parameters.push({ name: headerName, in: 'header', required: headersSchema.required?.includes(headerName), schema: headerSchema, }); } } if (schemas.request?.params) { isRequestValidated = true; const paramsSchema = schemas.request.params; for (const paramName in paramsSchema.properties) { const paramSchema = paramsSchema.properties[paramName]; operation.parameters = operation.parameters || []; operation.parameters.push({ name: paramName, in: 'path', required: paramsSchema.required?.includes(paramName), schema: paramSchema, }); } } if (schemas.request?.query) { const queriesSchema = schemas.request.query; for (const queryName in queriesSchema.properties) { const querySchema = queriesSchema.properties[queryName]; operation.parameters = operation.parameters || []; operation.parameters.push({ name: queryName, in: 'query', required: queriesSchema.required?.includes(queryName), schema: querySchema, }); } } if (schemas.request?.json) { isRequestValidated = true; const requestJsonSchema = schemas.request.json; const requestBody = (operation.requestBody = (operation.requestBody || {})); requestBody.required = !isOptionalSchema(requestJsonSchema); const requestBodyContent = (requestBody.content = (requestBody.content || {})); requestBodyContent['application/json'] = { schema: requestJsonSchema, }; } if (schemas.request?.formData) { isRequestValidated = true; const requestFormDataSchema = schemas.request.formData; const requestBody = (operation.requestBody = (operation.requestBody || {})); requestBody.required = !isOptionalSchema(requestFormDataSchema); const requestBodyContent = (requestBody.content = (requestBody.content || {})); requestBodyContent['multipart/form-data'] = { schema: requestFormDataSchema, }; } if (schemas.responses) { for (const statusCode in schemas.responses) { const responseSchema = schemas.responses[statusCode]; operation.responses ||= {}; operation.responses[statusCode] = { description: '', content: { 'application/json': { schema: responseSchema, }, }, }; } } else { operation.responses = { default: { description: '', }, }; } if (includeValidationErrors && isRequestValidated && !operation.responses?.[400]) { operation.responses ||= {}; operation.responses[400] = { description: 'Request validation failed', content: { 'application/json': { schema: { $ref: '#/components/schemas/RequestValidationError', }, }, }, }; } } }, }; }