Files
opencloud/services/groupware/apidoc-process.ts

208 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string>()
const unresolvedExampleReferences = new Set<string>()
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<string>(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")
}
}
})