Skip to content

Commit

Permalink
- Fixed quotes in enums
Browse files Browse the repository at this point in the history
- Fixed better default operation name
- Fixed unittest
  • Loading branch information
ferdikoomen committed Feb 25, 2022
1 parent ec2c712 commit b32c85e
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 51 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog
All notable changes to this project will be documented in this file.

## [0.20.0] - 2022-02-25
### Fixed
- Support enums with single quotes in names
- Generating better names when `operationId` is not given (breaking change)

## [0.19.0] - 2022-02-02
### Added
- Support for Angular client with `--name` option
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Ferdi Koomen
Copyright (c) Ferdi Koomen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion src/openApi/v2/parser/escapeName.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { escapeName } from './escapeName';

describe('escapeName', () => {
it('should escape', () => {
expect(escapeName('')).toEqual('');
expect(escapeName('')).toEqual("''");
expect(escapeName('fooBar')).toEqual('fooBar');
expect(escapeName('Foo Bar')).toEqual(`'Foo Bar'`);
expect(escapeName('foo bar')).toEqual(`'foo bar'`);
Expand Down
2 changes: 1 addition & 1 deletion src/openApi/v2/parser/getOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const getOperation = (
pathParams: OperationParameters
): Operation => {
const serviceName = getServiceName(tag);
const operationName = getOperationName(op.operationId || `${method}`);
const operationName = getOperationName(url, method, op.operationId);

// Create a new operation object for this method.
const operation: Operation = {
Expand Down
33 changes: 21 additions & 12 deletions src/openApi/v2/parser/getOperationName.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@ import { getOperationName } from './getOperationName';

describe('getOperationName', () => {
it('should produce correct result', () => {
expect(getOperationName('')).toEqual('');
expect(getOperationName('FooBar')).toEqual('fooBar');
expect(getOperationName('Foo Bar')).toEqual('fooBar');
expect(getOperationName('foo bar')).toEqual('fooBar');
expect(getOperationName('foo-bar')).toEqual('fooBar');
expect(getOperationName('foo_bar')).toEqual('fooBar');
expect(getOperationName('foo.bar')).toEqual('fooBar');
expect(getOperationName('@foo.bar')).toEqual('fooBar');
expect(getOperationName('$foo.bar')).toEqual('fooBar');
expect(getOperationName('_foo.bar')).toEqual('fooBar');
expect(getOperationName('-foo.bar')).toEqual('fooBar');
expect(getOperationName('123.foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers');
expect(getOperationName('/api/v{api-version}/users', 'GET', undefined)).toEqual('getApiUsers');
expect(getOperationName('/api/v{api-version}/users', 'POST', undefined)).toEqual('postApiUsers');
expect(getOperationName('/api/v1/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers');
expect(getOperationName('/api/v1/users', 'GET', undefined)).toEqual('getApiV1Users');
expect(getOperationName('/api/v1/users', 'POST', undefined)).toEqual('postApiV1Users');
expect(getOperationName('/api/v1/users/{id}', 'GET', undefined)).toEqual('getApiV1Users');
expect(getOperationName('/api/v1/users/{id}', 'POST', undefined)).toEqual('postApiV1Users');

expect(getOperationName('/api/v{api-version}/users', 'GET', 'fooBar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'FooBar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'Foo Bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo-bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo_bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '@foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '$foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '_foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '-foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '123.foo.bar')).toEqual('fooBar');
});
});
26 changes: 18 additions & 8 deletions src/openApi/v2/parser/getOperationName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ import camelCase from 'camelcase';

/**
* Convert the input value to a correct operation (method) classname.
* This converts the input string to camelCase, so the method name follows
* the most popular Javascript and Typescript writing style.
* This will use the operation ID - if available - and otherwise fallback
* on a generated name from the URL
*/
export const getOperationName = (value: string): string => {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
return camelCase(clean);
export const getOperationName = (url: string, method: string, operationId?: string): string => {
if (operationId) {
return camelCase(
operationId
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim()
);
}

const urlWithoutPlaceholders = url
.replace(/[^/]*?{api-version}.*?\//g, '')
.replace(/{(.*?)}/g, '')
.replace(/\//g, '-');

return camelCase(`${method}-${urlWithoutPlaceholders}`);
};
2 changes: 1 addition & 1 deletion src/openApi/v3/parser/getEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const getEnum = (values?: (string | number)[]): Enum[] => {
.replace(/^(\d+)/g, '_$1')
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: `'${value}'`,
value: `'${value.replace(/'/g, "\\'")}'`,
type: 'string',
description: null,
};
Expand Down
2 changes: 1 addition & 1 deletion src/openApi/v3/parser/getOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const getOperation = (
pathParams: OperationParameters
): Operation => {
const serviceName = getServiceName(tag);
const operationName = getOperationName(op.operationId || `${method}`);
const operationName = getOperationName(url, method, op.operationId);

// Create a new operation object for this method.
const operation: Operation = {
Expand Down
33 changes: 21 additions & 12 deletions src/openApi/v3/parser/getOperationName.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@ import { getOperationName } from './getOperationName';

describe('getOperationName', () => {
it('should produce correct result', () => {
expect(getOperationName('')).toEqual('');
expect(getOperationName('FooBar')).toEqual('fooBar');
expect(getOperationName('Foo Bar')).toEqual('fooBar');
expect(getOperationName('foo bar')).toEqual('fooBar');
expect(getOperationName('foo-bar')).toEqual('fooBar');
expect(getOperationName('foo_bar')).toEqual('fooBar');
expect(getOperationName('foo.bar')).toEqual('fooBar');
expect(getOperationName('@foo.bar')).toEqual('fooBar');
expect(getOperationName('$foo.bar')).toEqual('fooBar');
expect(getOperationName('_foo.bar')).toEqual('fooBar');
expect(getOperationName('-foo.bar')).toEqual('fooBar');
expect(getOperationName('123.foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers');
expect(getOperationName('/api/v{api-version}/users', 'GET', undefined)).toEqual('getApiUsers');
expect(getOperationName('/api/v{api-version}/users', 'POST', undefined)).toEqual('postApiUsers');
expect(getOperationName('/api/v1/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers');
expect(getOperationName('/api/v1/users', 'GET', undefined)).toEqual('getApiV1Users');
expect(getOperationName('/api/v1/users', 'POST', undefined)).toEqual('postApiV1Users');
expect(getOperationName('/api/v1/users/{id}', 'GET', undefined)).toEqual('getApiV1Users');
expect(getOperationName('/api/v1/users/{id}', 'POST', undefined)).toEqual('postApiV1Users');

expect(getOperationName('/api/v{api-version}/users', 'GET', 'fooBar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'FooBar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'Foo Bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo-bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo_bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '@foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '$foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '_foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '-foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '123.foo.bar')).toEqual('fooBar');
});
});
26 changes: 18 additions & 8 deletions src/openApi/v3/parser/getOperationName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ import camelCase from 'camelcase';

/**
* Convert the input value to a correct operation (method) classname.
* This converts the input string to camelCase, so the method name follows
* the most popular Javascript and Typescript writing style.
* This will use the operation ID - if available - and otherwise fallback
* on a generated name from the URL
*/
export const getOperationName = (value: string): string => {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
return camelCase(clean);
export const getOperationName = (url: string, method: string, operationId?: string): string => {
if (operationId) {
return camelCase(
operationId
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim()
);
}

const urlWithoutPlaceholders = url
.replace(/[^/]*?{api-version}.*?\//g, '')
.replace(/{(.*?)}/g, '')
.replace(/\//g, '-');

return camelCase(`${method}-${urlWithoutPlaceholders}`);
};
160 changes: 158 additions & 2 deletions test/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`v2 should generate: ./test/generated/v2/Demo.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { NgModule} from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AngularHttpRequest } from './core/AngularHttpRequest';
import { BaseHttpRequest } from './core/BaseHttpRequest';
import type { OpenAPIConfig } from './core/OpenAPI';
import { OpenAPI } from './core/OpenAPI';

import { CollectionFormatService } from './services/CollectionFormatService';
import { ComplexService } from './services/ComplexService';
import { DefaultService } from './services/DefaultService';
import { DefaultsService } from './services/DefaultsService';
import { DescriptionsService } from './services/DescriptionsService';
import { DuplicateService } from './services/DuplicateService';
import { ErrorService } from './services/ErrorService';
import { HeaderService } from './services/HeaderService';
import { MultipleTags1Service } from './services/MultipleTags1Service';
import { MultipleTags2Service } from './services/MultipleTags2Service';
import { MultipleTags3Service } from './services/MultipleTags3Service';
import { NoContentService } from './services/NoContentService';
import { ParametersService } from './services/ParametersService';
import { ResponseService } from './services/ResponseService';
import { SimpleService } from './services/SimpleService';
import { TypesService } from './services/TypesService';

@NgModule({
imports: [HttpClientModule],
providers: [
{
provide: OpenAPI,
useValue: {
BASE: OpenAPI?.BASE ?? 'http://localhost:3000/base',
VERSION: OpenAPI?.VERSION ?? '1.0',
WITH_CREDENTIALS: OpenAPI?.WITH_CREDENTIALS ?? false,
CREDENTIALS: OpenAPI?.CREDENTIALS ?? 'include',
TOKEN: OpenAPI?.TOKEN,
USERNAME: OpenAPI?.USERNAME,
PASSWORD: OpenAPI?.PASSWORD,
HEADERS: OpenAPI?.HEADERS,
ENCODE_PATH: OpenAPI?.ENCODE_PATH,
} as OpenAPIConfig,
},
{
provide: BaseHttpRequest,
useClass: AngularHttpRequest,
},
CollectionFormatService,
ComplexService,
DefaultService,
DefaultsService,
DescriptionsService,
DuplicateService,
ErrorService,
HeaderService,
MultipleTags1Service,
MultipleTags2Service,
MultipleTags3Service,
NoContentService,
ParametersService,
ResponseService,
SimpleService,
TypesService,
]
})
export class Demo {}
"
`;

exports[`v2 should generate: ./test/generated/v2/core/ApiError.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
Expand Down Expand Up @@ -943,6 +1015,8 @@ export enum EnumWithStrings {
SUCCESS = 'Success',
WARNING = 'Warning',
ERROR = 'Error',
_SINGLE_QUOTE_ = ''Single Quote'',
_DOUBLE_QUOTES_ = '\\"Double Quotes\\"',
}"
`;

Expand Down Expand Up @@ -2949,6 +3023,86 @@ export class TypesService {
}"
`;

exports[`v3 should generate: ./test/generated/v3/Demo.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { NgModule} from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AngularHttpRequest } from './core/AngularHttpRequest';
import { BaseHttpRequest } from './core/BaseHttpRequest';
import type { OpenAPIConfig } from './core/OpenAPI';
import { OpenAPI } from './core/OpenAPI';

import { CollectionFormatService } from './services/CollectionFormatService';
import { ComplexService } from './services/ComplexService';
import { DefaultService } from './services/DefaultService';
import { DefaultsService } from './services/DefaultsService';
import { DescriptionsService } from './services/DescriptionsService';
import { DuplicateService } from './services/DuplicateService';
import { ErrorService } from './services/ErrorService';
import { FormDataService } from './services/FormDataService';
import { HeaderService } from './services/HeaderService';
import { MultipartService } from './services/MultipartService';
import { MultipleTags1Service } from './services/MultipleTags1Service';
import { MultipleTags2Service } from './services/MultipleTags2Service';
import { MultipleTags3Service } from './services/MultipleTags3Service';
import { NoContentService } from './services/NoContentService';
import { ParametersService } from './services/ParametersService';
import { RequestBodyService } from './services/RequestBodyService';
import { ResponseService } from './services/ResponseService';
import { SimpleService } from './services/SimpleService';
import { TypesService } from './services/TypesService';
import { UploadService } from './services/UploadService';

@NgModule({
imports: [HttpClientModule],
providers: [
{
provide: OpenAPI,
useValue: {
BASE: OpenAPI?.BASE ?? 'http://localhost:3000/base',
VERSION: OpenAPI?.VERSION ?? '1.0',
WITH_CREDENTIALS: OpenAPI?.WITH_CREDENTIALS ?? false,
CREDENTIALS: OpenAPI?.CREDENTIALS ?? 'include',
TOKEN: OpenAPI?.TOKEN,
USERNAME: OpenAPI?.USERNAME,
PASSWORD: OpenAPI?.PASSWORD,
HEADERS: OpenAPI?.HEADERS,
ENCODE_PATH: OpenAPI?.ENCODE_PATH,
} as OpenAPIConfig,
},
{
provide: BaseHttpRequest,
useClass: AngularHttpRequest,
},
CollectionFormatService,
ComplexService,
DefaultService,
DefaultsService,
DescriptionsService,
DuplicateService,
ErrorService,
FormDataService,
HeaderService,
MultipartService,
MultipleTags1Service,
MultipleTags2Service,
MultipleTags3Service,
NoContentService,
ParametersService,
RequestBodyService,
ResponseService,
SimpleService,
TypesService,
UploadService,
]
})
export class Demo {}
"
`;

exports[`v3 should generate: ./test/generated/v3/core/ApiError.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
Expand Down Expand Up @@ -4089,6 +4243,8 @@ export enum EnumWithStrings {
SUCCESS = 'Success',
WARNING = 'Warning',
ERROR = 'Error',
_SINGLE_QUOTE_ = '\\\\'Single Quote\\\\'',
_DOUBLE_QUOTES_ = '\\"Double Quotes\\"',
}"
`;

Expand Down Expand Up @@ -6125,7 +6281,7 @@ export class FormDataService {
* @param formData A reusable request body
* @throws ApiError
*/
public static post(
public static postApiFormData(
parameter?: string,
formData?: ModelWithString,
): CancelablePromise<void> {
Expand Down Expand Up @@ -6502,7 +6658,7 @@ export class RequestBodyService {
* @param requestBody A reusable request body
* @throws ApiError
*/
public static post(
public static postApiRequestBody(
parameter?: string,
requestBody?: ModelWithString,
): CancelablePromise<void> {
Expand Down
Loading

0 comments on commit b32c85e

Please sign in to comment.