Merge pull request #1192 from DEVTomatoCake/feat/improve-schema-openapi-generation

This commit is contained in:
Madeline 2024-08-22 09:49:21 +10:00 committed by GitHub
commit 4f19ee19bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 39 deletions

Binary file not shown.

Binary file not shown.

View File

@ -28,15 +28,13 @@ require("missing-native-js-functions");
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json"); const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
// const specification = JSON.parse(
// fs.readFileSync(openapiPath, { encoding: "utf8" }),
// );
let specification = { let specification = {
openapi: "3.1.0", openapi: "3.1.0",
info: { info: {
title: "Spacebar Server", title: "Spacebar Server",
description: description:
"Spacebar is a free open source selfhostable discord compatible chat, voice and video platform", "Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.",
license: { license: {
name: "AGPLV3", name: "AGPLV3",
url: "https://www.gnu.org/licenses/agpl-3.0.en.html", url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
@ -68,8 +66,9 @@ let specification = {
paths: {}, paths: {},
}; };
const schemaRegEx = new RegExp(/^[\w.]+$/);
function combineSchemas(schemas) { function combineSchemas(schemas) {
var definitions = {}; let definitions = {};
for (const name in schemas) { for (const name in schemas) {
definitions = { definitions = {
@ -84,9 +83,8 @@ function combineSchemas(schemas) {
} }
for (const key in definitions) { for (const key in definitions) {
const reg = new RegExp(/^[a-zA-Z0-9.\-_]+$/, "gm"); if (!schemaRegEx.test(key)) {
if (!reg.test(key)) { console.error(`Invalid schema name: ${key}`);
console.error(`Invalid schema name: ${key} (${reg.test(key)})`);
continue; continue;
} }
specification.components = specification.components || {}; specification.components = specification.components || {};
@ -116,7 +114,7 @@ function getTag(key) {
return key.match(/\/([\w-]+)/)[1]; return key.match(/\/([\w-]+)/)[1];
} }
function apiRoutes() { function apiRoutes(missingRoutes) {
const routes = getRouteDescriptions(); const routes = getRouteDescriptions();
// populate tags // populate tags
@ -157,32 +155,30 @@ function apiRoutes() {
}, },
}, },
}, },
}.merge(obj.requestBody); };
} }
if (route.responses) { if (route.responses) {
for (const [k, v] of Object.entries(route.responses)) { obj.responses = {};
let schema = {
$ref: `#/components/schemas/${v.body}`,
};
obj.responses = { for (const [k, v] of Object.entries(route.responses)) {
[k]: { if (v.body)
...(v.body obj.responses[k] = {
? { description: obj?.responses?.[k]?.description || "",
description: content: {
obj?.responses?.[k]?.description || "", "application/json": {
content: { schema: {
"application/json": { $ref: `#/components/schemas/${v.body}`,
schema: schema, },
}, },
}, },
} };
: { else
description: "No description available", obj.responses[k] = {
}), description:
}, obj?.responses?.[k]?.description ||
}.merge(obj.responses); "No description available",
};
} }
} else { } else {
obj.responses = { obj.responses = {
@ -218,6 +214,15 @@ function apiRoutes() {
obj.tags = [...(obj.tags || []), getTag(p)].unique(); obj.tags = [...(obj.tags || []), getTag(p)].unique();
if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
obj["x-badges"] = [
{
label: "Spacebar-only",
color: "red",
},
];
}
specification.paths[path] = Object.assign( specification.paths[path] = Object.assign(
specification.paths[path] || {}, specification.paths[path] || {},
{ {
@ -227,10 +232,21 @@ function apiRoutes() {
}); });
} }
function main() { async function main() {
console.log("Generating OpenAPI Specification..."); console.log("Generating OpenAPI Specification...");
const routesRes = await fetch(
"https://github.com/spacebarchat/missing-routes/raw/main/missing.json",
{
headers: {
Accept: "application/json",
},
},
);
const missingRoutes = await routesRes.json();
combineSchemas(schemas); combineSchemas(schemas);
apiRoutes(); apiRoutes(missingRoutes);
fs.writeFileSync( fs.writeFileSync(
openapiPath, openapiPath,

View File

@ -41,11 +41,16 @@ const Excluded = [
"EntitySchema", "EntitySchema",
"ServerResponse", "ServerResponse",
"Http2ServerResponse", "Http2ServerResponse",
"ExpressResponse",
"global.Express.Response", "global.Express.Response",
"global.Response",
"Response", "Response",
"e.Response", "e.Response",
"request.Response", "request.Response",
"supertest.Response", "supertest.Response",
"DiagnosticsChannel.Response",
"_Response",
"ReadableStream<any>",
// TODO: Figure out how to exclude schemas from node_modules? // TODO: Figure out how to exclude schemas from node_modules?
"SomeJSONSchema", "SomeJSONSchema",