mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 00:52:55 +00:00
feat: modernize core-tools
This commit is contained in:
195
src/routes.ts
195
src/routes.ts
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2019 StApps
|
||||
* 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.
|
||||
@@ -12,182 +12,139 @@
|
||||
* 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 {assign, filter, map} from 'lodash';
|
||||
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';
|
||||
import {isLightweightClass} from './easy-ast/ast-util';
|
||||
import {LightweightProjectWithIndex} from './easy-ast/types/lightweight-project';
|
||||
import {RouteInstanceWithMeta, RouteWithMetaInformation} from './types/routes';
|
||||
import {rejectNil} from './util/collections';
|
||||
import {capitalize} from './util/string';
|
||||
import path from 'path';
|
||||
import {lightweightProjectFromPath} from './easy-ast/easy-ast';
|
||||
|
||||
/**
|
||||
* 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[] = [];
|
||||
export async function gatherRouteInformation(path: string): Promise<RouteWithMetaInformation[]> {
|
||||
const project = new LightweightProjectWithIndex(lightweightProjectFromPath(path));
|
||||
|
||||
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});
|
||||
}
|
||||
// find all classes that implement the SCAbstractRoute
|
||||
return rejectNil(
|
||||
await Promise.all(
|
||||
map(filter(project.definitions, isLightweightClass), async node => {
|
||||
if (!node.extendedDefinitions?.some(it => it.referenceName === 'SCAbstractRoute')) {
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
if (routes.length === 0) {
|
||||
throw new Error('No route information found.');
|
||||
}
|
||||
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
|
||||
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 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;
|
||||
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 outDirSchemasPath Path to directory that will contain relevant schemas for the route
|
||||
* @param outDirectorySchemasPath 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 {
|
||||
export function generateOpenAPIForRoute(
|
||||
routeWithInfo: RouteWithMetaInformation,
|
||||
outDirectorySchemasPath: string,
|
||||
schemasToCopy: string[],
|
||||
tagsToKeep: string[],
|
||||
): OpenAPIV3.PathItemObject {
|
||||
const route = routeWithInfo.route;
|
||||
const path: OpenAPIV3.PathItemObject = {};
|
||||
const openapiPath: 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, '')),
|
||||
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: join(outDirSchemasPath, `${route.requestBodyName}.json`),
|
||||
$ref: path.join(outDirectorySchemasPath, `${route.requestBodyName}.json`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
parameters: [{
|
||||
name: 'X-StApps-Version',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: '2.0.0',
|
||||
parameters: [
|
||||
{
|
||||
name: 'X-StApps-Version',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: '2.0.0',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
required: true,
|
||||
}],
|
||||
],
|
||||
responses: {},
|
||||
tags: routeWithInfo.tags?.filter(value => tagsToKeep.includes(value)),
|
||||
};
|
||||
|
||||
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)]!.responses![route.statusCodeSuccess] = {
|
||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![route.statusCodeSuccess] = {
|
||||
description: route.responseBodyDescription,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: join(outDirSchemasPath, `${route.responseBodyName}.json`),
|
||||
$ref: path.join(outDirectorySchemasPath, `${route.responseBodyName}.json`),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
route.errors.forEach(error => {
|
||||
for (const error of route.errors) {
|
||||
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 ', '')),
|
||||
openapiPath[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`),
|
||||
$ref: path.join(outDirectorySchemasPath, `${error.name}.json`),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof route.obligatoryParameters === 'object') {
|
||||
for (const [parameter, schemaDefinition] of Object.entries(route.obligatoryParameters)) {
|
||||
const openapiParam: OpenAPIV3.ParameterObject = {
|
||||
const openapiParameter: OpenAPIV3.ParameterObject = {
|
||||
in: 'path',
|
||||
name: parameter,
|
||||
required: true,
|
||||
@@ -196,9 +153,9 @@ export function getLinkForNode(name: string, node: NodeWithMetaInformation): str
|
||||
$ref: `schemas/SCSearchResponse.json#/definitions/${schemaDefinition}`,
|
||||
},
|
||||
};
|
||||
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)]?.parameters?.push(openapiParam);
|
||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]?.parameters?.push(openapiParameter);
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
return openapiPath;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user