@furystack/rest
Version:
Generic REST package
145 lines • 5.7 kB
JavaScript
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