- Added multiple tags support

This commit is contained in:
Ferdi Koomen 2021-11-11 13:01:11 +01:00
parent 697de58134
commit f083ffb79d
15 changed files with 329 additions and 70 deletions

View File

@ -257,7 +257,7 @@ export const $MyModel = {
format: 'date-time',
},
},
};
} as const;
```
These runtime object are prefixed with a `$` character and expose all the interesting attributes of a model

View File

@ -10,25 +10,25 @@ import { getOperationPath } from './getOperationPath';
import { getOperationResponseHeader } from './getOperationResponseHeader';
import { getOperationResponses } from './getOperationResponses';
import { getOperationResults } from './getOperationResults';
import { getServiceClassName } from './getServiceClassName';
import { getServiceName } from './getServiceName';
import { sortByRequired } from './sortByRequired';
export function getOperation(
openApi: OpenApi,
url: string,
method: string,
tag: string,
op: OpenApiOperation,
pathParams: OperationParameters
): Operation {
const serviceName = op.tags?.[0] || 'Service';
const serviceClassName = getServiceClassName(serviceName);
const operationNameFallback = `${method}${serviceClassName}`;
const serviceName = getServiceName(tag);
const operationNameFallback = `${method}${serviceName}`;
const operationName = getOperationName(op.operationId || operationNameFallback);
const operationPath = getOperationPath(url);
// Create a new operation object for this method.
const operation: Operation = {
service: serviceClassName,
service: serviceName,
name: operationName,
summary: getComment(op.summary),
description: getComment(op.description),

View File

@ -1,13 +0,0 @@
import { getServiceClassName } from './getServiceClassName';
describe('getServiceClassName', () => {
it('should produce correct result', () => {
expect(getServiceClassName('')).toEqual('');
expect(getServiceClassName('FooBar')).toEqual('FooBar');
expect(getServiceClassName('Foo Bar')).toEqual('FooBar');
expect(getServiceClassName('foo bar')).toEqual('FooBar');
expect(getServiceClassName('@fooBar')).toEqual('FooBar');
expect(getServiceClassName('$fooBar')).toEqual('FooBar');
expect(getServiceClassName('123fooBar')).toEqual('FooBar');
});
});

View File

@ -0,0 +1,13 @@
import { getServiceName } from './getServiceName';
describe('getServiceName', () => {
it('should produce correct result', () => {
expect(getServiceName('')).toEqual('');
expect(getServiceName('FooBar')).toEqual('FooBar');
expect(getServiceName('Foo Bar')).toEqual('FooBar');
expect(getServiceName('foo bar')).toEqual('FooBar');
expect(getServiceName('@fooBar')).toEqual('FooBar');
expect(getServiceName('$fooBar')).toEqual('FooBar');
expect(getServiceName('123fooBar')).toEqual('FooBar');
});
});

View File

@ -1,10 +1,10 @@
import camelCase from 'camelcase';
/**
* Convert the input value to a correct service classname. This converts
* Convert the input value to a correct service name. This converts
* the input string to PascalCase.
*/
export function getServiceClassName(value: string): string {
export function getServiceName(value: string): string {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')

View File

@ -1,4 +1,5 @@
import type { Service } from '../../../client/interfaces/Service';
import { unique } from '../../../utils/unique';
import type { OpenApi } from '../interfaces/OpenApi';
import { getOperation } from './getOperation';
import { getOperationParameters } from './getOperationParameters';
@ -27,20 +28,23 @@ export function getServices(openApi: OpenApi): Service[] {
case 'patch':
// Each method contains an OpenAPI operation, we parse the operation
const op = path[method]!;
const operation = getOperation(openApi, url, method, op, pathParams);
const tags = op.tags?.filter(unique) || ['Service'];
tags.forEach(tag => {
const operation = getOperation(openApi, url, method, tag, op, pathParams);
// If we have already declared a service, then we should fetch that and
// append the new method to it. Otherwise we should create a new service object.
const service: Service = services.get(operation.service) || {
name: operation.service,
operations: [],
imports: [],
};
// If we have already declared a service, then we should fetch that and
// append the new method to it. Otherwise we should create a new service object.
const service: Service = services.get(operation.service) || {
name: operation.service,
operations: [],
imports: [],
};
// Push the operation in the service
service.operations.push(operation);
service.imports.push(...operation.imports);
services.set(operation.service, service);
// Push the operation in the service
service.operations.push(operation);
service.imports.push(...operation.imports);
services.set(operation.service, service);
});
break;
}
}

View File

@ -13,25 +13,25 @@ import { getOperationResponseHeader } from './getOperationResponseHeader';
import { getOperationResponses } from './getOperationResponses';
import { getOperationResults } from './getOperationResults';
import { getRef } from './getRef';
import { getServiceClassName } from './getServiceClassName';
import { getServiceName } from './getServiceName';
import { sortByRequired } from './sortByRequired';
export function getOperation(
openApi: OpenApi,
url: string,
method: string,
tag: string,
op: OpenApiOperation,
pathParams: OperationParameters
): Operation {
const serviceName = op.tags?.[0] || 'Service';
const serviceClassName = getServiceClassName(serviceName);
const operationNameFallback = `${method}${serviceClassName}`;
const serviceName = getServiceName(tag);
const operationNameFallback = `${method}${serviceName}`;
const operationName = getOperationName(op.operationId || operationNameFallback);
const operationPath = getOperationPath(url);
// Create a new operation object for this method.
const operation: Operation = {
service: serviceClassName,
service: serviceName,
name: operationName,
summary: getComment(op.summary),
description: getComment(op.description),

View File

@ -1,13 +0,0 @@
import { getServiceClassName } from './getServiceClassName';
describe('getServiceClassName', () => {
it('should produce correct result', () => {
expect(getServiceClassName('')).toEqual('');
expect(getServiceClassName('FooBar')).toEqual('FooBar');
expect(getServiceClassName('Foo Bar')).toEqual('FooBar');
expect(getServiceClassName('foo bar')).toEqual('FooBar');
expect(getServiceClassName('@fooBar')).toEqual('FooBar');
expect(getServiceClassName('$fooBar')).toEqual('FooBar');
expect(getServiceClassName('123fooBar')).toEqual('FooBar');
});
});

View File

@ -0,0 +1,13 @@
import { getServiceName } from './getServiceName';
describe('getServiceName', () => {
it('should produce correct result', () => {
expect(getServiceName('')).toEqual('');
expect(getServiceName('FooBar')).toEqual('FooBar');
expect(getServiceName('Foo Bar')).toEqual('FooBar');
expect(getServiceName('foo bar')).toEqual('FooBar');
expect(getServiceName('@fooBar')).toEqual('FooBar');
expect(getServiceName('$fooBar')).toEqual('FooBar');
expect(getServiceName('123fooBar')).toEqual('FooBar');
});
});

View File

@ -1,10 +1,10 @@
import camelCase from 'camelcase';
/**
* Convert the input value to a correct service classname. This converts
* Convert the input value to a correct service name. This converts
* the input string to PascalCase.
*/
export function getServiceClassName(value: string): string {
export function getServiceName(value: string): string {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')

View File

@ -1,4 +1,5 @@
import type { Service } from '../../../client/interfaces/Service';
import { unique } from '../../../utils/unique';
import type { OpenApi } from '../interfaces/OpenApi';
import { getOperation } from './getOperation';
import { getOperationParameters } from './getOperationParameters';
@ -27,22 +28,23 @@ export function getServices(openApi: OpenApi): Service[] {
case 'patch':
// Each method contains an OpenAPI operation, we parse the operation
const op = path[method]!;
const operation = getOperation(openApi, url, method, op, pathParams);
const tags = op.tags?.filter(unique) || ['Service'];
tags.forEach(tag => {
const operation = getOperation(openApi, url, method, tag, op, pathParams);
// If we have already declared a service, then we should fetch that and
// append the new method to it. Otherwise we should create a new service object.
const service =
services.get(operation.service) ||
({
// If we have already declared a service, then we should fetch that and
// append the new method to it. Otherwise we should create a new service object.
const service: Service = services.get(operation.service) || {
name: operation.service,
operations: [],
imports: [],
} as Service);
};
// Push the operation in the service
service.operations.push(operation);
service.imports.push(...operation.imports);
services.set(operation.service, service);
// Push the operation in the service
service.operations.push(operation);
service.imports.push(...operation.imports);
services.set(operation.service, service);
});
break;
}
}

View File

@ -4,13 +4,12 @@ import type { ApiRequestOptions } from './ApiRequestOptions';
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
type Headers = Record<string, string>;
type CredentialModes = 'include' | 'omit' | 'same-origin';
type Config = {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
CREDENTIALS: CredentialModes;
CREDENTIALS: 'include' | 'omit' | 'same-origin';
TOKEN?: string | Resolver<string>;
USERNAME?: string | Resolver<string>;
PASSWORD?: string | Resolver<string>;

View File

@ -180,13 +180,12 @@ import type { ApiRequestOptions } from './ApiRequestOptions';
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
type Headers = Record<string, string>;
type CredentialModes = 'include' | 'omit' | 'same-origin';
type Config = {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
CREDENTIALS: CredentialModes;
CREDENTIALS: 'include' | 'omit' | 'same-origin';
TOKEN?: string | Resolver<string>;
USERNAME?: string | Resolver<string>;
PASSWORD?: string | Resolver<string>;
@ -573,6 +572,9 @@ export { ComplexService } from './services/ComplexService';
export { DefaultsService } from './services/DefaultsService';
export { DuplicateService } from './services/DuplicateService';
export { HeaderService } from './services/HeaderService';
export { MultipleTags1Service } from './services/MultipleTags1Service';
export { MultipleTags2Service } from './services/MultipleTags2Service';
export { MultipleTags3Service } from './services/MultipleTags3Service';
export { NoContentService } from './services/NoContentService';
export { ParametersService } from './services/ParametersService';
export { ResponseService } from './services/ResponseService';
@ -2206,6 +2208,100 @@ export class HeaderService {
}"
`;
exports[`v2 should generate: ./test/generated/v2/services/MultipleTags1Service.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CancelablePromise } from '../core/CancelablePromise';
import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
export class MultipleTags1Service {
/**
* @returns void
* @throws ApiError
*/
public static dummyA(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/a\`,
});
}
/**
* @returns void
* @throws ApiError
*/
public static dummyB(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/b\`,
});
}
}"
`;
exports[`v2 should generate: ./test/generated/v2/services/MultipleTags2Service.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CancelablePromise } from '../core/CancelablePromise';
import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
export class MultipleTags2Service {
/**
* @returns void
* @throws ApiError
*/
public static dummyA(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/a\`,
});
}
/**
* @returns void
* @throws ApiError
*/
public static dummyB(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/b\`,
});
}
}"
`;
exports[`v2 should generate: ./test/generated/v2/services/MultipleTags3Service.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CancelablePromise } from '../core/CancelablePromise';
import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
export class MultipleTags3Service {
/**
* @returns void
* @throws ApiError
*/
public static dummyB(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/b\`,
});
}
}"
`;
exports[`v2 should generate: ./test/generated/v2/services/NoContentService.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
@ -2694,13 +2790,12 @@ import type { ApiRequestOptions } from './ApiRequestOptions';
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
type Headers = Record<string, string>;
type CredentialModes = 'include' | 'omit' | 'same-origin';
type Config = {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
CREDENTIALS: CredentialModes;
CREDENTIALS: 'include' | 'omit' | 'same-origin';
TOKEN?: string | Resolver<string>;
USERNAME?: string | Resolver<string>;
PASSWORD?: string | Resolver<string>;
@ -3105,6 +3200,9 @@ export { DuplicateService } from './services/DuplicateService';
export { FormDataService } from './services/FormDataService';
export { HeaderService } from './services/HeaderService';
export { MultipartService } from './services/MultipartService';
export { MultipleTags1Service } from './services/MultipleTags1Service';
export { MultipleTags2Service } from './services/MultipleTags2Service';
export { MultipleTags3Service } from './services/MultipleTags3Service';
export { NoContentService } from './services/NoContentService';
export { ParametersService } from './services/ParametersService';
export { RequestBodyService } from './services/RequestBodyService';
@ -5241,6 +5339,100 @@ export class MultipartService {
}"
`;
exports[`v3 should generate: ./test/generated/v3/services/MultipleTags1Service.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CancelablePromise } from '../core/CancelablePromise';
import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
export class MultipleTags1Service {
/**
* @returns void
* @throws ApiError
*/
public static dummyA(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/a\`,
});
}
/**
* @returns void
* @throws ApiError
*/
public static dummyB(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/b\`,
});
}
}"
`;
exports[`v3 should generate: ./test/generated/v3/services/MultipleTags2Service.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CancelablePromise } from '../core/CancelablePromise';
import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
export class MultipleTags2Service {
/**
* @returns void
* @throws ApiError
*/
public static dummyA(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/a\`,
});
}
/**
* @returns void
* @throws ApiError
*/
public static dummyB(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/b\`,
});
}
}"
`;
exports[`v3 should generate: ./test/generated/v3/services/MultipleTags3Service.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CancelablePromise } from '../core/CancelablePromise';
import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
export class MultipleTags3Service {
/**
* @returns void
* @throws ApiError
*/
public static dummyB(): CancelablePromise<void> {
return __request({
method: 'GET',
path: \`/api/v\${OpenAPI.VERSION}/multiple-tags/b\`,
});
}
}"
`;
exports[`v3 should generate: ./test/generated/v3/services/NoContentService.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */

View File

@ -347,24 +347,28 @@
"/api/v{api-version}/duplicate": {
"get": {
"tags": [
"Duplicate",
"Duplicate"
],
"operationId": "DuplicateName"
},
"post": {
"tags": [
"Duplicate",
"Duplicate"
],
"operationId": "DuplicateName"
},
"put": {
"tags": [
"Duplicate",
"Duplicate"
],
"operationId": "DuplicateName"
},
"delete": {
"tags": [
"Duplicate",
"Duplicate"
],
"operationId": "DuplicateName"
@ -383,6 +387,35 @@
}
}
},
"/api/v{api-version}/multiple-tags/a": {
"get": {
"tags": [
"MultipleTags1",
"MultipleTags2"
],
"operationId": "DummyA",
"responses": {
"204": {
"description": "Success"
}
}
}
},
"/api/v{api-version}/multiple-tags/b": {
"get": {
"tags": [
"MultipleTags1",
"MultipleTags2",
"MultipleTags3"
],
"operationId": "DummyB",
"responses": {
"204": {
"description": "Success"
}
}
}
},
"/api/v{api-version}/response": {
"get": {
"tags": [

View File

@ -572,6 +572,35 @@
}
}
},
"/api/v{api-version}/multiple-tags/a": {
"get": {
"tags": [
"MultipleTags1",
"MultipleTags2"
],
"operationId": "DummyA",
"responses": {
"204": {
"description": "Success"
}
}
}
},
"/api/v{api-version}/multiple-tags/b": {
"get": {
"tags": [
"MultipleTags1",
"MultipleTags2",
"MultipleTags3"
],
"operationId": "DummyB",
"responses": {
"204": {
"description": "Success"
}
}
}
},
"/api/v{api-version}/response": {
"get": {
"tags": [