mirror of
https://github.com/hanzoai/trpc-openapi.git
synced 2026-10-30 18:18:55 +00:00
126 lines
5.9 KiB
JavaScript
126 lines
5.9 KiB
JavaScript
import { TRPCError } from '@trpc/server';
|
|
import { z } from 'zod';
|
|
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',
|
|
});
|
|
}
|
|
// When no output parser is defined, use empty object schema (procedure still included in OpenAPI doc)
|
|
const responseSchema = instanceofZodType(outputParser) ? outputParser : z.object({});
|
|
// Request body is required only when the input schema does not accept undefined (e.g. not .optional())
|
|
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(responseSchema, 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
|