2021-11-16 20:41:08 +00:00
|
|
|
/**
|
|
|
|
* @author Martin Karkowski
|
|
|
|
* @email m.karkowski@zema.de
|
|
|
|
* @create date 2021-11-11 11:36:37
|
|
|
|
* @modify date 2021-11-11 11:36:37
|
|
|
|
* @desc [description]
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2020-11-09 14:28:09 +00:00
|
|
|
import { readFile } from "fs/promises";
|
2021-11-16 20:41:08 +00:00
|
|
|
import * as handlebars from "handlebars";
|
|
|
|
import { ILogger } from "js-logger";
|
2020-11-09 06:42:24 +00:00
|
|
|
import { join, relative } from "path";
|
|
|
|
import { createFile, createPath } from "../../helpers/fileMethods";
|
|
|
|
import { deepClone } from "../../helpers/objectMethods";
|
2020-11-09 14:28:09 +00:00
|
|
|
import { replaceAll } from "../../helpers/stringMethods";
|
2021-11-16 20:41:08 +00:00
|
|
|
import { IJsonSchema } from "../../types/IJSONSchema";
|
2020-11-09 14:28:09 +00:00
|
|
|
import { INopeDescriptorFunctionParameter } from "../../types/nope/nopeDescriptor.interface";
|
2020-11-11 16:08:11 +00:00
|
|
|
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
|
|
|
|
*/
|
2020-11-11 16:08:11 +00:00
|
|
|
function _unifySchema(elements: INopeDescriptorFunctionParameter[], options: {
|
|
|
|
sharedDefinitions: boolean,
|
2021-11-16 20:41:08 +00:00
|
|
|
logger?: ILogger,
|
2020-11-15 19:11:25 +00:00
|
|
|
}) {
|
2020-11-09 06:42:24 +00:00
|
|
|
const ret: IJsonSchema = {
|
2021-11-16 20:41:08 +00:00
|
|
|
type: "object",
|
2020-11-09 06:42:24 +00:00
|
|
|
properties: {},
|
|
|
|
required: []
|
2021-11-16 20:41:08 +00:00
|
|
|
};
|
2020-11-09 06:42:24 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
for (const item of elements) {
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
if (options.sharedDefinitions) {
|
2020-11-11 16:08:11 +00:00
|
|
|
item.schema.definitions = undefined;
|
|
|
|
item.schema.$schema = undefined;
|
|
|
|
}
|
|
|
|
|
2020-11-09 06:42:24 +00:00
|
|
|
// Assign the Schema
|
2020-11-11 16:08:11 +00:00
|
|
|
ret.properties[item.name] = item.schema as any as IJsonSchema;
|
2020-11-09 06:42:24 +00:00
|
|
|
|
|
|
|
// If the Element is Optional,
|
2020-11-15 19:11:25 +00:00
|
|
|
if (!item.optional) {
|
2021-11-16 20:41:08 +00:00
|
|
|
ret.required.push(item.name);
|
2020-11-09 06:42:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function, to parse a description to an Open-API Element.
|
|
|
|
* @param description
|
|
|
|
* @param options
|
|
|
|
*/
|
2020-11-11 16:08:11 +00:00
|
|
|
export async function parseModuleToOpenAPI(description: IParsableDescription, options: {
|
2020-11-09 06:42:24 +00:00
|
|
|
outputDir: string,
|
2020-11-11 16:08:11 +00:00
|
|
|
sharedDefinitions: boolean,
|
2021-11-16 20:41:08 +00:00
|
|
|
mode: "js" | "ts",
|
2020-11-09 06:42:24 +00:00
|
|
|
logger?: Logger,
|
2021-11-16 20:41:08 +00:00
|
|
|
}): Promise<void> {
|
2020-11-15 19:11:25 +00:00
|
|
|
|
2020-11-09 06:42:24 +00:00
|
|
|
const _description = deepClone(description);
|
|
|
|
|
|
|
|
// load the Template.
|
2021-11-16 20:41:08 +00:00
|
|
|
const template = await readFile(join(process.cwd(), "lib", "parsers", "open-api", "templates", "method." + options.mode + ".handlebars"), {
|
|
|
|
encoding: "utf-8"
|
|
|
|
});
|
2020-11-15 19:11:25 +00:00
|
|
|
|
2020-11-09 06:42:24 +00:00
|
|
|
// Renderfuncting
|
|
|
|
const renderAPI = handlebars.compile(template);
|
|
|
|
|
|
|
|
await createPath(join(options.outputDir));
|
|
|
|
|
|
|
|
// Now iterate over the Methods of the Module and find parsable Methods.
|
2020-11-15 19:11:25 +00:00
|
|
|
for (const [idx, method] of _description.methods.entries()) {
|
2020-11-09 06:42:24 +00:00
|
|
|
// 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);
|
2021-11-16 20:41:08 +00:00
|
|
|
if (!parsedInputs.includes("\"function\"") && !parsedOutput.includes("\"function\"")) {
|
2020-11-09 06:42:24 +00:00
|
|
|
// The Method should be parseable.
|
|
|
|
|
|
|
|
// 1. Specify the Mode (No Params = GET, else POST)
|
2021-11-16 20:41:08 +00:00
|
|
|
(method as any).mode = method.schema.inputs.length > 0 ? "POST" : "GET";
|
2020-11-09 06:42:24 +00:00
|
|
|
|
2020-11-12 16:07:05 +00:00
|
|
|
(method as any).required = method.schema.inputs.filter(param => !param.optional).map(param => param.name);
|
|
|
|
(method as any).hasInput = method.schema.inputs.length > 0;
|
|
|
|
|
2020-11-09 06:42:24 +00:00
|
|
|
// 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)
|
2021-11-16 20:41:08 +00:00
|
|
|
param.description = param.description || "Not provided";
|
2020-11-09 06:42:24 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
if (options.sharedDefinitions) {
|
2020-11-11 16:08:11 +00:00
|
|
|
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) ?
|
2020-11-11 16:08:11 +00:00
|
|
|
_unifySchema(method.schema.outputs, options) :
|
2020-11-09 06:42:24 +00:00
|
|
|
method.schema.outputs;
|
|
|
|
|
2020-11-11 16:08:11 +00:00
|
|
|
if (options.sharedDefinitions) {
|
|
|
|
method.schema.outputs.definitions = undefined;
|
|
|
|
method.schema.outputs.$schema = undefined;
|
|
|
|
}
|
2020-11-15 19:11:25 +00:00
|
|
|
|
2020-11-09 06:42:24 +00:00
|
|
|
// Add an Description to the result.
|
2021-11-16 20:41:08 +00:00
|
|
|
(method as any).resultDescription = method.schema.outputs.description || "Not Provided";
|
2020-11-09 06:42:24 +00:00
|
|
|
// And add a Schema for the Return type.
|
|
|
|
(method as any).parsedOutput = JSON.stringify(method.schema.outputs);
|
2020-11-12 16:07:05 +00:00
|
|
|
(method as any).parsedInput = JSON.stringify(_unifySchema(method.schema.inputs, options));
|
|
|
|
(method as any).hasInput = method.schema.inputs.length > 0;
|
2020-11-11 16:08:11 +00:00
|
|
|
(method as any).tag = description.name;
|
2020-11-09 06:42:24 +00:00
|
|
|
|
|
|
|
// Determine the Filename.
|
2021-11-16 20:41:08 +00:00
|
|
|
const fileDir = join(options.outputDir, _description.name, "{instance}", "methods");
|
|
|
|
const fileName = join(fileDir, method.id + "." + options.mode);
|
2020-11-09 06:42:24 +00:00
|
|
|
|
|
|
|
// Determine the Import Pathes.
|
|
|
|
const imports = [
|
|
|
|
{
|
2021-11-16 20:41:08 +00:00
|
|
|
dir: join(process.cwd(), "lib", "types", "nope"),
|
|
|
|
fileName: "nopeDispatcher.interface",
|
|
|
|
name: "pathOfDispatcher"
|
2020-11-09 06:42:24 +00:00
|
|
|
},
|
|
|
|
{
|
2021-11-16 20:41:08 +00:00
|
|
|
dir: join(process.cwd(), "lib", "helpers"),
|
|
|
|
fileName: "dispatcherPathes",
|
|
|
|
name: "pathOfHelper"
|
2020-11-09 06:42:24 +00:00
|
|
|
}
|
2021-11-16 20:41:08 +00:00
|
|
|
];
|
2020-11-09 06:42:24 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
for (const imp of imports) {
|
2020-11-09 06:42:24 +00:00
|
|
|
const relativDir = relative(fileDir, imp.dir);
|
2021-11-16 20:41:08 +00:00
|
|
|
(method as any)[imp.name] = replaceAll(join(relativDir, imp.fileName), "\\", "/");
|
2020-11-12 16:07:05 +00:00
|
|
|
}
|
2020-11-09 06:42:24 +00:00
|
|
|
|
|
|
|
// Write down the Schema:
|
|
|
|
await createFile(
|
|
|
|
// Generate the Path.
|
|
|
|
fileName,
|
|
|
|
renderAPI(method)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (options.logger) {
|
2021-11-16 20:41:08 +00:00
|
|
|
options.logger.info("Generated -> " + fileName);
|
2020-11-09 06:42:24 +00:00
|
|
|
}
|
2020-11-15 19:11:25 +00:00
|
|
|
} else if (options.logger) {
|
2021-11-16 20:41:08 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2020-11-11 16:08:11 +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,
|
2021-11-16 20:41:08 +00:00
|
|
|
mode: "js" | "ts",
|
2020-11-11 16:08:11 +00:00
|
|
|
logger?: Logger,
|
2021-11-16 20:41:08 +00:00
|
|
|
}): Promise<void> {
|
2020-11-15 19:11:25 +00:00
|
|
|
|
2020-11-11 16:08:11 +00:00
|
|
|
const _description = deepClone(description);
|
|
|
|
|
|
|
|
// load the Template.
|
2021-11-16 20:41:08 +00:00
|
|
|
const template = await readFile(join(process.cwd(), "lib", "parsers", "open-api", "templates", "function." + options.mode + ".handlebars"), {
|
|
|
|
encoding: "utf-8"
|
|
|
|
});
|
2020-11-15 19:11:25 +00:00
|
|
|
|
2020-11-11 16:08:11 +00:00
|
|
|
// Renderfuncting
|
|
|
|
const renderAPI = handlebars.compile(template);
|
|
|
|
|
|
|
|
await createPath(join(options.outputDir));
|
|
|
|
|
|
|
|
// Now iterate over the Methods of the Module and find parsable Methods.
|
2020-11-15 19:11:25 +00:00
|
|
|
// 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);
|
2021-11-16 20:41:08 +00:00
|
|
|
if (!parsedInputs.includes("\"function\"") && !parsedOutput.includes("\"function\"")) {
|
2020-11-15 19:11:25 +00:00
|
|
|
// The Method should be parseable.
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
// 1. Specify the Mode (No Params = GET, else POST)
|
2021-11-16 20:41:08 +00:00
|
|
|
(_description as any).mode = _description.schema.inputs.length > 0 ? "POST" : "GET";
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
// 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 => {
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
// Provide a Description. (If not provided)
|
2021-11-16 20:41:08 +00:00
|
|
|
param.description = param.description || "Not provided";
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
if (options.sharedDefinitions) {
|
|
|
|
param.schema.definitions = undefined;
|
|
|
|
param.schema.$schema = undefined;
|
|
|
|
}
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
// Parse the Schema in here.
|
|
|
|
(param as any).parsedSchema = JSON.stringify(param.schema);
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
return param;
|
|
|
|
});
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
// Make shure the Return type isnt an array
|
|
|
|
_description.schema.outputs = Array.isArray(_description.schema.outputs) ?
|
|
|
|
_unifySchema(_description.schema.outputs, options) :
|
|
|
|
_description.schema.outputs;
|
2020-11-11 16:08:11 +00:00
|
|
|
|
2020-11-15 19:11:25 +00:00
|
|
|
if (options.sharedDefinitions) {
|
2020-11-11 16:08:11 +00:00
|
|
|
_description.schema.outputs.definitions = undefined;
|
|
|
|
_description.schema.outputs.$schema = undefined;
|
2020-11-15 19:11:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add an Description to the result.
|
2021-11-16 20:41:08 +00:00
|
|
|
(_description as any).resultDescription = _description.schema.outputs.description || "Not Provided";
|
2020-11-15 19:11:25 +00:00
|
|
|
// And add a Schema for the Return type.
|
|
|
|
(_description as any).parsedOutput = JSON.stringify(_description.schema.outputs);
|
2021-11-16 20:41:08 +00:00
|
|
|
(_description as any).tag = "generic-services";
|
2020-11-15 19:11:25 +00:00
|
|
|
(_description as any).parsedInput = JSON.stringify(_unifySchema(_description.schema.inputs, options));
|
|
|
|
(_description as any).hasInput = _description.schema.inputs.length > 0;
|
|
|
|
(_description as any).required = _description.schema.inputs.filter(param => !param.optional).map(param => param.name);
|
|
|
|
(_description as any).name = _description.id;
|
|
|
|
|
|
|
|
// Determine the Filename.
|
2021-11-16 20:41:08 +00:00
|
|
|
const fileDir = join(options.outputDir, "generic-services");
|
|
|
|
const fileName = join(fileDir, _description.id + "." + options.mode);
|
2020-11-15 19:11:25 +00:00
|
|
|
|
|
|
|
// Determine the Import Pathes.
|
|
|
|
const imports = [
|
|
|
|
{
|
2021-11-16 20:41:08 +00:00
|
|
|
dir: join(process.cwd(), "lib", "types", "nope"),
|
|
|
|
fileName: "nopeDispatcher.interface",
|
|
|
|
name: "pathOfDispatcher"
|
2020-11-15 19:11:25 +00:00
|
|
|
}
|
2021-11-16 20:41:08 +00:00
|
|
|
];
|
2020-11-15 19:11:25 +00:00
|
|
|
|
|
|
|
for (const imp of imports) {
|
|
|
|
const relativDir = relative(fileDir, imp.dir);
|
2021-11-16 20:41:08 +00:00
|
|
|
(_description as any)[imp.name] = replaceAll(join(relativDir, imp.fileName), "\\", "/");
|
2020-11-15 19:11:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write down the Schema:
|
|
|
|
await createFile(
|
|
|
|
// Generate the Path.
|
|
|
|
fileName,
|
|
|
|
renderAPI(_description)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (options.logger) {
|
2021-11-16 20:41:08 +00:00
|
|
|
options.logger.info("Generated -> " + fileName);
|
2020-11-15 19:11:25 +00:00
|
|
|
}
|
|
|
|
} else if (options.logger) {
|
2021-11-16 20:41:08 +00:00
|
|
|
options.logger.warn("parser can not convert: \"" + _description.id + "\". The Function contains functions as parameters");
|
2020-11-15 19:11:25 +00:00
|
|
|
}
|
2020-11-09 06:42:24 +00:00
|
|
|
}
|