generate openapi documentation

This commit is contained in:
Flam3rboy
2021-09-21 22:52:30 +02:00
parent eb2f447d96
commit 2a094c603a
19 changed files with 7419 additions and 1052 deletions

View File

@@ -4,9 +4,9 @@ import path from "path";
import fs from "fs";
import * as TJS from "typescript-json-schema";
import "missing-native-js-functions";
const schemaPath = path.join(__dirname, "..", "assets", "responses.json");
const schemaPath = path.join(__dirname, "..", "assets", "schemas.json");
const settings: TJS.PartialArgs = {
const settings = {
required: true,
ignoreErrors: true,
excludePrivate: true,
@@ -14,10 +14,13 @@ const settings: TJS.PartialArgs = {
noExtraProps: true,
defaultProps: false
};
const compilerOptions: TJS.CompilerOptions = {
const compilerOptions = {
strictNullChecks: true
};
const ExcludedSchemas = [
const Excluded = [
"DefaultSchema",
"Schema",
"EntitySchema",
"ServerResponse",
"Http2ServerResponse",
"global.Express.Response",
@@ -32,13 +35,13 @@ function main() {
const generator = TJS.buildGenerator(program, settings);
if (!generator || !program) return;
const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x));
const schemas = generator.getUserSymbols().filter((x) => (x.endsWith("Schema") || x.endsWith("Response")) && !Excluded.includes(x));
console.log(schemas);
var definitions: any = {};
var definitions = {};
for (const name of schemas) {
const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator);
const part = TJS.generateSchema(program, name, settings, [], generator);
if (!part) continue;
definitions = { ...definitions, [name]: { ...part } };
@@ -47,11 +50,10 @@ function main() {
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
}
// #/definitions/
main();
function walk(dir: string) {
var results = [] as string[];
function walk(dir) {
var results = [];
var list = fs.readdirSync(dir);
list.forEach(function (file) {
file = dir + "/" + file;

View File

@@ -1,60 +0,0 @@
// https://mermade.github.io/openapi-gui/#
// https://editor.swagger.io/
import path from "path";
import fs from "fs";
import * as TJS from "typescript-json-schema";
import "missing-native-js-functions";
const schemaPath = path.join(__dirname, "..", "assets", "schemas.json");
const settings: TJS.PartialArgs = {
required: true,
ignoreErrors: true,
excludePrivate: true,
defaultNumberType: "integer",
noExtraProps: true,
defaultProps: false
};
const compilerOptions: TJS.CompilerOptions = {
strictNullChecks: true
};
const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"];
function main() {
const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions);
const generator = TJS.buildGenerator(program, settings);
if (!generator || !program) return;
const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x));
console.log(schemas);
var definitions: any = {};
for (const name of schemas) {
const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator);
if (!part) continue;
definitions = { ...definitions, [name]: { ...part } };
}
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
}
// #/definitions/
main();
function walk(dir: string) {
var results = [] as string[];
var list = fs.readdirSync(dir);
list.forEach(function (file) {
file = dir + "/" + file;
var stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
/* Recurse into a subdirectory */
results = results.concat(walk(file));
} else {
if (!file.endsWith(".ts")) return;
results.push(file);
}
});
return results;
}

View File

@@ -0,0 +1,127 @@
// https://mermade.github.io/openapi-gui/#
// https://editor.swagger.io/
const getRouteDescriptions = require("../jest/getRouteDescriptions");
const path = require("path");
const fs = require("fs");
require("missing-native-js-functions");
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
const specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" }));
function combineSchemas(schemas) {
var definitions = {};
for (const name in schemas) {
definitions = {
...definitions,
...schemas[name].definitions,
[name]: { ...schemas[name], definitions: undefined, $schema: undefined }
};
}
for (const key in definitions) {
specification.components.schemas[key] = definitions[key];
delete definitions[key].additionalProperties;
delete definitions[key].$schema;
const definition = definitions[key];
if (typeof definition.properties === "object") {
for (const property of Object.values(definition.properties)) {
if (Array.isArray(property.type)) {
if (property.type.includes("null")) {
property.type = property.type.find((x) => x !== "null");
property.nullable = true;
}
}
}
}
}
return definitions;
}
function getTag(key) {
return key.match(/\/([\w-]+)/)[1];
}
function apiRoutes() {
const routes = getRouteDescriptions();
const tags = Array.from(routes.keys()).map((x) => getTag(x));
specification.tags = [...specification.tags.map((x) => x.name), ...tags].unique().map((x) => ({ name: x }));
routes.forEach((route, pathAndMethod) => {
const [p, method] = pathAndMethod.split("|");
const path = p.replace(/:(\w+)/g, "{$1}");
let obj = specification.paths[path]?.[method] || {};
if (!obj.description) {
const permission = route.permission ? `##### Requires the \`\`${route.permission}\`\` permission\n` : "";
const event = route.test?.event ? `##### Fires a \`\`${route.test?.event}\`\` event\n` : "";
obj.description = permission + event;
}
if (route.body) {
obj.requestBody = {
required: true,
content: {
"application/json": {
schema: { $ref: `#/components/schemas/${route.body}` }
}
}
}.merge(obj.requestBody);
}
if (!obj.responses) {
obj.responses = {
default: {
description: "not documented"
}
};
}
if (route.test?.response) {
const status = route.test.response.status || 200;
obj.responses = {
[status]: {
...(route.test.response.body
? {
description: obj.responses[status].description || "",
content: {
"application/json": {
schema: {
$ref: `#/components/schemas/${route.test.response.body}`
}
}
}
}
: {})
}
}.merge(obj.responses);
delete obj.responses.default;
}
if (p.includes(":")) {
obj.parameters = p.match(/:\w+/g)?.map((x) => ({
name: x.replace(":", ""),
in: "path",
required: true,
schema: { type: "string" },
description: x.replace(":", "")
}));
}
obj.tags = [...(obj.tags || []), getTag(p)].unique();
specification.paths[path] = { ...specification.paths[path], [method]: obj };
});
}
function main() {
combineSchemas(schemas);
apiRoutes();
fs.writeFileSync(
openapiPath,
JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")
);
}
main();

View File

@@ -1,92 +0,0 @@
// https://mermade.github.io/openapi-gui/#
// https://editor.swagger.io/
import path from "path";
import fs from "fs";
import * as TJS from "typescript-json-schema";
import "missing-native-js-functions";
const settings: TJS.PartialArgs = {
required: true,
ignoreErrors: true,
excludePrivate: true,
defaultNumberType: "integer",
noExtraProps: true,
defaultProps: false
};
const compilerOptions: TJS.CompilerOptions = {
strictNullChecks: false
};
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
var specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" }));
async function utilSchemas() {
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "..", "util", "src", "index.ts")], compilerOptions);
const generator = TJS.buildGenerator(program, settings);
const schemas = ["UserPublic", "UserPrivate", "PublicConnectedAccount"];
// @ts-ignore
combineSchemas({ schemas, generator, program });
}
function combineSchemas(opts: { program: TJS.Program; generator: TJS.JsonSchemaGenerator; schemas: string[] }) {
var definitions: any = {};
for (const name of opts.schemas) {
const part = TJS.generateSchema(opts.program, name, settings, [], opts.generator as TJS.JsonSchemaGenerator);
if (!part) continue;
definitions = { ...definitions, [name]: { ...part, definitions: undefined, $schema: undefined } };
}
for (const key in definitions) {
specification.components.schemas[key] = definitions[key];
delete definitions[key].additionalProperties;
delete definitions[key].$schema;
}
return definitions;
}
const ExcludedSchemas = [
"DefaultSchema",
"Schema",
"EntitySchema",
"ServerResponse",
"Http2ServerResponse",
"global.Express.Response",
"Response",
"e.Response",
"request.Response",
"supertest.Response"
];
function apiSchemas() {
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "src", "schema", "index.ts")], compilerOptions);
const generator = TJS.buildGenerator(program, settings);
const schemas = generator
.getUserSymbols()
.filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x))
.concat(generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x)));
// @ts-ignore
combineSchemas({ schemas, generator, program });
}
function addDefaultResponses() {
Object.values(specification.paths).forEach((path: any) => Object.values(path).forEach((request: any) => {}));
}
function main() {
addDefaultResponses();
utilSchemas();
apiSchemas();
fs.writeFileSync(
openapiPath,
JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")
);
}
main();