import { readFile } from "fs/promises"; import * as handlebars from 'handlebars'; import { join, relative } from "path"; import { Logger } from 'winston'; import { createFile, createPath } from "../../helpers/fileMethods"; import { deepClone } from "../../helpers/objectMethods"; import { replaceAll } from "../../helpers/stringMethods"; import { IJsonSchema } from '../../types/IJSONSchema'; import { INopeDescriptorFunctionParameter } from "../../types/nope/nopeDescriptor.interface"; import { IParsableDescription } from "../../types/nope/nopeModule.interface"; /** * Helper function to merge the Parameters into 1 description. * @param elements */ function _unifySchema(elements: INopeDescriptorFunctionParameter[]){ const ret: IJsonSchema = { type: 'object', properties: {}, required: [] } for (const item of elements){ // Assign the Schema ret[item.name] = item.schema; // 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 parseToOpenAPI(description: IParsableDescription, options: { outputDir: string, logger?: Logger, }){ const _description = deepClone(description); // load the Template. const template = await readFile(join(process.cwd(), 'lib' , 'parsers', 'open-api' ,'templates', 'openApiSchema.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. 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; // 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) : method.schema.outputs; // 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); // 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); (method as any)[imp.name] = replaceAll(join(relativDir, imp.fileName), '\\', '/'); } // Write down the Schema: await createFile( // Generate the Path. fileName, renderAPI(method) ); if (options.logger) { options.logger.info('Generated -> ' + fileName); } } } }