Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support non-ascii characters in service name, operation name and parameter name. #15

Merged
merged 1 commit into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/openApi/v2/parser/getOperationName.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import camelCase from 'camelcase';

import sanitizeOperationName from '../../../utils/sanitizeOperationName';

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

const urlWithoutPlaceholders = url
Expand Down
6 changes: 2 additions & 4 deletions src/openApi/v2/parser/getOperationParameterName.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import camelCase from 'camelcase';

import { reservedWords } from '../../../utils/reservedWords';
import sanitizeOperationParameterName from '../../../utils/sanitizeOperationParameterName';

/**
* Replaces any invalid characters from a parameter name.
* For example: 'filter.someProperty' becomes 'filterSomeProperty'.
*/
export const getOperationParameterName = (value: string): string => {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
const clean = sanitizeOperationParameterName(value).trim();
return camelCase(clean).replace(reservedWords, '_$1');
};
7 changes: 3 additions & 4 deletions src/openApi/v2/parser/getServiceName.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import camelCase from 'camelcase';

import sanitizeServiceName from '../../../utils/sanitizeServiceName';

/**
* Convert the input value to a correct service name. This converts
* the input string to PascalCase.
*/
export const getServiceName = (value: string): string => {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
const clean = sanitizeServiceName(value).trim();
return camelCase(clean, { pascalCase: true });
};
9 changes: 3 additions & 6 deletions src/openApi/v3/parser/getOperationName.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import camelCase from 'camelcase';

import sanitizeOperationName from '../../../utils/sanitizeOperationName';

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

const urlWithoutPlaceholders = url
Expand Down
7 changes: 2 additions & 5 deletions src/openApi/v3/parser/getOperationParameterName.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import camelCase from 'camelcase';

import { reservedWords } from '../../../utils/reservedWords';
import sanitizeOperationParameterName from '../../../utils/sanitizeOperationParameterName';

/**
* Replaces any invalid characters from a parameter name.
* For example: 'filter.someProperty' becomes 'filterSomeProperty'.
*/
export const getOperationParameterName = (value: string): string => {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace('[]', 'Array')
.replace(/[^\w\-]+/g, '-')
.trim();
const clean = sanitizeOperationParameterName(value).trim();
return camelCase(clean).replace(reservedWords, '_$1');
};
1 change: 1 addition & 0 deletions src/openApi/v3/parser/getServiceName.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ describe('getServiceName', () => {
expect(getServiceName('@fooBar')).toEqual('FooBar');
expect(getServiceName('$fooBar')).toEqual('FooBar');
expect(getServiceName('123fooBar')).toEqual('FooBar');
expect(getServiceName('non-ascii-æøåÆØÅöôêÊ字符串')).toEqual('NonAsciiÆøåÆøÅöôêÊ字符串');
});
});
7 changes: 3 additions & 4 deletions src/openApi/v3/parser/getServiceName.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import camelCase from 'camelcase';

import sanitizeServiceName from '../../../utils/sanitizeServiceName';

/**
* Convert the input value to a correct service name. This converts
* the input string to PascalCase.
*/
export const getServiceName = (value: string): string => {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
const clean = sanitizeServiceName(value).trim();
return camelCase(clean, { pascalCase: true });
};
7 changes: 7 additions & 0 deletions src/utils/sanitizeOperationName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import sanitizeServiceName from './sanitizeServiceName';

/**
* sanitizeOperationName does the same as sanitizeServiceName.
*/
const sanitizeOperationName = sanitizeServiceName;
export default sanitizeOperationName;
7 changes: 7 additions & 0 deletions src/utils/sanitizeOperationParameterName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import sanitizeOperationName from './sanitizeOperationName';

const sanitizeOperationParameterName = (name: string): string => {
const withoutBrackets = name.replace('[]', 'Array');
return sanitizeOperationName(withoutBrackets);
};
export default sanitizeOperationParameterName;
18 changes: 18 additions & 0 deletions src/utils/sanitizeServiceName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Sanitizes service names, so they are valid typescript identifiers of a certain form.
*
* 1: Remove any leading characters that are illegal as starting character of a typescript identifier.
* 2: Replace illegal characters in remaining part of type name with underscore (-).
*
* Step 1 should perhaps instead also replace illegal characters with underscore, or prefix with it, like sanitizeEnumName
* does. The way this is now one could perhaps end up removing all characters, if all are illegal start characters. It
* would be sort of a breaking change to do so, though, previously generated code might change then.
*
* Javascript identifier regexp pattern retrieved from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers
*
* The output of this is expected to be converted to PascalCase
*/
const sanitizeServiceName = (name: string) =>
name.replace(/^[^\p{ID_Start}]+/u, '').replace(/[^$\u200c\u200d\p{ID_Continue}]/gu, '-');

