fets
Version:
TypeScript HTTP Framework focusing on e2e type-safety, easy setup, performance & great developer experience
199 lines (198 loc) • 8.56 kB
JavaScript
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',
},
},
},
};
}
}
},
};
}