import * as fs from 'fs' import * as yaml from 'js-yaml' const API_PARAMS_CONFIG_FILE = 'api-params.yaml' const API_EXAMPLES_CONFIG_FILE = 'api-examples.yaml' interface Response { $ref: string } interface Parameter { type: string required: boolean format: string example: any name: string description: string in: string } interface VerbData { tags: string[] summary: string description: string | undefined operationId: string parameters: Parameter[] responses: {[status:string]:Response} } interface Item { $ref: string } interface AdditionalProperties { $ref: string } interface Property { description: string type: string items: Item example: any additionalProperties: AdditionalProperties } interface Definition { type: string title: string required: string[] properties: {[property:string]:Property} example: string examples: string[] } interface OpenApi { paths: {[path:string]:{[verb:string]:VerbData}} definitions: {[type:string]:Definition} } interface Param { description: string type: string } interface ParamsConfig { params: {[param:string]:Param} } interface ExamplesConfigExamples { refs: {[id:string]:any} inject: {[id:string]:{[property:string]:any}} } interface ExamplesConfig { examples: ExamplesConfigExamples } let inputData = '' process.stdin.on('data', (chunk) => { inputData += chunk.toString() }) const usedExamples = new Set() const unresolvedExampleReferences = new Set() function processDescription(description: string|null|undefined): string|null|undefined { if (description !== null && description !== undefined) { return description.split("\n").map(line => line.replace(/^(\s*)![\*\-]?/, '$1*')).join("\n") } else { return description } } process.stdin.on('end', () => { try { const paramsConfig = yaml.load(fs.readFileSync(API_PARAMS_CONFIG_FILE, 'utf8')) as ParamsConfig const params = paramsConfig.params || {} const examplesConfig = yaml.load(fs.readFileSync(API_EXAMPLES_CONFIG_FILE, 'utf8')) as ExamplesConfig const exampleRefs = examplesConfig.examples.refs const exampleInjects = examplesConfig.examples.inject const data = yaml.load(inputData) as OpenApi for (const path in data.paths) { const pathData = data.paths[path] for (const param in params) { if (path.includes(`{${param}}`)) { const paramsData = params[param] as Param for (const verb in pathData) { const verbData = pathData[verb] verbData.parameters ??= [] verbData.parameters.push({ name: param, required: true, type: paramsData.type !== undefined ? paramsData.type : 'string', in: 'path', description: paramsData.description, } as Parameter) } } } // do some magic with the formatting of endpoint descriptions: for (const verb in pathData) { const verbData = pathData[verb] verbData.description = processDescription(verbData.description) } } for (const def in data.definitions) { const defData = data.definitions[def] if (def.startsWith('TypeOf')) { const value = def.substring('TypeOf'.length) defData.title = value defData.example = value } const injects = exampleInjects[def] || {} if (defData.properties !== null && defData.properties !== undefined) { for (const prop in defData.properties as any) { const propData = defData.properties[prop] const inject = injects[prop] if (inject !== null && inject !== undefined) { propData.example = inject } if (propData.example !== null && propData.example !== undefined) { if (typeof propData.example === 'string' && (propData.example as string).startsWith('$')) { const exampleId = propData.example.substring(1) const value = exampleRefs[exampleId] if (value === null || value === undefined) { unresolvedExampleReferences.add(exampleId) } else { usedExamples.add(exampleId) propData.example = value } } } propData.description = processDescription(propData.description) } } else { if (typeof(injects) === 'string') { defData.example = injects } else if (Array.isArray(injects)) { defData.examples = injects } } } process.stdout.write(yaml.dump(data)) process.stdout.write("\n") if (unresolvedExampleReferences.size > 0) { console.error(`\x1b[33;1m⚠️ WARNING: unresolved example references not contained in ${API_PARAMS_CONFIG_FILE}:\x1b[0m`) unresolvedExampleReferences.forEach(item => { console.error(` - ${item}`) }) console.error() } const unusedExampleReferences = new Set(Object.keys(exampleRefs)) usedExamples.forEach(item => { unusedExampleReferences.delete(item) }) if (unusedExampleReferences.size > 0) { console.error(`\x1b[33;1m⚠️ WARNING: unused examples in ${API_EXAMPLES_CONFIG_FILE}:\x1b[0m`) unusedExampleReferences.forEach(item => { console.error(` - ${item}`) }) console.error() } } catch (error) { if (error instanceof Error) { console.error(`Error occured while post-processing OpenAPI: ${error.message}`) } else { console.error("Unknown error occurred") } } })