UNPKG

fastify-openapi-connector

Version:

Fastify plugin that will set-up routes with security & json validation based on OpenAPI specification

87 lines (86 loc) 4.01 kB
import { createSecurityProcessors } from './createSecurityProcessors.js'; import { defaultHandler } from './defaultOperationHandler.js'; import { parseParams } from './parseParams.js'; import { createRouteSchema } from './routeSchema.js'; // TypeGuard to check extension x-security object fulfills the SecurityObject specification export const validateSecurityObject = (security) => { if (typeof security !== 'object' || !Array.isArray(security)) { return false; } for (const item of security) { for (const [key, value] of Object.entries(item)) { if (typeof key !== 'string' || !Array.isArray(value) || !value.every((scope) => typeof scope === 'string')) { return false; } } } return true; }; export const fixEmptyResponses = (responses) => { if (!responses) { return undefined; } const copy = structuredClone(responses); for (const response of Object.values(copy)) { if (!response.content) { response.type = 'null'; } } return copy; }; export const setupRoutes = (fastify, routesInfo, settings) => { const schemaParameters = routesInfo.components?.parameters ?? {}; for (const [path, pathObject] of Object.entries(routesInfo.paths)) { let url = path; if (settings.isWebhook) { if ('$ref' in pathObject) { fastify.log.error(`Webhook path ${path} is a reference, references need to be resolved for the plugin to work!`); continue; } if (!path.startsWith('/')) { fastify.log.warn(`Webhook path ${path} does not start with a slash, slash will be added.`); url = `/${url}`; } } const { parameters, 'x-security': xSecurity, ...methods } = pathObject; let routeSecurity = undefined; if (settings.useXSecurity === true && xSecurity) { if (validateSecurityObject(xSecurity)) { routeSecurity = xSecurity; } else { fastify.log.warn(`${path} - x-security is not a valid SecurityObject! Will not be used.`); } } const params = parseParams(parameters ?? [], schemaParameters); for (const [method, operation] of Object.entries(methods)) { // Skip extensions if (method.startsWith('x-')) { continue; } const { parameters, operationId, requestBody, security: operationSecurity, responses, ...operationValues } = operation; if (!operationId) { fastify.log.error(`${path} - ${method} is missing operationId! Will be skipped.`); continue; } // It is safe to retype since, TypedRequest & TypedReply are FastifyRequest & FastifyReply with generic parameters. let handler = routesInfo.operationHandlers[operationId]; if (!handler) { fastify.log.warn(`${path} - ${method} has no handler! Will use default handler.`); handler = defaultHandler; } // Overrides any path params already defined const operationParams = parseParams(parameters ?? [], schemaParameters, structuredClone(params)); fastify.route({ method: method.toUpperCase(), // fastify wants 'path/:param' instead of openapis 'path/{param}' url: url.replace(/{(\w+)}/g, ':$1'), handler, config: operationValues['x-fastify-config'], schema: createRouteSchema(operationParams, settings.contentTypes, requestBody, fixEmptyResponses(responses), settings.validateResponse), // Operation security overrides global security preParsing: createSecurityProcessors(routesInfo.securityHandlers ?? {}, operationSecurity ?? routeSecurity ?? routesInfo.globalSecurity), }); } } };