export default sanitizeServiceName;
62 changes: 62 additions & 0 deletions test/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ export { MultipleTags1Service } from './services/MultipleTags1Service';
export { MultipleTags2Service } from './services/MultipleTags2Service';
export { MultipleTags3Service } from './services/MultipleTags3Service';
export { NoContentService } from './services/NoContentService';
export { NonAsciiÆøåÆøÅöôêÊService } from './services/NonAsciiÆøåÆøÅöôêÊService';
export { ParametersService } from './services/ParametersService';
export { ResponseService } from './services/ResponseService';
export { SimpleService } from './services/SimpleService';
Expand Down Expand Up @@ -2841,6 +2842,36 @@ export class NoContentService {
"
`;

exports[`v2 should generate: test/generated/v2/services/NonAsciiÆøåÆøÅöôêÊService.ts 1`] = `
"/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { NonAsciiStringæøåÆØÅöôêÊ字符串 } from '../models/NonAsciiStringæøåÆØÅöôêÊ字符串';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class NonAsciiÆøåÆøÅöôêÊService {
/**
* @param nonAsciiParamæøåÆøÅöôêÊ Dummy input param
* @returns NonAsciiStringæøåÆØÅöôêÊ字符串 Successful response
* @throws ApiError
*/
public static nonAsciiæøåÆøÅöôêÊ字符串(
nonAsciiParamæøåÆøÅöôêÊ: number,
): CancelablePromise<NonAsciiStringæøåÆØÅöôêÊ字符串> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串',
query: {
'nonAsciiParamæøåÆØÅöôêÊ': nonAsciiParamæøåÆøÅöôêÊ,
},
});
}
}
"
`;

exports[`v2 should generate: test/generated/v2/services/ParametersService.ts 1`] = `
"/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
Expand Down Expand Up @@ -3852,6 +3883,7 @@ export { MultipleTags1Service } from './services/MultipleTags1Service';
export { MultipleTags2Service } from './services/MultipleTags2Service';
export { MultipleTags3Service } from './services/MultipleTags3Service';
export { NoContentService } from './services/NoContentService';
export { NonAsciiÆøåÆøÅöôêÊService } from './services/NonAsciiÆøåÆøÅöôêÊService';
export { ParametersService } from './services/ParametersService';
export { RequestBodyService } from './services/RequestBodyService';
export { ResponseService } from './services/ResponseService';
Expand Down Expand Up @@ -7045,6 +7077,36 @@ export class NoContentService {
"
`;

exports[`v3 should generate: test/generated/v3/services/NonAsciiÆøåÆøÅöôêÊService.ts 1`] = `
"/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { NonAsciiStringæøåÆØÅöôêÊ字符串 } from '../models/NonAsciiStringæøåÆØÅöôêÊ字符串';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class NonAsciiÆøåÆøÅöôêÊService {
/**
* @param nonAsciiParamæøåÆøÅöôêÊ Dummy input param
* @returns NonAsciiStringæøåÆØÅöôêÊ字符串 Successful response
* @throws ApiError
*/
public static nonAsciiæøåÆøÅöôêÊ字符串(
nonAsciiParamæøåÆøÅöôêÊ: number,
): CancelablePromise<Array<NonAsciiStringæøåÆØÅöôêÊ字符串>> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串',
query: {
'nonAsciiParamæøåÆØÅöôêÊ': nonAsciiParamæøåÆøÅöôêÊ,
},
});
}
}
"
`;

exports[`v3 should generate: test/generated/v3/services/ParametersService.ts 1`] = `
"/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
Expand Down
27 changes: 27 additions & 0 deletions test/spec/v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,33 @@
}
}
}
},
"/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串": {
"post": {
"tags": [
"Non-Ascii-æøåÆØÅöôêÊ"
],
"operationId": "nonAsciiæøåÆØÅöôêÊ字符串",
"parameters": [
{
"description": "Dummy input param",
"name": "nonAsciiParamæøåÆØÅöôêÊ",
"in": "query",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"schema": {
"$ref": "#/definitions/NonAsciiStringæøåÆØÅöôêÊ字符串"
}
}
}
}
}
},
"definitions": {
Expand Down
34 changes: 34 additions & 0 deletions test/spec/v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,40 @@
}
}
}
},
"/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串": {
"post": {
"tags": [
"Non-Ascii-æøåÆØÅöôêÊ"
],
"operationId": "nonAsciiæøåÆØÅöôêÊ字符串",
"parameters": [
{
"description": "Dummy input param",
"name": "nonAsciiParamæøåÆØÅöôêÊ",
"in": "query",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NonAsciiStringæøåÆØÅöôêÊ字符串"
}
}
}
}
}
}
}
}
},
"components": {
Expand Down
Loading