Created
September 23, 2025 19:59
-
-
Save bricejulia/41c662c9838b3dad29ae678ee6dec11d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import fs from 'fs'; | |
| function resolveAllOf(schema, definitions = {}, processedRefs = new Set()) { | |
| if (!schema || typeof schema !== 'object') return schema; | |
| if (schema.allOf) { | |
| // Merge all schemas in allOf | |
| const merged = {type: 'object', properties: {}}; | |
| schema.allOf.forEach(subSchema => { | |
| if (subSchema.$ref) { | |
| // Resolve reference | |
| const refPath = subSchema.$ref.replace('#/components/schemas/', ''); | |
| const resolvedSchema = definitions[refPath]; | |
| if (resolvedSchema && !processedRefs.has(refPath)) { | |
| processedRefs.add(refPath); | |
| const resolvedContent = resolveAllOf(resolvedSchema, definitions, processedRefs); | |
| processedRefs.delete(refPath); | |
| mergeSchemas(merged, resolvedContent); | |
| } | |
| } else { | |
| const resolvedSubSchema = resolveAllOf(subSchema, definitions, processedRefs); | |
| mergeSchemas(merged, resolvedSubSchema); | |
| } | |
| }); | |
| // Merge any additional properties from the parent schema | |
| const {allOf, ...rest} = schema; | |
| mergeSchemas(merged, rest); | |
| return merged; | |
| } | |
| // Recursively process nested objects and arrays | |
| const result = {...schema}; | |
| Object.keys(result).forEach(key => { | |
| if (Array.isArray(result[key])) { | |
| result[key] = result[key].map(item => resolveAllOf(item, definitions, processedRefs)); | |
| } else if (typeof result[key] === 'object' && result[key] !== null && key !== 'properties') { | |
| result[key] = resolveAllOf(result[key], definitions, processedRefs); | |
| } | |
| }); | |
| return result; | |
| } | |
| function mergeSchemas(target, source) { | |
| if (!source || typeof source !== 'object') return target; | |
| // Merge properties | |
| if (source.properties) { | |
| target.properties = {...target.properties, ...source.properties}; | |
| } | |
| // Merge required arrays | |
| if (source.required) { | |
| target.required = [ | |
| ...(target.required || []), | |
| ...source.required.filter(req => !(target.required || []).includes(req)) | |
| ]; | |
| } | |
| // Copy other properties, giving priority to source | |
| Object.keys(source).forEach(key => { | |
| if (key !== 'properties' && key !== 'required') { | |
| if (target[key] && typeof target[key] === 'object' && typeof source[key] === 'object' && !Array.isArray(source[key])) { | |
| target[key] = {...target[key], ...source[key]}; | |
| } else { | |
| target[key] = source[key]; | |
| } | |
| } | |
| }); | |
| let sourceRequired = []; | |
| if (source.properties) { | |
| sourceRequired = source.required ?? Object.keys(source.properties).map(key => key); | |
| } | |
| let targetRequired = []; | |
| if (target.properties) { | |
| targetRequired = target.required ?? Object.keys(target.properties).map(key => key); | |
| } | |
| target.required = [...new Set([...sourceRequired, ...targetRequired])]; | |
| return target; | |
| } | |
| function processPathsAllOf(paths, definitions = {}) { | |
| if (!paths || typeof paths !== 'object') return paths; | |
| const processedPaths = {}; | |
| Object.keys(paths).forEach(pathKey => { | |
| const path = paths[pathKey]; | |
| const processedPath = {}; | |
| Object.keys(path).forEach(methodKey => { | |
| const method = path[methodKey]; | |
| const processedMethod = {...method}; | |
| // Process responses | |
| if (method.responses) { | |
| processedMethod.responses = {}; | |
| Object.keys(method.responses).forEach(statusCode => { | |
| const response = method.responses[statusCode]; | |
| processedMethod.responses[statusCode] = {...response}; | |
| // Process content schemas | |
| if (response.content) { | |
| processedMethod.responses[statusCode].content = {}; | |
| Object.keys(response.content).forEach(mediaType => { | |
| const mediaContent = response.content[mediaType]; | |
| processedMethod.responses[statusCode].content[mediaType] = { | |
| ...mediaContent, | |
| schema: resolveAllOf(mediaContent.schema, definitions) | |
| }; | |
| }); | |
| } | |
| }); | |
| } | |
| // Process requests body schemas if they exist | |
| if (method.requestBody && method.requestBody.content) { | |
| processedMethod.requestBody = {...method.requestBody, content: {}}; | |
| Object.keys(method.requestBody.content).forEach(mediaType => { | |
| const mediaContent = method.requestBody.content[mediaType]; | |
| processedMethod.requestBody.content[mediaType] = { | |
| ...mediaContent, | |
| schema: resolveAllOf(mediaContent.schema, definitions) | |
| }; | |
| }); | |
| } | |
| // Process parameters if they have schemas with allOf | |
| if (method.parameters) { | |
| processedMethod.parameters = method.parameters.map(param => ({ | |
| ...param, | |
| schema: param.schema ? resolveAllOf(param.schema, definitions) : param.schema | |
| })); | |
| } | |
| processedPath[methodKey] = processedMethod; | |
| }); | |
| processedPaths[pathKey] = processedPath; | |
| }); | |
| return processedPaths; | |
| } | |
| function preprocessOpenAPI(spec) { | |
| const processed = {...spec}; | |
| // First, process schemas in components to resolve all allOf | |
| if (processed.components && processed.components.schemas) { | |
| const schemas = processed.components.schemas; | |
| const definitions = {...schemas}; // Keep original references | |
| // Process each schema to resolve allOf | |
| Object.keys(schemas).forEach(key => { | |
| schemas[key] = resolveAllOf(schemas[key], definitions); | |
| }); | |
| } | |
| // Then, process allOf in paths using the updated schemas | |
| if (processed.paths) { | |
| processed.paths = processPathsAllOf(processed.paths, processed.components?.schemas || {}); | |
| } | |
| return processed; | |
| } | |
| // Read and process the spec | |
| const specPath = process.argv[2] || 'openapi.json'; | |
| let spec; | |
| // specPath can be a path or a URL | |
| if (specPath.startsWith('http')) { | |
| try { | |
| const response = await fetch(specPath); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| spec = await response.json(); | |
| } catch (error) { | |
| console.error(`Error fetching spec from URL: ${error.message}`); | |
| process.exit(1); | |
| } | |
| } else { | |
| try { | |
| const specContent = fs.readFileSync(specPath, 'utf8'); | |
| spec = JSON.parse(specContent); | |
| } catch (error) { | |
| console.error(`Error reading spec file: ${error.message}`); | |
| process.exit(1); | |
| } | |
| } | |
| const processedSpec = preprocessOpenAPI(spec); | |
| // Determine an output format based on input | |
| const outputPath = 'openapi-processed.json'; | |
| fs.writeFileSync(outputPath, JSON.stringify(processedSpec, null, 2)); | |
| console.log(`Processed OpenAPI spec written to ${outputPath}`); | |
| console.log(`Found and resolved allOf schemas in both components and paths sections.`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment