diff --git a/bin/index.js b/bin/index.js index 32f2fecbc..1f60d805a 100755 --- a/bin/index.js +++ b/bin/index.js @@ -27,6 +27,16 @@ const params = program .parse(process.argv) .opts(); +if (params.client === 'mappersmith' && params.name === undefined) { + console.error('ERROR: --name option is required for mappersmith client'); + process.exit(1); +} + +if (params.client === 'mappersmith') { + console.log('INFO: --exportServices option is forced to be false for mappersmith client'); + params.exportServices = false; +} + const OpenAPI = require(path.resolve(__dirname, '../dist/index.js')); if (OpenAPI) { diff --git a/package.json b/package.json index 17abcdec0..73d937a30 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "glob": "10.2.1", "jest": "29.5.0", "jest-cli": "29.5.0", + "mappersmith": "^2.38.1", "node-fetch": "2.6.9", "prettier": "2.8.7", "puppeteer": "19.10.1", @@ -121,7 +122,7 @@ "typescript": "4.9.5", "zone.js": "0.13.0" }, - "overrides" : { + "overrides": { "node-fetch": "2.6.9", "rollup": "3.20.2", "typescript": "4.9.5" diff --git a/src/HttpClient.ts b/src/HttpClient.ts index 40c77c7c9..1408dc71e 100644 --- a/src/HttpClient.ts +++ b/src/HttpClient.ts @@ -4,4 +4,5 @@ export enum HttpClient { NODE = 'node', AXIOS = 'axios', ANGULAR = 'angular', + MAPPERSMITH = 'mappersmith', } diff --git a/src/client/interfaces/OperationParameters.d.ts b/src/client/interfaces/OperationParameters.d.ts index 43419438c..bbb067c0c 100644 --- a/src/client/interfaces/OperationParameters.d.ts +++ b/src/client/interfaces/OperationParameters.d.ts @@ -9,4 +9,5 @@ export interface OperationParameters { parametersCookie: OperationParameter[]; parametersHeader: OperationParameter[]; parametersBody: OperationParameter | null; + hasDefault: boolean; } diff --git a/src/openApi/v2/parser/getOperation.ts b/src/openApi/v2/parser/getOperation.ts index 9aa157460..4c43f4499 100644 --- a/src/openApi/v2/parser/getOperation.ts +++ b/src/openApi/v2/parser/getOperation.ts @@ -42,6 +42,7 @@ export const getOperation = ( errors: [], results: [], responseHeader: null, + hasDefault: false }; // Parse the operation parameters (path, query, body, etc). diff --git a/src/openApi/v2/parser/getOperationParameters.ts b/src/openApi/v2/parser/getOperationParameters.ts index f265d1ccc..f3c8cf028 100644 --- a/src/openApi/v2/parser/getOperationParameters.ts +++ b/src/openApi/v2/parser/getOperationParameters.ts @@ -14,6 +14,7 @@ export const getOperationParameters = (openApi: OpenApi, parameters: OpenApiPara parametersCookie: [], parametersHeader: [], parametersBody: null, + hasDefault: false }; // Iterate over the parameters diff --git a/src/openApi/v3/parser/getMappedType.ts b/src/openApi/v3/parser/getMappedType.ts index a7c32fd84..61b8c3162 100644 --- a/src/openApi/v3/parser/getMappedType.ts +++ b/src/openApi/v3/parser/getMappedType.ts @@ -17,7 +17,7 @@ const TYPE_MAPPINGS = new Map([ ['date-time', 'string'], ['password', 'string'], ['string', 'string'], - ['void', 'void'], + ['void', 'null'], ['null', 'null'], ]); diff --git a/src/openApi/v3/parser/getOperation.ts b/src/openApi/v3/parser/getOperation.ts index aee4bd0c2..fcd19ee7d 100644 --- a/src/openApi/v3/parser/getOperation.ts +++ b/src/openApi/v3/parser/getOperation.ts @@ -45,6 +45,7 @@ export const getOperation = ( errors: [], results: [], responseHeader: null, + hasDefault: false }; // Parse the operation parameters (path, query, body, etc). @@ -58,6 +59,7 @@ export const getOperation = ( operation.parametersHeader.push(...parameters.parametersHeader); operation.parametersCookie.push(...parameters.parametersCookie); operation.parametersBody = parameters.parametersBody; + operation.hasDefault = parameters.hasDefault } if (op.requestBody) { diff --git a/src/openApi/v3/parser/getOperationParameters.ts b/src/openApi/v3/parser/getOperationParameters.ts index 051b4a0a9..8ccf98453 100644 --- a/src/openApi/v3/parser/getOperationParameters.ts +++ b/src/openApi/v3/parser/getOperationParameters.ts @@ -14,6 +14,7 @@ export const getOperationParameters = (openApi: OpenApi, parameters: OpenApiPara parametersCookie: [], parametersHeader: [], parametersBody: null, // Not used in V3 -> @see requestBody + hasDefault: false }; // Iterate over the parameters @@ -21,6 +22,9 @@ export const getOperationParameters = (openApi: OpenApi, parameters: OpenApiPara const parameterDef = getRef(openApi, parameterOrReference); const parameter = getOperationParameter(openApi, parameterDef); + // At least one parameter has default value + operationParameters.hasDefault ||= parameter.default !== undefined; + // We ignore the "api-version" param, since we do not want to add this // as the first / default parameter for each of the service calls. if (parameter.prop !== 'api-version') { diff --git a/src/openApi/v3/parser/getOperationResults.ts b/src/openApi/v3/parser/getOperationResults.ts index 9d8111fe8..570c38161 100644 --- a/src/openApi/v3/parser/getOperationResults.ts +++ b/src/openApi/v3/parser/getOperationResults.ts @@ -27,8 +27,8 @@ export const getOperationResults = (operationResponses: OperationResponse[]): Op code: 200, description: '', export: 'generic', - type: 'void', - base: 'void', + type: 'null', + base: 'null', template: null, link: null, isDefinition: false, diff --git a/src/templates/core/mappersmith/request.hbs b/src/templates/core/mappersmith/request.hbs new file mode 100644 index 000000000..c6a855094 --- /dev/null +++ b/src/templates/core/mappersmith/request.hbs @@ -0,0 +1,71 @@ +{{>header}} + + +import forge, { Middleware, Response } from 'mappersmith'; +{{#each services}} +{{#if imports}} + {{#each imports}} + import type { {{{this}}} } from '../models/{{{this}}}'; + {{/each}} +{{/if}} +{{/each}} + +export type {{{clientName}}}MappersmithClient = { +{{#each services}} + {{name}}: { + {{#each operations}} + /** + {{#if deprecated}} + * @deprecated + {{/if}} + {{#if summary}} + * {{{escapeComment summary}}} + {{/if}} + {{#if description}} + * {{{escapeComment description}}} + {{/if}} + {{#if parameters}} + * @param { Object } {{#if hasDefault}}[{{/if}}params{{#if hasDefault}}]{{/if}} - Request parameters. + {{#each parameters}} + * @param { {{>type}} } {{#unless isRequired}}[{{else if default}}[{{/unless}}params.{{{name}}}{{#if default}}={{default}}{{/if}}{{#unless isRequired}}]{{else if default}}]{{/unless}} {{#if description}}{{{escapeComment description}}}{{/if}} + {{/each}} + {{/if}} + {{#each results}} + * @returns { Promisetype}}>> } {{#if description}}{{{escapeComment description}}}{{/if}} + {{/each}} + */ + {{name}}: ({{>parameters2}}) => Promiseresult}}>>, + {{/each}} + }, +{{/each}} +}; + +{{!--export const {{{camelCase clientName}}}MappersmithClientFactory = (host: string): Client<{{{clientName}}}Resource> =>--}} +export const {{{camelCase clientName}}}MappersmithClientFactory = ({ + host, + middleware +}: { + host: string, + middleware?: Middleware[] +}) => + forge({ + clientId: '{{clientName}}', + bodyAttr: 'requestBody', + host, + middleware, + resources: { + {{#each services}} + {{name}}: { + {{#each operations}} + {{name}}: { method: '{{method}}', path: '{{path}}'{{#if hasDefault}}, params: { + {{#each parameters}} + {{#if default}} + {{{name}}}: {{{default}}}, + {{/if}} + {{/each}} + }{{/if}} }, + {{/each}} + }, + {{/each}} + }, + }) as {{{clientName}}}MappersmithClient; diff --git a/src/templates/core/request.hbs b/src/templates/core/request.hbs index c4e2478dd..6d4c2e22d 100644 --- a/src/templates/core/request.hbs +++ b/src/templates/core/request.hbs @@ -3,3 +3,4 @@ {{~#equals @root.httpClient 'axios'}}{{>axios/request}}{{/equals~}} {{~#equals @root.httpClient 'angular'}}{{>angular/request}}{{/equals~}} {{~#equals @root.httpClient 'node'}}{{>node/request}}{{/equals~}} +{{~#equals @root.httpClient 'mappersmith'}}{{>mappersmith/request}}{{/equals~}} diff --git a/src/templates/exportService.hbs b/src/templates/exportService.hbs index d6bccbbeb..61317422e 100644 --- a/src/templates/exportService.hbs +++ b/src/templates/exportService.hbs @@ -17,6 +17,10 @@ import type { {{{this}}} } from '../models/{{{this}}}'; {{/each}} {{/if}} +{{#equals @root.httpClient 'mappersmith'}} +import { Client } from 'mappersmith' +import type { {{{clientName}}}Resource } from '../core/request'; +{{else}} {{#notEquals @root.httpClient 'angular'}} import type { CancelablePromise } from '../core/CancelablePromise'; {{/notEquals}} @@ -30,6 +34,7 @@ import type { BaseHttpRequest } from '../core/BaseHttpRequest'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; {{/if}} +{{/equals}} {{#equals @root.httpClient 'angular'}} @Injectable({ @@ -37,6 +42,13 @@ import { request as __request } from '../core/request'; }) {{/equals}} export class {{{name}}}{{{@root.postfix}}} { + {{#equals @root.httpClient 'mappersmith'}} + private mappersmithClient: Client<{{{clientName}}}Resource> + + constructor(mappersmithClient: Client<{{{clientName}}}Resource>) { + this.mappersmithClient = mappersmithClient + } + {{else}} {{#if @root.exportClient}} constructor(public readonly httpRequest: BaseHttpRequest) {} @@ -46,6 +58,7 @@ export class {{{name}}}{{{@root.postfix}}} { constructor(public readonly http: HttpClient) {} {{/equals}} {{/if}} + {{/equals}} {{#each operations}} /** @@ -70,6 +83,58 @@ export class {{{name}}}{{{@root.postfix}}} { {{/each}} * @throws ApiError */ + {{#equals @root.httpClient 'mappersmith'}} + public async {{{name}}}({{>parameters}}): Promise<{{>result}}> { + return (await this.mappersmithClient.{{{../name}}}.{{{name}}}( + {{#if parameters}} { {{/if}} + + {{#if parametersPath}} + {{#each parametersPath}} + '{{{prop}}}': {{{name}}}, + {{/each}} + {{/if}} +{{!-- {{#if parametersCookie}} --}} +{{!-- cookies: {--}} +{{!-- {{#each parametersCookie}}--}} +{{!-- '{{{prop}}}': {{{name}}},--}} +{{!-- {{/each}}--}} +{{!-- },--}} +{{!-- {{/if}}--}} + {{#if parametersHeader}} + headers: { + {{#each parametersHeader}} + '{{{prop}}}': {{{name}}}, + {{/each}} + }, + {{/if}} + {{#if parametersQuery}} + {{#each parametersQuery}} + '{{{prop}}}': {{{name}}}, + {{/each}} + {{/if}} +{{!-- {{#if parametersForm}}--}} +{{!-- formData: {--}} +{{!-- {{#each parametersForm}}--}} +{{!-- '{{{prop}}}': {{{name}}},--}} +{{!-- {{/each}}--}} +{{!-- },--}} +{{!-- {{/if}}--}} + {{#if parametersBody}} +{{!-- {{#equals parametersBody.in 'formData'}}--}} +{{!-- formData: {{{parametersBody.name}}},--}} +{{!-- {{/equals}}--}} + {{#equals parametersBody.in 'body'}} + body: {{{parametersBody.name}}}, + {{/equals}} +{{!-- {{#if parametersBody.mediaType}}--}} +{{!-- mediaType: '{{{parametersBody.mediaType}}}',--}} +{{!-- {{/if}}--}} + {{/if}} + + {{#if parameters}} } {{/if}} + )).data() + } + {{else}} {{#if @root.exportClient}} {{#equals @root.httpClient 'angular'}} public {{{name}}}({{>parameters}}): Observable<{{>result}}> { @@ -148,5 +213,6 @@ export class {{{name}}}{{{@root.postfix}}} { }); } + {{/equals}} {{/each}} } diff --git a/src/templates/index.hbs b/src/templates/index.hbs index 6f5b27d8c..e86222ab1 100644 --- a/src/templates/index.hbs +++ b/src/templates/index.hbs @@ -4,6 +4,8 @@ export { {{{clientName}}} } from './{{{clientName}}}'; {{/if}} +{{#equals @root.httpClient 'mappersmith'}} +{{else}} {{#if @root.exportCore}} export { ApiError } from './core/ApiError'; {{#if @root.exportClient}} @@ -13,6 +15,7 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise'; export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; {{/if}} +{{/equals}} {{#if @root.exportModels}} {{#if models}} @@ -45,3 +48,8 @@ export { {{{name}}}{{{@root.postfixServices}}} } from './services/{{{name}}}{{{@ {{/each}} {{/if}} {{/if}} + +{{#equals @root.httpClient 'mappersmith'}} +export type { {{clientName}}MappersmithClient } from './core/request'; +export { {{{camelCase clientName}}}MappersmithClientFactory } from './core/request'; +{{/equals}} diff --git a/src/templates/partials/base.hbs b/src/templates/partials/base.hbs index 1799e7d2a..35294958d 100644 --- a/src/templates/partials/base.hbs +++ b/src/templates/partials/base.hbs @@ -4,6 +4,7 @@ {{~#equals @root.httpClient 'axios'}}Blob{{/equals~}} {{~#equals @root.httpClient 'angular'}}Blob{{/equals~}} {{~#equals @root.httpClient 'node'}}Blob{{/equals~}} +{{~#equals @root.httpClient 'mappersmith'}}Blob{{/equals~}} {{~else~}} {{{base}}} {{~/equals~}} diff --git a/src/templates/partials/parameters2.hbs b/src/templates/partials/parameters2.hbs new file mode 100644 index 000000000..725a7473d --- /dev/null +++ b/src/templates/partials/parameters2.hbs @@ -0,0 +1,8 @@ +{{#if parameters}} + params{{#if hasDefault}}?{{/if}}: { + {{#each parameters}} + {{{name}}}{{>isRequired}}: {{>type}}, + {{/each}} + } +{{/if}} + diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index bf77cbdc1..b4559ff29 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -38,6 +38,7 @@ import functionIsStringWithValue from '../templates/core/functions/isStringWithV import functionIsSuccess from '../templates/core/functions/isSuccess.hbs'; import functionResolve from '../templates/core/functions/resolve.hbs'; import templateCoreHttpRequest from '../templates/core/HttpRequest.hbs'; +import mappersmithRequest from '../templates/core/mappersmith/request.hbs'; import nodeGetHeaders from '../templates/core/node/getHeaders.hbs'; import nodeGetRequestBody from '../templates/core/node/getRequestBody.hbs'; import nodeGetResponseBody from '../templates/core/node/getResponseBody.hbs'; @@ -66,6 +67,7 @@ import partialIsNullable from '../templates/partials/isNullable.hbs'; import partialIsReadOnly from '../templates/partials/isReadOnly.hbs'; import partialIsRequired from '../templates/partials/isRequired.hbs'; import partialParameters from '../templates/partials/parameters.hbs'; +import partialParameters2 from '../templates/partials/parameters2.hbs'; import partialResult from '../templates/partials/result.hbs'; import partialSchema from '../templates/partials/schema.hbs'; import partialSchemaArray from '../templates/partials/schemaArray.hbs'; @@ -147,6 +149,7 @@ export const registerHandlebarTemplates = (root: { Handlebars.registerPartial('isReadOnly', Handlebars.template(partialIsReadOnly)); Handlebars.registerPartial('isRequired', Handlebars.template(partialIsRequired)); Handlebars.registerPartial('parameters', Handlebars.template(partialParameters)); + Handlebars.registerPartial('parameters2', Handlebars.template(partialParameters2)); Handlebars.registerPartial('result', Handlebars.template(partialResult)); Handlebars.registerPartial('schema', Handlebars.template(partialSchema)); Handlebars.registerPartial('schemaArray', Handlebars.template(partialSchemaArray)); @@ -220,5 +223,8 @@ export const registerHandlebarTemplates = (root: { Handlebars.registerPartial('angular/sendRequest', Handlebars.template(angularSendRequest)); Handlebars.registerPartial('angular/request', Handlebars.template(angularRequest)); + // Specific files for the mappersmith client implementation + Handlebars.registerPartial('mappersmith/request', Handlebars.template(mappersmithRequest)); + return templates; }; diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index cea2f3d88..4c7d20033 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -1,7 +1,7 @@ import { resolve } from 'path'; import type { Client } from '../client/interfaces/Client'; -import type { HttpClient } from '../HttpClient'; +import { HttpClient } from '../HttpClient'; import type { Indent } from '../Indent'; import { mkdir, rmdir } from './fileSystem'; import { isDefined } from './isDefined'; @@ -94,7 +94,7 @@ export const writeClient = async ( await writeClientModels(client.models, templates, outputPathModels, httpClient, useUnionTypes, indent); } - if (isDefined(clientName)) { + if (isDefined(clientName) && httpClient !== HttpClient.MAPPERSMITH) { await mkdir(outputPath); await writeClientClass(client, templates, outputPath, httpClient, clientName, indent, postfixServices); } @@ -112,7 +112,8 @@ export const writeClient = async ( exportSchemas, postfixServices, postfixModels, - clientName + clientName, + httpClient ); } }; diff --git a/src/utils/writeClientCore.ts b/src/utils/writeClientCore.ts index 6d35849d2..722728fe0 100644 --- a/src/utils/writeClientCore.ts +++ b/src/utils/writeClientCore.ts @@ -1,13 +1,13 @@ -import { resolve } from 'path'; +import {resolve} from 'path'; -import type { Client } from '../client/interfaces/Client'; -import type { HttpClient } from '../HttpClient'; -import type { Indent } from '../Indent'; -import { copyFile, exists, writeFile } from './fileSystem'; -import { formatIndentation as i } from './formatIndentation'; -import { getHttpRequestName } from './getHttpRequestName'; -import { isDefined } from './isDefined'; -import type { Templates } from './registerHandlebarTemplates'; +import type {Client} from '../client/interfaces/Client'; +import {HttpClient} from '../HttpClient'; +import type {Indent} from '../Indent'; +import {copyFile, exists, writeFile} from './fileSystem'; +import {formatIndentation as i} from './formatIndentation'; +import {getHttpRequestName} from './getHttpRequestName'; +import {isDefined} from './isDefined'; +import type {Templates} from './registerHandlebarTemplates'; /** * Generate OpenAPI core files, this includes the basic boilerplate code to handle requests. @@ -35,16 +35,19 @@ export const writeClientCore = async ( httpRequest, server: client.server, version: client.version, + services: client.services, }; - await writeFile(resolve(outputPath, 'OpenAPI.ts'), i(templates.core.settings(context), indent)); - await writeFile(resolve(outputPath, 'ApiError.ts'), i(templates.core.apiError(context), indent)); - await writeFile(resolve(outputPath, 'ApiRequestOptions.ts'), i(templates.core.apiRequestOptions(context), indent)); - await writeFile(resolve(outputPath, 'ApiResult.ts'), i(templates.core.apiResult(context), indent)); - await writeFile(resolve(outputPath, 'CancelablePromise.ts'), i(templates.core.cancelablePromise(context), indent)); + if (httpClient !== HttpClient.MAPPERSMITH) { + await writeFile(resolve(outputPath, 'OpenAPI.ts'), i(templates.core.settings(context), indent)); + await writeFile(resolve(outputPath, 'ApiError.ts'), i(templates.core.apiError(context), indent)); + await writeFile(resolve(outputPath, 'ApiRequestOptions.ts'), i(templates.core.apiRequestOptions(context), indent)); + await writeFile(resolve(outputPath, 'ApiResult.ts'), i(templates.core.apiResult(context), indent)); + await writeFile(resolve(outputPath, 'CancelablePromise.ts'), i(templates.core.cancelablePromise(context), indent)); + } await writeFile(resolve(outputPath, 'request.ts'), i(templates.core.request(context), indent)); - if (isDefined(clientName)) { + if (isDefined(clientName) && httpClient !== HttpClient.MAPPERSMITH) { await writeFile(resolve(outputPath, 'BaseHttpRequest.ts'), i(templates.core.baseHttpRequest(context), indent)); await writeFile(resolve(outputPath, `${httpRequest}.ts`), i(templates.core.httpRequest(context), indent)); } diff --git a/src/utils/writeClientIndex.ts b/src/utils/writeClientIndex.ts index 5044294d5..1ea75e981 100644 --- a/src/utils/writeClientIndex.ts +++ b/src/utils/writeClientIndex.ts @@ -6,6 +6,7 @@ import { isDefined } from './isDefined'; import { Templates } from './registerHandlebarTemplates'; import { sortModelsByName } from './sortModelsByName'; import { sortServicesByName } from './sortServicesByName'; +import {HttpClient} from '../HttpClient' /** * Generate the OpenAPI client index file using the Handlebar template and write it to disk. @@ -22,6 +23,7 @@ import { sortServicesByName } from './sortServicesByName'; * @param postfixServices Service name postfix * @param postfixModels Model name postfix * @param clientName Custom client class name + * @param httpClient The selected httpClient (fetch, xhr, node, axios or mappersmith) */ export const writeClientIndex = async ( client: Client, @@ -34,7 +36,8 @@ export const writeClientIndex = async ( exportSchemas: boolean, postfixServices: string, postfixModels: string, - clientName?: string + clientName?: string, + httpClient?: string ): Promise => { const templateResult = templates.index({ exportCore, @@ -49,7 +52,8 @@ export const writeClientIndex = async ( version: client.version, models: sortModelsByName(client.models), services: sortServicesByName(client.services), - exportClient: isDefined(clientName), + exportClient: isDefined(clientName) && httpClient !== HttpClient.MAPPERSMITH, + httpClient, }); await writeFile(resolve(outputPath, 'index.ts'), templateResult); diff --git a/src/utils/writeClientServices.ts b/src/utils/writeClientServices.ts index 2f95341d2..97e36234c 100644 --- a/src/utils/writeClientServices.ts +++ b/src/utils/writeClientServices.ts @@ -1,7 +1,7 @@ import { resolve } from 'path'; import type { Service } from '../client/interfaces/Service'; -import type { HttpClient } from '../HttpClient'; +import { HttpClient } from '../HttpClient'; import type { Indent } from '../Indent'; import { writeFile } from './fileSystem'; import { formatCode as f } from './formatCode'; @@ -40,7 +40,8 @@ export const writeClientServices = async ( useUnionTypes, useOptions, postfix, - exportClient: isDefined(clientName), + exportClient: isDefined(clientName) && httpClient !== HttpClient.MAPPERSMITH, + clientName, }); await writeFile(file, i(f(templateResult), indent)); }