mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-11 12:12:55 +00:00
162 lines
5.8 KiB
TypeScript
162 lines
5.8 KiB
TypeScript
/*
|
|
* Copyright (C) 2018-2021 StApps
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
import {OpenAPIV3} from 'openapi-types';
|
|
import {
|
|
isLightweightClass,
|
|
lightweightProjectFromPath,
|
|
LightweightProjectWithIndex,
|
|
} from '@openstapps/easy-ast';
|
|
import {RouteInstanceWithMeta, RouteWithMetaInformation} from './types/routes.js';
|
|
import {rejectNil} from './util/collections.js';
|
|
import {capitalize} from './util/string.js';
|
|
import path from 'path';
|
|
|
|
/**
|
|
* Gather relevant information of routes
|
|
*
|
|
* This gathers the information for all routes that implement the abstract class SCAbstractRoute.
|
|
* Furthermore it instantiates every route and adds it to the information.
|
|
*/
|
|
export async function gatherRouteInformation(path: string): Promise<RouteWithMetaInformation[]> {
|
|
const project = new LightweightProjectWithIndex(lightweightProjectFromPath(path));
|
|
|
|
// find all classes that implement the SCAbstractRoute
|
|
return rejectNil(
|
|
await Promise.all(
|
|
Object.values(project.definitions)
|
|
.filter(isLightweightClass)
|
|
.map(async node => {
|
|
if (!node.extendedDefinitions?.some(it => it.referenceName === 'SCAbstractRoute')) {
|
|
return undefined;
|
|
}
|
|
|
|
const instantiatedRoute = (await project.instantiateDefinitionByName(
|
|
node.name,
|
|
)) as RouteInstanceWithMeta;
|
|
// instantiate all errors
|
|
instantiatedRoute.errors = await Promise.all(
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
instantiatedRoute.errorNames.map(async (error: any) =>
|
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
Object.assign((await project.instantiateDefinitionByName(error.name)) as object, {
|
|
name: error.name,
|
|
}),
|
|
),
|
|
);
|
|
instantiatedRoute.responseBodyDescription =
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
project.definitions[instantiatedRoute.responseBodyName]?.comment?.shortSummary!;
|
|
instantiatedRoute.requestBodyDescription =
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
project.definitions[instantiatedRoute.requestBodyName]?.comment?.shortSummary!;
|
|
|
|
return {
|
|
description: {
|
|
shortText: node.comment?.shortSummary,
|
|
text: node.comment?.description,
|
|
},
|
|
name: node.name!,
|
|
route: instantiatedRoute,
|
|
};
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Generate documentation snippet for one route
|
|
* @param routeWithInfo A route instance with its meta information
|
|
* @param outDirectorySchemasPath Path to directory that will contain relevant schemas for the route
|
|
* @param tagsToKeep Tags / keywords that can be used for grouping routes
|
|
*/
|
|
export function generateOpenAPIForRoute(
|
|
routeWithInfo: RouteWithMetaInformation,
|
|
outDirectorySchemasPath: string,
|
|
tagsToKeep: string[],
|
|
): OpenAPIV3.PathItemObject {
|
|
const route = routeWithInfo.route;
|
|
const openapiPath: OpenAPIV3.PathItemObject = {};
|
|
|
|
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods] = {
|
|
summary: capitalize(routeWithInfo.description.shortText?.replace(/(Route to |Route for )/gim, '')),
|
|
description: routeWithInfo.description.text,
|
|
requestBody: {
|
|
description: route.responseBodyDescription ?? undefined,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
$ref: path.join(outDirectorySchemasPath, `${route.requestBodyName}.json`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
parameters: [
|
|
{
|
|
name: 'X-StApps-Version',
|
|
in: 'header',
|
|
schema: {
|
|
type: 'string',
|
|
example: '2.0.0',
|
|
},
|
|
required: true,
|
|
},
|
|
],
|
|
responses: {},
|
|
tags: routeWithInfo.tags?.filter(value => tagsToKeep.includes(value)),
|
|
};
|
|
|
|
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![route.statusCodeSuccess] = {
|
|
description: route.responseBodyDescription,
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
$ref: path.join(outDirectorySchemasPath, `${route.responseBodyName}.json`),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
for (const error of route.errors) {
|
|
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![error.statusCode] = {
|
|
description:
|
|
error.message ?? capitalize(error.name.replaceAll(/([A-Z][a-z])/g, ' $1').replace('SC ', '')),
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
$ref: path.join(outDirectorySchemasPath, `${error.name}.json`),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
if (typeof route.obligatoryParameters === 'object') {
|
|
for (const [parameter, schemaDefinition] of Object.entries(route.obligatoryParameters)) {
|
|
const openapiParameter: OpenAPIV3.ParameterObject = {
|
|
in: 'path',
|
|
name: parameter,
|
|
required: true,
|
|
schema: {
|
|
// TODO make this less of a hack and search copied schemas for the first occurring definition
|
|
$ref: `schema/SCSearchResponse.json#/definitions/${schemaDefinition}`,
|
|
},
|
|
};
|
|
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]?.parameters?.push(openapiParameter);
|
|
}
|
|
}
|
|
|
|
return openapiPath;
|
|
}
|