Skip to content

Instantly share code, notes, and snippets.

@bricejulia
Created September 23, 2025 19:59
Show Gist options
  • Select an option

  • Save bricejulia/41c662c9838b3dad29ae678ee6dec11d to your computer and use it in GitHub Desktop.

Select an option

Save bricejulia/41c662c9838b3dad29ae678ee6dec11d to your computer and use it in GitHub Desktop.
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