Files
openstapps/src/routes.ts
2021-07-20 09:35:49 +02:00

205 lines
7.0 KiB
TypeScript

/*
* Copyright (C) 2018-2019 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 {asyncPool} from '@krlwlfrt/async-pool/lib/async-pool';
import {Logger} from '@openstapps/logger';
import {OpenAPIV3} from 'openapi-types';
import {basename, dirname, join} from 'path';
import {ProjectReflection} from 'typedoc';
import {Type} from 'typedoc/dist/lib/models';
import {capitalize, NodeWithMetaInformation, RouteWithMetaInformation} from './common';
/**
* 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.
*
* @param reflection Contents of the JSON representation which Typedoc generates
*/
export async function gatherRouteInformation(reflection: ProjectReflection): Promise<RouteWithMetaInformation[]> {
const routes: RouteWithMetaInformation[] = [];
if (!Array.isArray(reflection.children)) {
throw new Error('Project reflection doesn\'t contain any modules.');
}
// tslint:disable-next-line:no-magic-numbers
await asyncPool(2, reflection.children, async (module) => {
if (Array.isArray(module.children) && module.children.length > 0) {
// tslint:disable-next-line:no-magic-numbers
await asyncPool(2, module.children, (async (node) => {
if (Array.isArray(node.extendedTypes) && node.extendedTypes.length > 0) {
if (node.extendedTypes.some((extendedType) => {
// tslint:disable-next-line:completed-docs
return (extendedType as (Type & { name: string; })).name === 'SCAbstractRoute';
})) {
Logger.info(`Found ${node.name} in ${module.originalName}.`);
if (Array.isArray(module.originalName.match(/\.d\.ts$/))) {
module.originalName = join(dirname(module.originalName), basename(module.originalName, '.d.ts'));
Logger.info(`Using compiled version of module in ${module.originalName}.`);
}
const importedModule = await import(module.originalName);
const route = new importedModule[node.name]();
// tslint:disable-next-line: no-any
const errors = route.errorNames.map((error: any) => {
const scError = new importedModule[error.name]();
scError.name = error.name;
return scError;
});
route.responseBodyDescription = module.children!.find(element => element.name === route.responseBodyName)?.comment?.shortText;
route.requestBodyDescription = module.children!.find(element => element.name === route.requestBodyName)?.comment?.shortText;
route.errors = errors;
routes.push({description: node.comment!, name: node.name, route});
}
}
}));
}
});
if (routes.length === 0) {
throw new Error('No route information found.');
}
return routes;
}
/**
* Get link for a node
*
* @param name Name of the node
* @param node Node itself
*/
export function getLinkForNode(name: string, node: NodeWithMetaInformation): string {
let link = 'https://openstapps.gitlab.io/core/';
const module = node.module
.toLowerCase()
.split('/')
.join('_');
if (node.type === 'Type alias') {
link += 'modules/';
link += `_${module}_`;
link += `.html#${name.toLowerCase()}`;
return link;
}
let type = 'classes';
if (node.type !== 'Class') {
type = `${node.type.toLowerCase()}s`;
}
link += `${type}/`;
link += `_${module}_`;
link += `.${name.toLowerCase()}.html`;
return link;
}
/**
* Generate documentation snippet for one route
*
* @param routeWithInfo A route instance with its meta information
* @param outDirSchemasPath Path to directory that will contain relevant schemas for the route
* @param schemasToCopy Schemas identified as relevant for this route
* @param tagsToKeep Tags / keywords that can be used for grouping routes
*/
export function generateOpenAPIForRoute(routeWithInfo: RouteWithMetaInformation,
outDirSchemasPath: string,
schemasToCopy: string[],
tagsToKeep: string[]): OpenAPIV3.PathItemObject {
const route = routeWithInfo.route;
const path: OpenAPIV3.PathItemObject = {};
schemasToCopy.push(route.requestBodyName, route.responseBodyName);
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)] = {
summary: capitalize(routeWithInfo.description.shortText?.replace(/(Route to |Route for )/gmi, '')),
description: routeWithInfo.description.text,
requestBody: {
description: route.responseBodyDescription ?? undefined,
content: {
'application/json': {
schema: {
$ref: join(outDirSchemasPath, `${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)),
};
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)]!.responses![route.statusCodeSuccess] = {
description: route.responseBodyDescription,
content: {
'application/json': {
schema: {
$ref: join(outDirSchemasPath, `${route.responseBodyName}.json`),
},
},
},
};
route.errors.forEach(error => {
schemasToCopy.push(error.name);
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)]!.responses![error.statusCode] = {
description: error.message ?? capitalize(error.name.replace(/([A-Z][a-z])/g,' $1')
.replace('SC ', '')),
content: {
'application/json': {
schema: {
$ref: join(outDirSchemasPath, `${error.name}.json`),
},
},
},
};
});
if (typeof route.obligatoryParameters === 'object') {
for (const [parameter, schemaDefinition] of Object.entries(route.obligatoryParameters)) {
const openapiParam: 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: `schemas/SCSearchResponse.json#/definitions/${schemaDefinition}`,
},
};
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)]?.parameters?.push(openapiParam);
}
}
return path;
}