diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index cea2f3d88..f842124d0 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -11,6 +11,7 @@ import { writeClientClass } from './writeClientClass'; import { writeClientCore } from './writeClientCore'; import { writeClientIndex } from './writeClientIndex'; import { writeClientModels } from './writeClientModels'; +import { writeClientRequest } from './writeClientRequest'; import { writeClientSchemas } from './writeClientSchemas'; import { writeClientServices } from './writeClientServices'; @@ -99,6 +100,8 @@ export const writeClient = async ( await writeClientClass(client, templates, outputPath, httpClient, clientName, indent, postfixServices); } + writeClientRequest(client.services, outputPath); + if (exportCore || exportServices || exportSchemas || exportModels) { await mkdir(outputPath); await writeClientIndex( diff --git a/src/utils/writeClientRequest.ts b/src/utils/writeClientRequest.ts new file mode 100644 index 000000000..550c9ac84 --- /dev/null +++ b/src/utils/writeClientRequest.ts @@ -0,0 +1,118 @@ +import { writeFileSync } from 'fs'; + +import { OperationParameter } from '../client/interfaces/OperationParameter'; +import { OperationResponse } from '../client/interfaces/OperationResponse'; +import { Service } from '../client/interfaces/Service'; + +const nl = '\n\t'; + +/** + * @param services Array of Services to write + * @param outputPath Directory to write the generated files to + */ +export const writeClientRequest = (services: Service[], outputPath: string) => { + const serviceName = getServiceName(outputPath); + + const endPointsName = `${serviceName + .split('-') + .map(v => v[0].toUpperCase() + v.slice(1)) + .join('')}Endpoints`; + + writeFileSync( + outputPath + '/services.ts', + `${mapImports(services)}\n\nexport type Services = { + "${serviceName}": ${endPointsName} + }` + + '\n\n' + + `export type ${endPointsName} = {` + + nl + + mapEndpoints(services) + + '\n}' + ); +}; + +const getServiceName = (path: string) => { + const gen = '/generated/'; + const generatedIdx = path.lastIndexOf(gen) + gen.length; + const endIdx = path.indexOf('/', generatedIdx); + return path.substring(generatedIdx, endIdx === -1 ? undefined : endIdx); +}; + +const REPLACE_TYPES = new Map([['binary', 'Blob']]); + +const mapImports = (services: Service[]) => { + const imports = new Set(); + + for (const service of services) + for (const operation of service.operations) { + mapOperationType(operation.parameters, imports); + mapOperationType(operation.results, imports); + } + + if (imports.size === 0) return ''; + + return `import { ${Array.from(imports).join(',')} } from '.';`; +}; + +const mapOperationType = (p: Pick[], importsG: Set) => { + p.forEach(({ type, imports }, index) => { + const replacement = REPLACE_TYPES.get(type); + if (replacement) p[index].type = replacement; + if (imports?.length) imports.forEach(v => importsG.add(v)); + }); +}; + +const mapEndpoints = (services: Service[]) => { + const endpoints = new Map(); + + for (const service of services) + for (const operation of service.operations) { + endpoints.set(`${operation.method} ${operation.path}`, { + parameters: operation.parameters, + response: operation.results, + }); + } + + return Array.from(endpoints).map( + ([key, { parameters, response }]) => + `"${key}": {` + + nl + + '\t' + + `${constructOperation(parameters, 'query')}` + + `${constructOperation(parameters, 'path')}` + + `${constructOperationSingle( + parameters.filter(p => p.in === 'body'), + 'body' + )}` + + `${constructOperationSingle(response, 'response', false)}` + + nl + + `}` + ); +}; + +const constructOperation = (parameters: OperationParameter[], type: OperationParameter['in']) => { + const params = parameters.filter(p => p.in === type); + if (!params.length) return ''; + let isRequired = false; + const paramsStr = + params + .map(v => { + if (v.isRequired) isRequired = true; + return `"${v.prop}"${optional(v.isRequired)}: ${v.type + array(v.export)}`; + }) + .join('\n') + '\n}\n\t'; + return `${type + optional(isRequired)}: {\n\t` + paramsStr; +}; + +const constructOperationSingle = ( + parameters: Pick[], + type: string, + mapOptional = true +) => { + if (!parameters.length) return ''; + const p = parameters[0]; + return `${type + optional(mapOptional ? p.isRequired : true)}: ` + p.type + array(p.export) + '\n\t'; +}; + +const optional = (isRequired: boolean) => (isRequired ? '' : '?'); +const array = (exp: OperationParameter['export']) => (exp === 'array' ? '[]' : '');