nope/lib/parsers/open-api/OpenApiParser.ts

256 lines
9.4 KiB
TypeScript
Raw Normal View History

2020-11-09 14:28:09 +00:00
import { readFile } from "fs/promises";
2020-11-09 06:42:24 +00:00
import * as handlebars from 'handlebars';
import { join, relative } from "path";
2020-11-09 14:28:09 +00:00
import { Logger } from 'winston';
2020-11-09 06:42:24 +00:00
import { createFile, createPath } from "../../helpers/fileMethods";
import { deepClone } from "../../helpers/objectMethods";
2020-11-09 14:28:09 +00:00
import { replaceAll } from "../../helpers/stringMethods";
import { IJsonSchema } from '../../types/IJSONSchema';
import { INopeDescriptorFunctionParameter } from "../../types/nope/nopeDescriptor.interface";
import { IFunctionOptions, IParsableDescription } from "../../types/nope/nopeModule.interface";
2020-11-09 06:42:24 +00:00
/**
* Helper function to merge the Parameters into 1 description.
* @param elements
*/
function _unifySchema(elements: INopeDescriptorFunctionParameter[], options: {
sharedDefinitions: boolean,
logger?: Logger,
}){
2020-11-09 06:42:24 +00:00
const ret: IJsonSchema = {
type: 'object',
properties: {},
required: []
}
for (const item of elements){
if (options.sharedDefinitions){
item.schema.definitions = undefined;
item.schema.$schema = undefined;
}
2020-11-09 06:42:24 +00:00
// Assign the Schema
ret.properties[item.name] = item.schema as any as IJsonSchema;
2020-11-09 06:42:24 +00:00
// If the Element is Optional,
if (!item.optional){
ret.required.push(item.name)
}
}
return ret;
}
/**
* Function, to parse a description to an Open-API Element.
* @param description
* @param options
*/
export async function parseModuleToOpenAPI(description: IParsableDescription, options: {
2020-11-09 06:42:24 +00:00
outputDir: string,
sharedDefinitions: boolean,
2020-11-09 06:42:24 +00:00
logger?: Logger,
}){
const _description = deepClone(description);
// load the Template.
const template = await readFile(join(process.cwd(), 'lib' , 'parsers', 'open-api' ,'templates', 'method.handlebars'), {
2020-11-09 06:42:24 +00:00
encoding: 'utf-8'
})
// Renderfuncting
const renderAPI = handlebars.compile(template);
await createPath(join(options.outputDir));
// Now iterate over the Methods of the Module and find parsable Methods.
for (const [idx, method] of _description.methods.entries()){
// Test if the Method contains some functions in the input / Output:
const parsedInputs = JSON.stringify(method.schema.inputs);
const parsedOutput = JSON.stringify(method.schema.outputs);
if (!parsedInputs.includes('"function"') && !parsedOutput.includes('"function"')){
// The Method should be parseable.
// 1. Specify the Mode (No Params = GET, else POST)
(method as any).mode = method.schema.inputs.length > 0 ? 'POST' : 'GET';
// Now adapt the Schema of the Method.
// Iterate over the Inputs, add a Description if not provided
// And determine whehter the Parameters are required or not.
method.schema.inputs = method.schema.inputs.map(param => {
// Provide a Description. (If not provided)
param.description = param.description || 'Not provided';
// Open API uses "required" instead of optional => invert
param.optional = !param.optional;
if (options.sharedDefinitions){
param.schema.definitions = undefined;
param.schema.$schema = undefined;
}
2020-11-09 06:42:24 +00:00
// Parse the Schema in here.
(param as any).parsedSchema = JSON.stringify(param.schema);
return param;
});
// Make shure the Return type isnt an array
method.schema.outputs = Array.isArray(method.schema.outputs) ?
_unifySchema(method.schema.outputs, options) :
2020-11-09 06:42:24 +00:00
method.schema.outputs;
if (options.sharedDefinitions) {
method.schema.outputs.definitions = undefined;
method.schema.outputs.$schema = undefined;
}
2020-11-09 06:42:24 +00:00
// Add an Description to the result.
(method as any).resultDescription = method.schema.outputs.description || 'Not Provided';
// And add a Schema for the Return type.
(method as any).parsedOutput = JSON.stringify(method.schema.outputs);
(method as any).tag = description.name;
2020-11-09 06:42:24 +00:00
// Determine the Filename.
const fileDir = join(options.outputDir, _description.name, '{instance}' ,'methods');
const fileName = join(fileDir, method.id+'.ts');
// Determine the Import Pathes.
const imports = [
{
dir: join(process.cwd(), 'lib', 'types', 'nope'),
fileName: 'nopeDispatcher.interface',
name: 'pathOfDispatcher'
},
{
dir: join(process.cwd(), 'lib', 'helpers'),
fileName: 'dispatcherPathes',
name: 'pathOfHelper'
}
]
for (const imp of imports){
const relativDir = relative(fileDir, imp.dir);
2020-11-09 14:28:09 +00:00
(method as any)[imp.name] = replaceAll(join(relativDir, imp.fileName), '\\', '/');
2020-11-09 06:42:24 +00:00
}
// Write down the Schema:
await createFile(
// Generate the Path.
fileName,
renderAPI(method)
);
if (options.logger) {
options.logger.info('Generated -> ' + fileName);
}
2020-11-10 07:56:22 +00:00
} else if (options.logger){
options.logger.warn('parser can not convert: "' + description.name + '.' + method.id + '". The Function contains functions as parameters')
2020-11-09 06:42:24 +00:00
}
}
}
/**
* Function, to parse a description to an Open-API Element.
* @param description
* @param options
*/
export async function parseFunctionToOpenAPI(description: IFunctionOptions, options: {
outputDir: string,
sharedDefinitions: boolean,
logger?: Logger,
}){
const _description = deepClone(description);
// load the Template.
const template = await readFile(join(process.cwd(), 'lib' , 'parsers', 'open-api' ,'templates', 'function.handlebars'), {
encoding: 'utf-8'
})
// Renderfuncting
const renderAPI = handlebars.compile(template);
await createPath(join(options.outputDir));
// Now iterate over the Methods of the Module and find parsable Methods.
// Test if the Method contains some functions in the input / Output:
const parsedInputs = JSON.stringify(_description.schema.inputs);
const parsedOutput = JSON.stringify(_description.schema.outputs);
if (!parsedInputs.includes('"function"') && !parsedOutput.includes('"function"')){
// The Method should be parseable.
// 1. Specify the Mode (No Params = GET, else POST)
(_description as any).mode = _description.schema.inputs.length > 0 ? 'POST' : 'GET';
// Now adapt the Schema of the Method.
// Iterate over the Inputs, add a Description if not provided
// And determine whehter the Parameters are required or not.
_description.schema.inputs = _description.schema.inputs.map(param => {
// Provide a Description. (If not provided)
param.description = param.description || 'Not provided';
// Open API uses "required" instead of optional => invert
param.optional = !param.optional;
if (options.sharedDefinitions){
param.schema.definitions = undefined;
param.schema.$schema = undefined;
}
// Parse the Schema in here.
(param as any).parsedSchema = JSON.stringify(param.schema);
return param;
});
// Make shure the Return type isnt an array
_description.schema.outputs = Array.isArray(_description.schema.outputs) ?
_unifySchema(_description.schema.outputs, options) :
_description.schema.outputs;
if (options.sharedDefinitions) {
_description.schema.outputs.definitions = undefined;
_description.schema.outputs.$schema = undefined;
}
// Add an Description to the result.
(_description as any).resultDescription = _description.schema.outputs.description || 'Not Provided';
// And add a Schema for the Return type.
(_description as any).parsedOutput = JSON.stringify(_description.schema.outputs);
(_description as any).tag = 'generic-services';
// Determine the Filename.
const fileDir = join(options.outputDir, 'generic-services');
const fileName = join(fileDir, _description.id+'.ts');
// Determine the Import Pathes.
const imports = [
{
dir: join(process.cwd(), 'lib', 'types', 'nope'),
fileName: 'nopeDispatcher.interface',
name: 'pathOfDispatcher'
}
]
for (const imp of imports){
const relativDir = relative(fileDir, imp.dir);
(_description as any)[imp.name] = replaceAll(join(relativDir, imp.fileName), '\\', '/');
}
// Write down the Schema:
await createFile(
// Generate the Path.
fileName,
renderAPI(_description)
);
if (options.logger) {
options.logger.info('Generated -> ' + fileName);
}
} else if (options.logger){
options.logger.warn('parser can not convert: "' + _description.id + '". The Function contains functions as parameters')
}
2020-11-09 06:42:24 +00:00
}