Skip to content

Commit cf78f10

Browse files
Fix dangerous destructuration in typescript-nestjs services (#20157)
* refactor: remove requestParameters destructuration * feat: add reserved param names sample * feat: quote params * feat: improve with reservedWords * feat: use vendorExtensions instead of extending CodegenParameter
1 parent 26609e9 commit cf78f10

File tree

20 files changed

+659
-5
lines changed

20 files changed

+659
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
generatorName: typescript-nestjs
2+
outputDir: samples/client/petstore/typescript-nestjs/builds/reservedParamNames
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript-nestjs/reserved-param-names.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/typescript-nestjs
5+
additionalProperties:
6+
"useSingleRequestParameter" : true

docs/generators/typescript-nestjs.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,11 @@ These options may be applied as additional-properties (cli) or configOptions (pl
116116
<li>float</li>
117117
<li>for</li>
118118
<li>formParams</li>
119+
<li>from</li>
119120
<li>function</li>
120121
<li>goto</li>
121122
<li>headerParams</li>
123+
<li>headers</li>
122124
<li>if</li>
123125
<li>implements</li>
124126
<li>import</li>

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsClientCodegen.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public TypeScriptNestjsClientCodegen() {
8888
apiPackage = "api";
8989
modelPackage = "model";
9090

91+
reservedWords.addAll(Arrays.asList("from", "headers"));
92+
9193
this.cliOptions.add(new CliOption(NPM_REPOSITORY,
9294
"Use this property to set an url your private npmRepo in the package.json"));
9395
this.cliOptions.add(CliOption.newBoolean(WITH_INTERFACES,
@@ -327,6 +329,10 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L
327329

328330
// Overwrite path to TypeScript template string, after applying everything we just did.
329331
op.path = pathBuffer.toString();
332+
333+
for (CodegenParameter param : op.allParams) {
334+
param.vendorExtensions.putIfAbsent("x-param-has-sanitized-name", !param.baseName.equals(param.paramName));
335+
}
330336
}
331337

332338
operations.put("hasSomeFormParams", hasSomeFormParams);

modules/openapi-generator/src/main/resources/typescript-nestjs/api.service.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface {{classname}}{{operationIdCamelCase}}Request {
3535
* @type {{=<% %>=}}{<%&dataType%>}<%={{ }}=%>
3636
* @memberof {{classname}}{{operationIdCamelCase}}
3737
*/
38-
readonly {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}
38+
readonly {{#vendorExtensions.x-param-has-sanitized-name}}'{{{baseName}}}'{{/vendorExtensions.x-param-has-sanitized-name}}{{^vendorExtensions.x-param-has-sanitized-name}}{{{paramName}}}{{/vendorExtensions.x-param-has-sanitized-name}}{{^required}}?{{/required}}: {{{dataType}}}
3939
{{^-last}}
4040

4141
{{/-last}}
@@ -106,7 +106,7 @@ export class {{classname}} {
106106
{{#useSingleRequestParameter}}
107107
const {
108108
{{#allParams}}
109-
{{paramName}},
109+
{{#vendorExtensions.x-param-has-sanitized-name}}'{{{baseName}}}': {{/vendorExtensions.x-param-has-sanitized-name}}{{paramName}},
110110
{{/allParams}}
111111
} = requestParameters;
112112

modules/openapi-generator/src/main/resources/typescript-nestjs/modelGeneric.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ export interface {{classname}}{{#allParents}}{{#-first}} extends {{/-first}}{{{.
55
* {{{.}}}
66
*/
77
{{/description}}
8-
{{#isReadOnly}}readonly {{/isReadOnly}}{{{name}}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
8+
{{#isReadOnly}}readonly {{/isReadOnly}}{{#hasSanitizedName}}'{{{baseName}}}'{{/hasSanitizedName}}{{^hasSanitizedName}}{{{name}}}{{/hasSanitizedName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
99
{{/vars}}
1010
}{{>modelGenericEnums}}

modules/openapi-generator/src/main/resources/typescript-nestjs/modelTaggedUnion.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ export interface {{classname}} { {{>modelGenericAdditionalProperties}}
1010
* {{{.}}}
1111
*/
1212
{{/description}}
13-
{{name}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}};
13+
{{#hasSanitizedName}}'{{{baseName}}}'{{/hasSanitizedName}}{{^hasSanitizedName}}{{{name}}}{{/hasSanitizedName}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}};
1414
{{/allVars}}
1515
}
1616
{{>modelGenericEnums}}
1717
{{/parent}}
1818
{{^parent}}
1919
{{>modelGeneric}}
2020
{{/parent}}
21-
{{/discriminator}}
21+
{{/discriminator}}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
openapi: 3.0.0
2+
info:
3+
description: Test reserved param names
4+
version: 1.0.0
5+
title: Reserved param names
6+
paths:
7+
/test:
8+
post:
9+
security:
10+
- bearerAuth: []
11+
summary: Test reserved param names
12+
description: ''
13+
operationId: testReservedParamNames
14+
parameters:
15+
- name: notReserved
16+
in: query
17+
description: Should not be treated as a reserved param name
18+
required: true
19+
schema:
20+
type: string
21+
- name: from
22+
in: query
23+
description: Might conflict with rxjs import
24+
required: true
25+
schema:
26+
type: string
27+
- name: headers
28+
in: header
29+
description: Might conflict with headers const
30+
required: true
31+
schema:
32+
type: string
33+
responses:
34+
'200':
35+
description: successful operation
36+
'405':
37+
description: Invalid input
38+
components:
39+
securitySchemes:
40+
bearerAuth:
41+
type: http
42+
scheme: bearer
43+
bearerFormat: JWT
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
wwwroot/*.js
2+
node_modules
3+
typings
4+
dist
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.gitignore
2+
README.md
3+
api.module.ts
4+
api/api.ts
5+
api/default.service.ts
6+
configuration.ts
7+
git_push.sh
8+
index.ts
9+
model/models.ts
10+
variables.ts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.11.0-SNAPSHOT
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
## @
2+
3+
### Building
4+
5+
To install the required dependencies and to build the typescript sources run:
6+
```
7+
npm install
8+
npm run build
9+
```
10+
11+
#### General usage
12+
13+
In your Nestjs project:
14+
15+
16+
```
17+
// without configuring providers
18+
import { ApiModule } from '';
19+
import { HttpModule } from '@nestjs/axios';
20+
21+
@Module({
22+
imports: [
23+
ApiModule,
24+
HttpModule
25+
],
26+
providers: []
27+
})
28+
export class AppModule {}
29+
```
30+
31+
```
32+
// configuring providers
33+
import { ApiModule, Configuration, ConfigurationParameters } from '';
34+
35+
export function apiConfigFactory (): Configuration => {
36+
const params: ConfigurationParameters = {
37+
// set configuration parameters here.
38+
}
39+
return new Configuration(params);
40+
}
41+
42+
@Module({
43+
imports: [ ApiModule.forRoot(apiConfigFactory) ],
44+
declarations: [ AppComponent ],
45+
providers: [],
46+
bootstrap: [ AppComponent ]
47+
})
48+
export class AppModule {}
49+
```
50+
51+
```
52+
import { DefaultApi } from '';
53+
54+
export class AppComponent {
55+
constructor(private apiGateway: DefaultApi) { }
56+
}
57+
```
58+
59+
Note: The ApiModule a dynamic module and instantiated once app wide.
60+
This is to ensure that all services are treated as singletons.
61+
62+
#### Using multiple swagger files / APIs / ApiModules
63+
In order to use multiple `ApiModules` generated from different swagger files,
64+
you can create an alias name when importing the modules
65+
in order to avoid naming conflicts:
66+
```
67+
import { ApiModule } from 'my-api-path';
68+
import { ApiModule as OtherApiModule } from 'my-other-api-path';
69+
import { HttpModule } from '@nestjs/axios';
70+
71+
@Module({
72+
imports: [
73+
ApiModule,
74+
OtherApiModule,
75+
HttpModule
76+
]
77+
})
78+
export class AppModule {
79+
80+
}
81+
```
82+
83+
84+
### Set service base path
85+
If different than the generated base path, during app bootstrap, you can provide the base path to your service.
86+
87+
```
88+
import { BASE_PATH } from '';
89+
90+
bootstrap(AppComponent, [
91+
{ provide: BASE_PATH, useValue: 'https://your-web-service.com' },
92+
]);
93+
```
94+
or
95+
96+
```
97+
import { BASE_PATH } from '';
98+
99+
@Module({
100+
imports: [],
101+
declarations: [ AppComponent ],
102+
providers: [ provide: BASE_PATH, useValue: 'https://your-web-service.com' ],
103+
bootstrap: [ AppComponent ]
104+
})
105+
export class AppModule {}
106+
```
107+
108+
### Configuring the module with `forRootAsync`
109+
110+
You can also use the Nestjs Config Module/Service to configure your app with `forRootAsync`.
111+
112+
```
113+
@Module({
114+
imports: [
115+
ApiModule.forRootAsync({
116+
imports: [ConfigModule],
117+
inject: [ConfigService],
118+
useFactory: (config: ConfigService): Configuration => {
119+
const params: ConfigurationParameters = {
120+
// set configuration parameters here.
121+
basePath: config.get('API_URL'),
122+
};
123+
return new Configuration(params);
124+
},
125+
})
126+
],
127+
declarations: [ AppComponent ],
128+
providers: [],
129+
bootstrap: [ AppComponent ]
130+
})
131+
export class AppModule {}
132+
```
133+
134+
#### Using @nestjs/cli
135+
First extend your `src/environments/*.ts` files by adding the corresponding base path:
136+
137+
```
138+
export const environment = {
139+
production: false,
140+
API_BASE_PATH: 'http://127.0.0.1:8080'
141+
};
142+
```
143+
144+
In the src/app/app.module.ts:
145+
```
146+
import { BASE_PATH } from '';
147+
import { environment } from '../environments/environment';
148+
149+
@Module({
150+
declarations: [
151+
AppComponent
152+
],
153+
imports: [ ],
154+
providers: [
155+
{
156+
provide: 'BASE_PATH',
157+
useValue: environment.API_BASE_PATH
158+
}
159+
]
160+
})
161+
export class AppModule { }
162+
```

0 commit comments

Comments
 (0)