Skip to content

Commit 4320cb5

Browse files
authored
Merge pull request #55 from nicolas-chaulet/fix/duplicate-path-params
fix(parser): skip global params if they are duplicates of path params
2 parents 7c1c691 + 3001081 commit 4320cb5

17 files changed

+344
-205
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ This is an opinionated fork of the [openapi-typescript-codegen](https://github.c
1515
- can be used with CLI, Node.js, or npx
1616
- abortable requests through cancellable promise pattern
1717

18+
## Quickstart
19+
20+
The fastest way to use this package is via npx
21+
22+
```sh
23+
npx @nicolas-chaulet/openapi-typescript-codegen -i path/to/openapi.json -o src/client
24+
```
25+
26+
Congratulations on generating your first client! 🎉
27+
1828
## Install
1929

2030
```
@@ -64,7 +74,7 @@ $ openapi --help
6474
If you use Prettier, your client will be automatically formatted according to your configuration. To disable automatic formatting, run
6575

6676
```sh
67-
openapi --input path/to/openapi.json --output src/client --no-autoformat
77+
openapi -i path/to/openapi.json -o src/client --no-autoformat
6878
```
6979

7080
To prevent your client from being processed by linters and similar tools, you should add your output path to the tool's ignore file (e.g. `.eslintignore`).

src/openApi/v3/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { OpenApi } from './interfaces/OpenApi';
44
import { getModels } from './parser/getModels';
55
import { getServer } from './parser/getServer';
66
import { getServices } from './parser/getServices';
7-
import { getServiceVersion } from './parser/getServiceVersion';
7+
import { getServiceVersion } from './parser/service';
88

99
/**
1010
* Parse the OpenAPI specification to a Client model that contains

src/openApi/v3/interfaces/OpenApiPath.d.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ import type { OpenApiServer } from './OpenApiServer';
66
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathItemObject
77
*/
88
export interface OpenApiPath {
9-
summary?: string;
9+
delete?: OpenApiOperation;
1010
description?: string;
1111
get?: OpenApiOperation;
12-
put?: OpenApiOperation;
13-
post?: OpenApiOperation;
14-
delete?: OpenApiOperation;
15-
options?: OpenApiOperation;
1612
head?: OpenApiOperation;
13+
options?: OpenApiOperation;
14+
parameters?: OpenApiParameter[];
1715
patch?: OpenApiOperation;
18-
trace?: OpenApiOperation;
16+
post?: OpenApiOperation;
17+
put?: OpenApiOperation;
1918
servers?: OpenApiServer[];
20-
parameters?: OpenApiParameter[];
19+
summary?: string;
20+
trace?: OpenApiOperation;
2121
}

src/openApi/v3/parser/getServices.spec.ts renamed to src/openApi/v3/parser/__tests__/getServices.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { Options } from '../../../client/interfaces/Options';
2-
import { getServices } from './getServices';
1+
import type { Options } from '../../../../client/interfaces/Options';
2+
import { getServices } from '../getServices';
33

44
describe('getServices', () => {
55
it('should create a unnamed service if tags are empty', () => {

src/openApi/v3/parser/getServiceName.spec.ts renamed to src/openApi/v3/parser/__tests__/service.spec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getServiceName } from './getServiceName';
1+
import { getServiceName, getServiceVersion } from '../service';
22

33
describe('getServiceName', () => {
44
it('should produce correct result', () => {
@@ -12,3 +12,11 @@ describe('getServiceName', () => {
1212
expect(getServiceName('non-ascii-æøåÆØÅöôêÊ字符串')).toEqual('NonAsciiÆøåÆøÅöôêÊ字符串');
1313
});
1414
});
15+
16+
describe('getServiceVersion', () => {
17+
it('should produce correct result', () => {
18+
expect(getServiceVersion('1.0')).toEqual('1.0');
19+
expect(getServiceVersion('v1.0')).toEqual('1.0');
20+
expect(getServiceVersion('V1.0')).toEqual('1.0');
21+
});
22+
});

src/openApi/v3/parser/getOperation.ts

Lines changed: 0 additions & 93 deletions
This file was deleted.

src/openApi/v3/parser/getOperationParameters.ts

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
44
import { getOperationParameter } from './getOperationParameter';
55
import { getRef } from './getRef';
66

7+
const allowedIn = ['cookie', 'formData', 'header', 'path', 'query'] as const;
8+
79
export const getOperationParameters = (openApi: OpenApi, parameters: OpenApiParameter[]): OperationParameters => {
810
const operationParameters: OperationParameters = {
911
$refs: [],
@@ -14,54 +16,43 @@ export const getOperationParameters = (openApi: OpenApi, parameters: OpenApiPara
1416
parametersForm: [],
1517
parametersCookie: [],
1618
parametersHeader: [],
17-
parametersBody: null, // Not used in V3 -> @see requestBody
19+
parametersBody: null, // Not used in v3 -> @see requestBody
1820
};
1921

20-
// Iterate over the parameters
2122
parameters.forEach(parameterOrReference => {
2223
const parameterDef = getRef<OpenApiParameter>(openApi, parameterOrReference);
2324
const parameter = getOperationParameter(openApi, parameterDef);
2425

25-
// We ignore the "api-version" param, since we do not want to add this
26-
// as the first / default parameter for each of the service calls.
27-
if (parameter.prop !== 'api-version') {
28-
switch (parameterDef.in) {
29-
case 'path':
30-
operationParameters.parametersPath.push(parameter);
31-
operationParameters.parameters.push(parameter);
32-
operationParameters.$refs = [...operationParameters.$refs, ...parameter.$refs];
33-
operationParameters.imports = [...operationParameters.imports, ...parameter.imports];
34-
break;
35-
36-
case 'query':
37-
operationParameters.parametersQuery.push(parameter);
38-
operationParameters.parameters.push(parameter);
39-
operationParameters.$refs = [...operationParameters.$refs, ...parameter.$refs];
40-
operationParameters.imports = [...operationParameters.imports, ...parameter.imports];
41-
break;
42-
43-
case 'formData':
44-
operationParameters.parametersForm.push(parameter);
45-
operationParameters.parameters.push(parameter);
46-
operationParameters.$refs = [...operationParameters.$refs, ...parameter.$refs];
47-
operationParameters.imports = [...operationParameters.imports, ...parameter.imports];
48-
break;
26+
const defIn = parameterDef.in as (typeof allowedIn)[number];
4927

50-
case 'cookie':
51-
operationParameters.parametersCookie.push(parameter);
52-
operationParameters.parameters.push(parameter);
53-
operationParameters.$refs = [...operationParameters.$refs, ...parameter.$refs];
54-
operationParameters.imports = [...operationParameters.imports, ...parameter.imports];
55-
break;
28+
// ignore the "api-version" param since we do not want to add it
29+
// as the first/default parameter for each of the service calls
30+
if (parameter.prop === 'api-version' || !allowedIn.includes(defIn)) {
31+
return;
32+
}
5633

57-
case 'header':
58-
operationParameters.parametersHeader.push(parameter);
59-
operationParameters.parameters.push(parameter);
60-
operationParameters.$refs = [...operationParameters.$refs, ...parameter.$refs];
61-
operationParameters.imports = [...operationParameters.imports, ...parameter.imports];
62-
break;
63-
}
34+
switch (defIn) {
35+
case 'cookie':
36+
operationParameters.parametersCookie = [...operationParameters.parametersCookie, parameter];
37+
break;
38+
case 'formData':
39+
operationParameters.parametersForm = [...operationParameters.parametersForm, parameter];
40+
break;
41+
case 'header':
42+
operationParameters.parametersHeader = [...operationParameters.parametersHeader, parameter];
43+
break;
44+
case 'path':
45+
operationParameters.parametersPath = [...operationParameters.parametersPath, parameter];
46+
break;
47+
case 'query':
48+
operationParameters.parametersQuery = [...operationParameters.parametersQuery, parameter];
49+
break;
6450
}
51+
52+
operationParameters.$refs = [...operationParameters.$refs, ...parameter.$refs];
53+
operationParameters.imports = [...operationParameters.imports, ...parameter.imports];
54+
operationParameters.parameters = [...operationParameters.parameters, parameter];
6555
});
56+
6657
return operationParameters;
6758
};

src/openApi/v3/parser/getServiceVersion.spec.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/openApi/v3/parser/getServiceVersion.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/openApi/v3/parser/getServices.ts

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import type { Options } from '../../../client/interfaces/Options';
33
import type { Service } from '../../../client/interfaces/Service';
44
import { unique } from '../../../utils/unique';
55
import type { OpenApi } from '../interfaces/OpenApi';
6-
import { getOperation } from './getOperation';
76
import { getOperationParameters } from './getOperationParameters';
7+
import { getOperation } from './operation';
8+
import { allowedServiceMethods } from './service';
89

910
const getNewService = (operation: Operation): Service => ({
1011
$refs: [],
@@ -13,44 +14,35 @@ const getNewService = (operation: Operation): Service => ({
1314
operations: [],
1415
});
1516

16-
/**
17-
* Get the OpenAPI services
18-
*/
1917
export const getServices = (openApi: OpenApi, options: Options): Service[] => {
2018
const services = new Map<string, Service>();
19+
2120
for (const url in openApi.paths) {
22-
if (openApi.paths.hasOwnProperty(url)) {
23-
// Grab path and parse any global path parameters
24-
const path = openApi.paths[url];
25-
const pathParams = getOperationParameters(openApi, path.parameters || []);
21+
const path = openApi.paths[url];
22+
const pathParams = getOperationParameters(openApi, path.parameters ?? []);
2623

27-
// Parse all the methods for this path
28-
for (const method in path) {
29-
if (path.hasOwnProperty(method)) {
30-
switch (method) {
31-
case 'delete':
32-
case 'get':
33-
case 'head':
34-
case 'options':
35-
case 'patch':
36-
case 'post':
37-
case 'put':
38-
// Each method contains an OpenAPI operation, we parse the operation
39-
const op = path[method]!;
40-
const tags = op.tags?.length ? op.tags.filter(unique) : ['Default'];
41-
tags.forEach(tag => {
42-
const operation = getOperation(openApi, url, method, tag, op, pathParams, options);
43-
const service = services.get(operation.service) || getNewService(operation);
44-
service.$refs = [...service.$refs, ...operation.$refs];
45-
service.imports = [...service.imports, ...operation.imports];
46-
service.operations = [...service.operations, operation];
47-
services.set(operation.service, service);
48-
});
49-
break;
50-
}
51-
}
24+
for (const key in path) {
25+
const method = key as (typeof allowedServiceMethods)[number];
26+
if (allowedServiceMethods.includes(method)) {
27+
const op = path[method]!;
28+
const tags = op.tags?.length ? op.tags.filter(unique) : ['Default'];
29+
tags.forEach(tag => {
30+
const operation = getOperation(openApi, options, {
31+
method,
32+
op,
33+
pathParams,
34+
tag,
35+
url,
36+
});
37+
const service = services.get(operation.service) || getNewService(operation);
38+
service.$refs = [...service.$refs, ...operation.$refs];
39+
service.imports = [...service.imports, ...operation.imports];
40+
service.operations = [...service.operations, operation];
41+
services.set(operation.service, service);
42+
});
5243
}
5344
}
5445
}
46+
5547
return Array.from(services.values());
5648
};

0 commit comments

Comments
 (0)