UNPKG

@furystack/rest

Version:
145 lines 5.7 kB
const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace']; const isReferenceObject = (obj) => typeof obj === 'object' && obj !== null && '$ref' in obj; /** * Converts an OpenAPI `{param}` path format to FuryStack `:param` format. * * @param path - The OpenAPI path (e.g. `/users/{id}`) * @returns The FuryStack path (e.g. `/users/:id`) */ export const convertOpenApiPathToFuryStack = (path) => path.replace(/\{([^{}]+)\}/g, ':$1'); const extractResponseSchema = (operation) => { for (const statusCode of ['200', '201', '2XX', 'default']) { const response = operation.responses?.[statusCode]; if (!response || isReferenceObject(response)) continue; const jsonContent = response.content?.['application/json']; if (jsonContent?.schema) return jsonContent.schema; } return undefined; }; const extractSchemaName = (operation, method, path) => { if (operation.operationId) return operation.operationId; const cleanPath = path .replace(/\{([^{}]+)\}/g, '$1') .replace(/\//g, '_') .replace(/^_/, ''); return `${method}_${cleanPath}`; }; const isOperationAuthenticated = (operation, docSecurity) => { if (operation.security !== undefined) { return operation.security.length > 0; } if (docSecurity !== undefined) { return docSecurity.length > 0; } return false; }; const extractSecuritySchemeNames = (operation, docSecurity) => { const security = operation.security !== undefined ? operation.security : docSecurity; if (!security || security.length === 0) return undefined; const names = security.flatMap((req) => Object.keys(req)); return names.length > 0 ? names : undefined; }; const extractDocumentMetadata = (doc) => { const metadata = {}; let hasMetadata = false; if (doc.info.summary) { metadata.summary = doc.info.summary; hasMetadata = true; } if (doc.info.termsOfService) { metadata.termsOfService = doc.info.termsOfService; hasMetadata = true; } if (doc.info.contact) { metadata.contact = doc.info.contact; hasMetadata = true; } if (doc.info.license) { metadata.license = doc.info.license; hasMetadata = true; } if (doc.servers?.length) { metadata.servers = doc.servers; hasMetadata = true; } if (doc.tags?.length) { metadata.tags = doc.tags; hasMetadata = true; } if (doc.externalDocs) { metadata.externalDocs = doc.externalDocs; hasMetadata = true; } const schemes = doc.components?.securitySchemes; if (schemes) { const resolved = {}; for (const [name, scheme] of Object.entries(schemes)) { if (!isReferenceObject(scheme)) { resolved[name] = scheme; } } if (Object.keys(resolved).length > 0) { metadata.securitySchemes = resolved; hasMetadata = true; } } return hasMetadata ? metadata : undefined; }; /** * Converts an OpenAPI 3.x document to a FuryStack `ApiEndpointSchema`. * * This enables consuming external OpenAPI documents with FuryStack's runtime pipeline. * Preserves operation-level metadata (tags, deprecated, summary, description) and * document-level metadata (servers, tags, contact, license, securitySchemes). * * **Important:** If the document contains `$ref` pointers, call `resolveOpenApiRefs(doc)` * first to inline them. This function does not resolve `$ref` on its own. * * @param doc - The OpenAPI document to convert * @returns An ApiEndpointSchema that can be used with FuryStack's API tools */ export const openApiToSchema = (doc) => { const endpoints = {}; if (doc.paths) { for (const [openApiPath, pathItemOrRef] of Object.entries(doc.paths)) { if (isReferenceObject(pathItemOrRef)) continue; const pathItem = pathItemOrRef; const furyStackPath = convertOpenApiPathToFuryStack(openApiPath); for (const method of HTTP_METHODS) { const operation = pathItem[method]; if (!operation) continue; const upperMethod = method.toUpperCase(); const methodEndpoints = endpoints[upperMethod] ?? {}; endpoints[upperMethod] = methodEndpoints; const responseSchema = extractResponseSchema(operation); const schemaName = extractSchemaName(operation, method, openApiPath); const securitySchemeNames = extractSecuritySchemeNames(operation, doc.security); methodEndpoints[furyStackPath] = { path: furyStackPath, schema: responseSchema ?? {}, schemaName, isAuthenticated: isOperationAuthenticated(operation, doc.security), ...(securitySchemeNames ? { securitySchemes: securitySchemeNames } : {}), ...(operation.tags?.length ? { tags: operation.tags } : {}), ...(operation.deprecated ? { deprecated: true } : {}), ...(operation.summary ? { summary: operation.summary } : {}), ...(operation.description ? { description: operation.description } : {}), }; } } } return { name: doc.info.title, description: doc.info.description ?? '', version: doc.info.version, metadata: extractDocumentMetadata(doc), endpoints, }; }; //# sourceMappingURL=openapi-to-schema.js.map