- Working solution for export client

This commit is contained in:
Ferdi Koomen 2022-01-25 14:37:41 +01:00
parent e1268b197a
commit 5f58982210
25 changed files with 102 additions and 138 deletions

View File

@ -39,13 +39,13 @@ export type Options = {
* @param clientName Custom client class name
* @param useOptions Use options or arguments functions
* @param useUnionTypes Use union types instead of enums
* @param exportCore: Generate core client classes
* @param exportServices: Generate services
* @param exportModels: Generate models
* @param exportSchemas: Generate schemas
* @param indent: Indentation options (4, 2 or tab)
* @param postfix: Service name postfix
* @param request: Path to custom request file
* @param exportCore Generate core client classes
* @param exportServices Generate services
* @param exportModels Generate models
* @param exportSchemas Generate schemas
* @param indent Indentation options (4, 2 or tab)
* @param postfix Service name postfix
* @param request Path to custom request file
* @param write Write the files to disk (true or false)
*/
export const generate = async ({

View File

@ -5,7 +5,6 @@ import type { OpenApiOperation } from '../interfaces/OpenApiOperation';
import { getOperationErrors } from './getOperationErrors';
import { getOperationName } from './getOperationName';
import { getOperationParameters } from './getOperationParameters';
import { getOperationPath } from './getOperationPath';
import { getOperationResponseHeader } from './getOperationResponseHeader';
import { getOperationResponses } from './getOperationResponses';
import { getOperationResults } from './getOperationResults';
@ -22,7 +21,6 @@ export const getOperation = (
): Operation => {
const serviceName = getServiceName(tag);
const operationName = getOperationName(op.operationId || `${method}`);
const operationPath = getOperationPath(url);
// Create a new operation object for this method.
const operation: Operation = {
@ -32,7 +30,7 @@ export const getOperation = (
description: op.description || null,
deprecated: op.deprecated === true,
method: method.toUpperCase(),
path: operationPath,
path: url,
parameters: [...pathParams.parameters],
parametersPath: [...pathParams.parametersPath],
parametersQuery: [...pathParams.parametersQuery],

View File

@ -1,18 +0,0 @@
import { getOperationPath } from './getOperationPath';
describe('getOperationPath', () => {
it('should produce correct result', () => {
expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual(
'/api/v${OpenAPI.VERSION}/list/${id}/${type}'
);
expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}');
expect(getOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}');
expect(getOperationPath('/api/{foobar}')).toEqual('/api/${foobar}');
expect(getOperationPath('/api/{fooBar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{foo-bar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{foo_bar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{foo.bar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{Foo-Bar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{FOO-BAR}')).toEqual('/api/${fooBar}');
});
});

View File

@ -1,16 +0,0 @@
import { getOperationParameterName } from './getOperationParameterName';
/**
* Get the final service path, this replaces the "{api-version}" placeholder
* with a new template string placeholder so we can dynamically inject the
* OpenAPI version without the need to hardcode this in the URL.
* Plus we return the correct parameter names to replace in the URL.
* @param path
*/
export const getOperationPath = (path: string): string => {
return path
.replace(/\{(.*?)\}/g, (_, w: string) => {
return `\${${getOperationParameterName(w)}}`;
})
.replace('${apiVersion}', '${OpenAPI.VERSION}');
};

View File

@ -6,7 +6,6 @@ import type { OpenApiRequestBody } from '../interfaces/OpenApiRequestBody';
import { getOperationErrors } from './getOperationErrors';
import { getOperationName } from './getOperationName';
import { getOperationParameters } from './getOperationParameters';
import { getOperationPath } from './getOperationPath';
import { getOperationRequestBody } from './getOperationRequestBody';
import { getOperationResponseHeader } from './getOperationResponseHeader';
import { getOperationResponses } from './getOperationResponses';
@ -25,7 +24,6 @@ export const getOperation = (
): Operation => {
const serviceName = getServiceName(tag);
const operationName = getOperationName(op.operationId || `${method}`);
const operationPath = getOperationPath(url);
// Create a new operation object for this method.
const operation: Operation = {
@ -35,7 +33,7 @@ export const getOperation = (
description: op.description || null,
deprecated: op.deprecated === true,
method: method.toUpperCase(),
path: operationPath,
path: url,
parameters: [...pathParams.parameters],
parametersPath: [...pathParams.parametersPath],
parametersQuery: [...pathParams.parametersQuery],

View File

@ -1,18 +0,0 @@
import { getOperationPath } from './getOperationPath';
describe('getOperationPath', () => {
it('should produce correct result', () => {
expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual(
'/api/v${OpenAPI.VERSION}/list/${id}/${type}'
);
expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}');
expect(getOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}');
expect(getOperationPath('/api/{foobar}')).toEqual('/api/${foobar}');
expect(getOperationPath('/api/{fooBar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{foo-bar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{foo_bar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{foo.bar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{Foo-Bar}')).toEqual('/api/${fooBar}');
expect(getOperationPath('/api/{FOO-BAR}')).toEqual('/api/${fooBar}');
});
});

View File

@ -1,16 +0,0 @@
import { getOperationParameterName } from './getOperationParameterName';
/**
* Get the final service path, this replaces the "{api-version}" placeholder
* with a new template string placeholder so we can dynamically inject the
* OpenAPI version without the need to hardcode this in the URL.
* Plus we return the correct parameter names to replace in the URL.
* @param path
*/
export const getOperationPath = (path: string): string => {
return path
.replace(/\{(.*?)\}/g, (_, w: string) => {
return `\${${getOperationParameterName(w)}}`;
})
.replace('${apiVersion}', '${OpenAPI.VERSION}');
};

View File

@ -14,9 +14,9 @@ type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest;
export class {{{clientName}}} {
{{#each services}}
{{#each services}}
public readonly {{{camelCase name}}}: {{{name}}}{{{@root.postfix}}};
{{/each}}
{{/each}}
private readonly request: BaseHttpRequest;

View File

@ -2,7 +2,8 @@
export type ApiRequestOptions = {
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
readonly path: string;
readonly url: string;
readonly path?: Record<string, any>;
readonly cookies?: Record<string, any>;
readonly headers?: Record<string, any>;
readonly query?: Record<string, any>;

View File

@ -5,6 +5,7 @@ import type { CancelablePromise } from './CancelablePromise';
import type { OpenAPIConfig } from './OpenAPI';
export class BaseHttpRequest {
protected readonly config: OpenAPIConfig;
constructor(config: OpenAPIConfig) {

View File

@ -1,9 +1,18 @@
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
const path = config.ENCODE_PATH ? config.ENCODE_PATH(options.path) : options.path;
const url = `${config.BASE}${path}`;
if (options.query) {
return `${url}${getQueryString(options.query)}`;
}
const encoder = config.ENCODE_PATH || encodeURI;
return url;
const path = options.url
.replace('{api-version}', config.VERSION)
.replace(/{(.*?)}/g, (substring: string, group: string) => {
if (options.path?.hasOwnProperty(group)) {
return encoder(String(options.path[group]));
}
return substring;
});
const url = `${config.BASE}${path}`;
if (options.query) {
return `${url}${getQueryString(options.query)}`;
}
return url;
};

View File

@ -6,12 +6,22 @@ import type { {{{this}}} } from '../models/{{{this}}}';
{{/each}}
{{/if}}
import type { CancelablePromise } from '../core/CancelablePromise';
import { request as __request } from '../core/request';
{{#if @root.useVersion}}
{{#if @root.exportClient}}
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
{{else}}
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
{{/if}}
export class {{{name}}}{{{@root.postfix}}} {
{{#if @root.exportClient}}
private readonly httpRequest: BaseHttpRequest;
constructor(httpRequest: BaseHttpRequest) {
this.httpRequest = httpRequest;
}
{{/if}}
{{#each operations}}
/**
@ -36,10 +46,23 @@ export class {{{name}}}{{{@root.postfix}}} {
{{/each}}
* @throws ApiError
*/
{{#if @root.exportClient}}
public {{{name}}}({{>parameters}}): CancelablePromise<{{>result}}> {
return this.httpRequest.request({
{{/if}}
{{#unless @root.exportClient}}
public static {{{name}}}({{>parameters}}): CancelablePromise<{{>result}}> {
return __request(OpenAPI, {
{{/unless}}
method: '{{{method}}}',
path: `{{{path}}}`,
url: '{{{path}}}',
{{#if parametersPath}}
path: {
{{#each parametersPath}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersCookie}}
cookies: {
{{#each parametersCookie}}

View File

@ -2,7 +2,7 @@ import RefParser from 'json-schema-ref-parser';
/**
* Load and parse te open api spec. If the file extension is ".yml" or ".yaml"
* we will try to parse the file as a YAML spec, otherwise we will fallback
* we will try to parse the file as a YAML spec, otherwise we will fall back
* on parsing the file as JSON.
* @param location: Path or url
*/

View File

@ -4,8 +4,8 @@ import { postProcessModelEnums } from './postProcessModelEnums';
import { postProcessModelImports } from './postProcessModelImports';
/**
* Post process the model.
* This will cleanup any double imports or enum values.
* Post processes the model.
* This will clean up any double imports or enum values.
* @param model
*/
export const postProcessModel = (model: Model): Model => {

View File

@ -22,15 +22,15 @@ import { writeClientServices } from './writeClientServices';
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param useOptions Use options or arguments functions
* @param useUnionTypes Use union types instead of enums
* @param exportCore: Generate core client classes
* @param exportServices: Generate services
* @param exportModels: Generate models
* @param exportSchemas: Generate schemas
* @param exportSchemas: Generate schemas
* @param indent: Indentation options (4, 2 or tab)
* @param postfix: Service name postfix
* @param clientName: Custom client class name
* @param request: Path to custom request file
* @param exportCore Generate core client classes
* @param exportServices Generate services
* @param exportModels Generate models
* @param exportSchemas Generate schemas
* @param exportSchemas Generate schemas
* @param indent Indentation options (4, 2 or tab)
* @param postfix Service name postfix
* @param clientName Custom client class name
* @param request Path to custom request file
*/
export const writeClient = async (
client: Client,
@ -75,7 +75,8 @@ export const writeClient = async (
useUnionTypes,
useOptions,
indent,
postfix
postfix,
clientName
);
}

View File

@ -20,8 +20,8 @@ import { sortServicesByName } from './sortServicesByName';
* @param outputPath Directory to write the generated files to
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param clientName Custom client class name
* @param indent: Indentation options (4, 2 or tab)
* @param postfix: Service name postfix
* @param indent Indentation options (4, 2 or tab)
* @param postfix Service name postfix
*/
export const writeClientClass = async (
client: Client,

View File

@ -15,8 +15,9 @@ import type { Templates } from './registerHandlebarTemplates';
* @param templates The loaded handlebar templates
* @param outputPath Directory to write the generated files to
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param indent: Indentation options (4, 2 or tab)
* @param request: Path to custom request file
* @param indent Indentation options (4, 2 or tab)
* @param clientName Custom client class name
* @param request Path to custom request file
*/
export const writeClientCore = async (
client: Client,

View File

@ -14,11 +14,11 @@ import { sortServicesByName } from './sortServicesByName';
* @param templates The loaded handlebar templates
* @param outputPath Directory to write the generated files to
* @param useUnionTypes Use union types instead of enums
* @param exportCore: Generate core
* @param exportServices: Generate services
* @param exportModels: Generate models
* @param exportSchemas: Generate schemas
* @param postfix: Service name postfix
* @param exportCore Generate core
* @param exportServices Generate services
* @param exportModels Generate models
* @param exportSchemas Generate schemas
* @param postfix Service name postfix
*/
export const writeClientIndex = async (
client: Client,

View File

@ -15,7 +15,7 @@ import type { Templates } from './registerHandlebarTemplates';
* @param outputPath Directory to write the generated files to
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param useUnionTypes Use union types instead of enums
* @param indent: Indentation options (4, 2 or tab)
* @param indent Indentation options (4, 2 or tab)
*/
export const writeClientModels = async (
models: Model[],

View File

@ -15,7 +15,7 @@ import type { Templates } from './registerHandlebarTemplates';
* @param outputPath Directory to write the generated files to
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param useUnionTypes Use union types instead of enums
* @param indent: Indentation options (4, 2 or tab)
* @param indent Indentation options (4, 2 or tab)
*/
export const writeClientSchemas = async (
models: Model[],

View File

@ -6,10 +6,9 @@ import type { Indent } from '../Indent';
import { writeFile } from './fileSystem';
import { formatCode as f } from './formatCode';
import { formatIndentation as i } from './formatIndentation';
import { isDefined } from './isDefined';
import type { Templates } from './registerHandlebarTemplates';
const VERSION_TEMPLATE_STRING = 'OpenAPI.VERSION';
/**
* Generate Services using the Handlebar template and write to disk.
* @param services Array of Services to write
@ -18,8 +17,9 @@ const VERSION_TEMPLATE_STRING = 'OpenAPI.VERSION';
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param useUnionTypes Use union types instead of enums
* @param useOptions Use options or arguments functions
* @param indent: Indentation options (4, 2 or tab)
* @param postfix: Service name postfix
* @param indent Indentation options (4, 2 or tab)
* @param postfix Service name postfix
* @param clientName Custom client class name
*/
export const writeClientServices = async (
services: Service[],
@ -29,18 +29,18 @@ export const writeClientServices = async (
useUnionTypes: boolean,
useOptions: boolean,
indent: Indent,
postfix: string
postfix: string,
clientName?: string
): Promise<void> => {
for (const service of services) {
const file = resolve(outputPath, `${service.name}${postfix}.ts`);
const useVersion = service.operations.some(operation => operation.path.includes(VERSION_TEMPLATE_STRING));
const templateResult = templates.exports.service({
...service,
httpClient,
useUnionTypes,
useVersion,
useOptions,
postfix,
exportClient: isDefined(clientName),
});
await writeFile(file, i(f(templateResult), indent));
}

View File

@ -4,7 +4,7 @@ import type { OpenAPIConfig } from './OpenAPI';
export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise<T> => {
return new CancelablePromise((resolve, reject, onCancel) => {
const url = `${config.BASE}${options.path}`;
const url = `${config.BASE}${options.path}`.replace('{api-version}', config.VERSION);
try {
// Do your request...

View File

@ -7,7 +7,7 @@ const generate = async (input, output) => {
await OpenAPI.generate({
input,
output,
httpClient: OpenAPI.HttpClient.NODE,
httpClient: OpenAPI.HttpClient.FETCH,
useOptions: false,
useUnionTypes: false,
exportCore: true,

View File

@ -89,7 +89,7 @@
"required": true
},
{
"description": "This is the parameter that is send as request body",
"description": "This is the parameter that is sent as request body",
"name": "parameterBody",
"in": "body",
"type": "string",
@ -168,7 +168,7 @@
"required": true
},
{
"description": "This is the parameter that is send as request body",
"description": "This is the parameter that is sent as request body",
"name": "PARAMETER-BODY",
"in": "body",
"type": "string",
@ -574,7 +574,7 @@
"operationId": "CollectionFormat",
"parameters": [
{
"description": "This is an array parameter that is send as csv format (comma-separated values)",
"description": "This is an array parameter that is sent as csv format (comma-separated values)",
"name": "parameterArrayCSV",
"in": "query",
"required": true,
@ -585,7 +585,7 @@
"collectionFormat": "csv"
},
{
"description": "This is an array parameter that is send as ssv format (space-separated values)",
"description": "This is an array parameter that is sent as ssv format (space-separated values)",
"name": "parameterArraySSV",
"in": "query",
"required": true,
@ -596,7 +596,7 @@
"collectionFormat": "ssv"
},
{
"description": "This is an array parameter that is send as tsv format (tab-separated values)",
"description": "This is an array parameter that is sent as tsv format (tab-separated values)",
"name": "parameterArrayTSV",
"in": "query",
"required": true,
@ -607,7 +607,7 @@
"collectionFormat": "tsv"
},
{
"description": "This is an array parameter that is send as pipes format (pipe-separated values)",
"description": "This is an array parameter that is sent as pipes format (pipe-separated values)",
"name": "parameterArrayPipes",
"in": "query",
"required": true,
@ -618,7 +618,7 @@
"collectionFormat": "pipes"
},
{
"description": "This is an array parameter that is send as multi format (multiple parameter instances)",
"description": "This is an array parameter that is sent as multi format (multiple parameter instances)",
"name": "parameterArrayMulti",
"in": "query",
"required": true,

View File

@ -814,7 +814,7 @@
"operationId": "CollectionFormat",
"parameters": [
{
"description": "This is an array parameter that is send as csv format (comma-separated values)",
"description": "This is an array parameter that is sent as csv format (comma-separated values)",
"name": "parameterArrayCSV",
"in": "query",
"required": true,
@ -828,7 +828,7 @@
"collectionFormat": "csv"
},
{
"description": "This is an array parameter that is send as ssv format (space-separated values)",
"description": "This is an array parameter that is sent as ssv format (space-separated values)",
"name": "parameterArraySSV",
"in": "query",
"required": true,
@ -842,7 +842,7 @@
"collectionFormat": "ssv"
},
{
"description": "This is an array parameter that is send as tsv format (tab-separated values)",
"description": "This is an array parameter that is sent as tsv format (tab-separated values)",
"name": "parameterArrayTSV",
"in": "query",
"required": true,
@ -856,7 +856,7 @@
"collectionFormat": "tsv"
},
{
"description": "This is an array parameter that is send as pipes format (pipe-separated values)",
"description": "This is an array parameter that is sent as pipes format (pipe-separated values)",
"name": "parameterArrayPipes",
"in": "query",
"required": true,
@ -870,7 +870,7 @@
"collectionFormat": "pipes"
},
{
"description": "This is an array parameter that is send as multi format (multiple parameter instances)",
"description": "This is an array parameter that is sent as multi format (multiple parameter instances)",
"name": "parameterArrayMulti",
"in": "query",
"required": true,