Files
netbird-docs/generator/api.ts
pascal-fischer f33b4df3dd Add workflow for api gen and refactor genration script (#60)
* update gitignore

* add first version of api generation flow

* try to run flow

* testing flow

* update gen flow

* switch flow to macOS

* fix

* add npm install

* test workflow

* Update API pages

* test workflow with current main

* refactor parser

* merge examples and schema functions

* merge examples and schema functions

* merge parameters as well

* revert template to single class component

* update account

* finalizing

* update with the newest version of openapi

* full flow

* update

* add docker command

* split flow in two jobs

* remove testing trigger

---------

Co-authored-by: GitHub Actions <no-reply@github.com>
2023-06-05 09:35:54 +02:00

219 lines
7.0 KiB
TypeScript

import template from './templates/ApiTemplate'
import { slugify, toArrayWithKey, toTitle, writeToDisk } from './helpers'
import {OpenAPIV3} from 'openapi-types'
import * as fs from 'fs'
import * as ejs from 'ejs'
export default async function gen(inputFileName: string, outputDir: string) {
const specRaw = fs.readFileSync(inputFileName, 'utf8')
const spec = JSON.parse(specRaw) as any
switch (spec.openapi || spec.swagger) {
case '3.0.0':
case '3.0.1':
case '3.0.3':
await gen_v3(spec, outputDir)
break
default:
console.log('Unrecognized specification version:', spec.openapi)
break
}
}
/**
* Versioned Generator
*/
// OPENAPI-SPEC-VERSION: 3.0.0
type v3OperationWithPath = OpenAPIV3.OperationObject & {
path: string
}
export type enrichedOperation = OpenAPIV3.OperationObject & {
path: string
fullPath: string
operationId: string
}
export type schemaParameter = {
name: string
type: string
description: string
required: boolean
minimum?: number
maximum?: number
minLength?: number
maxLength?: number
enum?: string[]
}
export type component = {
example: Object
schema: Object
parameters: schemaParameter[]
}
async function gen_v3(spec: OpenAPIV3.Document, dest: string) {
const specLayout = spec.tags || []
// const operations: enrichedOperation[] = []
const tagGroups = new Map<string, enrichedOperation[]>()
const server = spec.servers.pop().url
Object.entries(spec.paths).forEach(([key, val]) => {
const fullPath = `${server}${key}`
toArrayWithKey(val!, 'operation').forEach((o) => {
const operation = o as v3OperationWithPath
const enriched = {
...operation,
path: key,
fullPath,
operationId: slugify(operation.summary!),
responseList: toArrayWithKey(operation.responses!, 'responseCode') || [],
}
let tag = operation.tags.pop()
let tagOperations = tagGroups.get(tag) ?? []
tagGroups.set(tag, tagOperations.concat(enriched))
})
})
let components = readComponents(spec.components)
tagGroups.forEach((value: enrichedOperation[], key: string) => {
const operations = value
const sections = specLayout.map((section) => {
return {
...section,
title: toTitle(section.name),
id: slugify(section.name),
operations: operations,
}
})
const content = ejs.render(template, {
info: spec.info,
tag: key,
sections,
operations,
components,
})
// Write to disk
let outputFile = dest + "/" + key.toLowerCase().replace(" ", "-") + ".mdx"
writeToDisk(outputFile, content)
// console.log('Saved: ', outputFile)
})
}
function readComponents(components: OpenAPIV3.ComponentsObject) : Map<string, component> {
let componentsOutput = new Map<string, component>()
for (const [key, value] of Object.entries(components.schemas)) {
let [schema, example, parameter] = resolveComponents(value, components)
let component = {
example: example,
schema: schema,
parameters: parameter
}
componentsOutput.set(key, component)
}
return componentsOutput
}
function resolveComponents(value: OpenAPIV3.ReferenceObject | OpenAPIV3.ArraySchemaObject | OpenAPIV3.NonArraySchemaObject, components: OpenAPIV3.ComponentsObject) : [Object, Object, schemaParameter[]] {
if((value as OpenAPIV3.ReferenceObject).$ref) {
let subcomponentName = (value as OpenAPIV3.ReferenceObject).$ref.split('/').pop()
let subcomponent = components.schemas[subcomponentName]
return resolveComponents(subcomponent, components)
}
if((value as OpenAPIV3.SchemaObject).properties) {
return resolveProperties(value as OpenAPIV3.SchemaObject, components)
}
if((value as OpenAPIV3.SchemaObject).allOf) {
return resolveAllOf(value as OpenAPIV3.SchemaObject, components)
}
if((value as OpenAPIV3.SchemaObject).type || (value as OpenAPIV3.SchemaObject).example) {
return [(value as OpenAPIV3.SchemaObject).type, (value as OpenAPIV3.SchemaObject).example, null]
}
}
function resolveAllOf(object: OpenAPIV3.SchemaObject, components: OpenAPIV3.ComponentsObject) : [Object, Object, schemaParameter[]] {
let examples = new Map<string, any>()
let schemas = new Map<string, any>()
let parameters: schemaParameter[] = []
for (const [key, value] of Object.entries(object.allOf)) {
let example;
let schema;
let parameter;
if((value as OpenAPIV3.ReferenceObject).$ref) {
let subcomponentName = (value as OpenAPIV3.ReferenceObject).$ref.split('/').pop()
let subcomponent = components.schemas[subcomponentName];
[schema, example, parameter] = resolveComponents(subcomponent, components)
}
if((value as OpenAPIV3.SchemaObject).properties) {
[schema, example, parameter] = resolveProperties(value as OpenAPIV3.SchemaObject, components)
}
if(!(example instanceof Map)) {
example = new Map(Object.entries(example))
}
if(!(schema instanceof Map)) {
schema = new Map(Object.entries(schema))
}
parameters = parameters.concat(parameter)
examples = mergeMaps(examples, example)
schemas = mergeMaps(schemas, schema)
}
return [Object.fromEntries(schemas), Object.fromEntries(examples), parameters]
}
function resolveProperties(value: OpenAPIV3.SchemaObject, components: OpenAPIV3.ComponentsObject): [Object, Object, schemaParameter[]] {
let examples = new Map<string, Object>()
let schemas = new Map<string, Object>()
let parameters: schemaParameter[] = []
for(const [key, property] of Object.entries(value.properties)) {
let type: string = ""
if(property["$ref"]) {
let [schema, example, parameter] = resolveComponents(property, components)
examples.set(key, example)
schemas.set(key, schema)
parameters = parameters.concat(parameter)
continue
}
switch (property["type"]) {
case "array":
type = ((property["items"] as OpenAPIV3.SchemaObject).type || (property["items"] as OpenAPIV3.ReferenceObject).$ref.split('/').pop()) + "[]"
let [schema, example] = resolveComponents(property["items"], components)
examples.set(key, new Array(example))
schemas.set(key, new Array(schema))
break;
case "object":
default:
type = property["type"]
examples.set(key, property["example"])
schemas.set(key, property["type"])
}
let parameter: schemaParameter = {
name: key,
type: type,
description: property["description"],
required: value.required?.includes(key) || false,
minimum: property["minimum"],
maximum: property["maximum"],
minLength: property["minLength"],
maxLength: property["maxLength"],
enum: property["enum"],
}
parameters.push(parameter)
}
return [Object.fromEntries(schemas), Object.fromEntries(examples), parameters]
}
function mergeMaps(map1: Map<string, Object>, map2: Map<string, Object>) : Map<string, Object> {
return new Map([...Array.from(map1.entries()), ...Array.from(map2.entries())]);
}