UNPKG

burger-api

Version:

<div align="center"> <a href="https://burger-api.com"> <img src="https://github.com/user-attachments/assets/0d9b376e-1d89-479a-aa7f-e7ee3c6b2342" alt="BurgerAPI"/> </a> </div>

170 lines (169 loc) 7.14 kB
// Import stuff from Zod 4.x import { toJSONSchema, ZodArray, ZodBoolean, ZodNumber, ZodObject, ZodOptional, ZodString, ZodType, } from 'zod'; /** * Maps a Zod type to an OpenAPI schema type. * @param zodType - The Zod type to map. * @returns The OpenAPI schema type. */ function mapZodTypeToOpenAPIType(zodType) { if (zodType instanceof ZodOptional) { return mapZodTypeToOpenAPIType(zodType.unwrap()); } if (zodType instanceof ZodString) return 'string'; if (zodType instanceof ZodNumber) return 'number'; if (zodType instanceof ZodBoolean) return 'boolean'; if (zodType instanceof ZodArray) return 'array'; if (zodType instanceof ZodObject) return 'object'; return 'string'; // fallback } /** * Builds an array of OpenAPI 3.0 parameters based on the Zod schema. * For each property in the Zod schema, an OpenAPI parameter is constructed * with the same name and required flag. The schema of the parameter is set * to a string type. * @param zodSchema - The Zod schema to construct parameters from. Can be undefined. * @param location - The location of the parameter. Must be either "path" or "query". * @returns An array of OpenAPI 3.0 parameter objects or an empty array if the Zod schema is undefined. */ function buildParameters(zodSchema, location) { const parameters = []; if (isZodObjectSchema(zodSchema)) { // Get the shape of the Zod schema const shape = zodSchema.shape; for (const key in shape) { // Get the definition of the field const fieldDef = shape[key]; // Determine if the field is optional const isOptional = fieldDef instanceof ZodOptional; // Map the Zod type to an OpenAPI schema type const type = mapZodTypeToOpenAPIType(fieldDef); parameters.push({ // Set the name of the parameter name: key, // Type of the parameter path or query in: location, // Set the required flag required: !isOptional, // Set the schema type schema: { type }, // Description of the parameter description: `${location} parameter ${key}`, }); } } return parameters; } function isZodObjectSchema(value) { return value instanceof ZodObject; } /** * Builds a request body object for OpenAPI based on a Zod schema. * Converts the Zod schema into JSON schema and constructs an OpenAPI * requestBody object with content type "application/json". * @param zodSchema - The Zod schema to convert into JSON schema. * @returns An OpenAPI requestBody object, or undefined if no schema is provided. */ function buildRequestBody(zodSchema) { // If no schema is provided, return undefined if (!(zodSchema instanceof ZodType)) return undefined; // Convert the Zod schema to a JSON schema const jsonSchema = toJSONSchema(zodSchema); // Return the OpenAPI requestBody object return { content: { 'application/json': { schema: jsonSchema, }, }, description: 'Request body', required: true, }; } /** * Converts a route path from colon-based dynamic segments to OpenAPI's curly brace syntax. * Also handles wildcard routes. * * @param routePath The original route path with colon-based dynamic segments (e.g., "/user/:id") or wildcards (e.g., "/files/*"). * @returns The converted route path with curly brace syntax (e.g., "/user/{id}") or wildcard format (e.g., "/files/*"). */ function convertPathForOpenAPI(routePath) { // Fast path: if no special chars, return as-is if (routePath.indexOf(':') === -1 && routePath.indexOf('*') === -1) { return routePath; } // Replace occurrences of :param with {param} let converted = routePath.replace(/:([a-zA-Z0-9_]+)/g, '{$1}'); // Handle wildcard routes // OpenAPI 3.0 doesn't have official wildcard syntax, so we keep /* as-is // This will display in Swagger as /api/files/* which is intuitive and common // Alternative: convert to {path*} format if preferred // converted = converted.replace(/\/\*$/g, '/{path*}'); return converted; } export function generateOpenAPIDocument(apiRoutes, options) { const openapiDoc = { openapi: '3.0.0', info: { title: options.title || 'Burger API', description: options.description || 'Burger API documentation', version: options.version || '1.0.0', }, paths: {}, }; // Iterate over each route for (const route of apiRoutes) { // Convert colon-based dynamic segments to OpenAPI's {param} syntax const openApiPath = convertPathForOpenAPI(route.path); // Initialize path object if necessary openapiDoc.paths[openApiPath] = openapiDoc.paths[openApiPath] || {}; // For each HTTP method in the route, add an OpenAPI operation. for (const method in route.handlers) { // If the handler is not a function, skip if (typeof route.handlers[method] !== 'function') continue; // Convert HTTP method to lowercase const lowerMethod = method.toLowerCase(); // Use provided openapi metadata if available; else, fallback to auto-generated values. const methodMeta = route.openapi?.[lowerMethod] || {}; // Generate an operationId: e.g., "get_api_product" const operationId = methodMeta.operationId || `${lowerMethod}_${route.path.replace(/[\/:]/g, '_')}`; // Build parameters for both path and query from the schema. let parameters = []; if (route.schema && route.schema[lowerMethod]) { const schemaDef = route.schema[lowerMethod]; parameters = [ ...buildParameters(schemaDef.params, 'path'), ...buildParameters(schemaDef.query, 'query'), ]; } // Build requestBody if a body schema exists. let requestBody = undefined; if (route.schema && route.schema[lowerMethod]?.body) { requestBody = buildRequestBody(route.schema[lowerMethod].body); } openapiDoc.paths[openApiPath][lowerMethod] = { operationId, summary: methodMeta.summary || `Summary for ${method} ${route.path}`, description: methodMeta.description || '', tags: methodMeta.tags || [], deprecated: methodMeta.deprecated || false, parameters: parameters, requestBody: requestBody, responses: methodMeta.responses || { '200': { description: 'Successful response', }, }, externalDocs: methodMeta.externalDocs || undefined, }; } } return openapiDoc; }