UNPKG

trpc-to-openapi

Version:
128 lines 5.92 kB
import { TRPCError } from '@trpc/server'; import { acceptsRequestBody, getPathParameters, normalizePath, forEachOpenApiProcedure, getInputOutputParsers, instanceofZodType, instanceofZodTypeLikeVoid, instanceofZodTypeObject, unwrapZodType, } from '../utils/index.mjs'; import { getParameterObjects, getRequestBodyObject, getResponsesObject, hasInputs } from './schema.mjs'; export var HttpMethods; (function (HttpMethods) { HttpMethods["GET"] = "get"; HttpMethods["POST"] = "post"; HttpMethods["PATCH"] = "patch"; HttpMethods["PUT"] = "put"; HttpMethods["DELETE"] = "delete"; })(HttpMethods || (HttpMethods = {})); export const getOpenApiPathsObject = (appRouter, securitySchemeNames, filter) => { const pathsObject = {}; const procedures = Object.assign({}, appRouter._def.procedures); forEachOpenApiProcedure(procedures, ({ path: procedurePath, type, procedure, meta }) => { if (typeof filter === 'function' && !filter({ metadata: meta })) { return; } const procedureName = `${type}.${procedurePath}`; try { if (type === 'subscription') { throw new TRPCError({ message: 'Subscriptions are not supported by OpenAPI v3', code: 'INTERNAL_SERVER_ERROR', }); } const { openapi } = meta; const { method, operationId, summary, description, tags, requestHeaders, responseHeaders, successDescription, errorResponses, protect = true, } = meta.openapi; const path = normalizePath(openapi.path); const pathParameters = getPathParameters(path); const httpMethod = HttpMethods[method]; if (!httpMethod) { throw new TRPCError({ message: 'Method must be GET, POST, PATCH, PUT or DELETE', code: 'INTERNAL_SERVER_ERROR', }); } if (pathsObject[path]?.[httpMethod]) { throw new TRPCError({ message: `Duplicate procedure defined for route ${method} ${path}`, code: 'INTERNAL_SERVER_ERROR', }); } const contentTypes = openapi.contentTypes ?? ['application/json']; if (contentTypes.length === 0) { throw new TRPCError({ message: 'At least one content type must be specified', code: 'INTERNAL_SERVER_ERROR', }); } const { inputParser, outputParser, hasInputsDefined } = getInputOutputParsers(procedure); if (!instanceofZodType(inputParser)) { throw new TRPCError({ message: 'Input parser expects a Zod validator', code: 'INTERNAL_SERVER_ERROR', }); } if (!instanceofZodType(outputParser)) { throw new TRPCError({ message: 'Output parser expects a Zod validator', code: 'INTERNAL_SERVER_ERROR', }); } const isInputRequired = !inputParser.safeParse(undefined).success; const o = inputParser.meta(); const inputSchema = unwrapZodType(inputParser, true).meta({ ...(o?.title ? { title: o?.title } : {}), ...(o?.description ? { description: o?.description } : {}), ...(o?.examples ? { examples: o?.examples } : {}), }); const requestData = {}; const hasPathParameters = pathParameters.length > 0; const hasVoidLikeInput = instanceofZodTypeLikeVoid(inputSchema); if (hasInputsDefined && (hasPathParameters || !hasVoidLikeInput)) { if (!instanceofZodTypeObject(inputSchema)) { throw new TRPCError({ message: 'Input parser must be a ZodObject', code: 'INTERNAL_SERVER_ERROR', }); } if (acceptsRequestBody(method)) { requestData.requestBody = getRequestBodyObject(inputSchema, isInputRequired, pathParameters, contentTypes); requestData.requestParams = getParameterObjects(inputSchema, isInputRequired, pathParameters, requestHeaders, 'path'); } else { requestData.requestParams = getParameterObjects(inputSchema, isInputRequired, pathParameters, requestHeaders, 'all'); } } const responses = getResponsesObject(outputParser, httpMethod, responseHeaders, protect, hasInputs(inputParser), successDescription, errorResponses); const security = protect ? securitySchemeNames.map((name) => ({ [name]: [] })) : undefined; pathsObject[path] = { ...pathsObject[path], [httpMethod]: { operationId: operationId ?? procedurePath.replace(/\./g, '-'), summary, description, tags, security, ...requestData, responses, ...(openapi.deprecated ? { deprecated: openapi.deprecated } : {}), }, }; } catch (error) { if (error instanceof TRPCError) { error.message = `[${procedureName}] - ${error.message}`; } throw error; } }); return pathsObject; }; export const mergePaths = (x, y) => { if (x === undefined) return y; if (y === undefined) return x; const obj = x; for (const [k, v] of Object.entries(y)) { if (k in obj) obj[k] = { ...obj[k], ...v }; else obj[k] = v; } return obj; }; //# sourceMappingURL=paths.js.map