[typescript-nestjs] Add Nestjs Generator#5518
[typescript-nestjs] Add Nestjs Generator#5518wing328 merged 15 commits intoOpenAPITools:typescript-nextjsfrom
Conversation
macjohnny
left a comment
There was a problem hiding this comment.
Thanks a lot for your contribution!
please generate the nestjs samples and remove the unrelated groovy and kotlin sample changes.
|
|
||
| @Global | ||
| @Module({ | ||
| imports: [ HttpModule ], |
There was a problem hiding this comment.
is this import necessary? shouldn't the consumer import this on a global level?
There was a problem hiding this comment.
Here the HttpModule is used as a static module so yes it can be, but I actually should probably change it to a dynamic module with HttpModule.register({})
| @@ -0,0 +1,4 @@ | |||
| wwwroot/*.js | |||
There was a problem hiding this comment.
I guess this file should be named .gitignore instead of gitignore
There was a problem hiding this comment.
Not sure why but the standard is to keep the name gitignore and then rename is the codegen supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore")); this is done is all typescript generators. Maybe it is something with git idk
| }; | ||
| {{/stringEnums}} | ||
| {{^stringEnums}} | ||
| export type {{enumName}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}; |
| "name": "{{{npmName}}}", | ||
| "version": "{{{npmVersion}}}", | ||
| "description": "swagger client for {{{npmName}}}", | ||
| "author": "Swagger Codegen Contributors", |
There was a problem hiding this comment.
| "author": "Swagger Codegen Contributors", | |
| "author": "OpenAPI Generator Contributors", |
| { | ||
| "name": "{{{npmName}}}", | ||
| "version": "{{{npmVersion}}}", | ||
| "description": "swagger client for {{{npmName}}}", |
There was a problem hiding this comment.
| "description": "swagger client for {{{npmName}}}", | |
| "description": "REST client for {{{npmName}}}", |
| @@ -0,0 +1,49 @@ | |||
| /* | |||
| * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) | |||
| * Copyright 2018 SmartBear Software | |||
There was a problem hiding this comment.
| * Copyright 2018 SmartBear Software |
| @@ -0,0 +1,49 @@ | |||
| /* | |||
| * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) | |||
| * Copyright 2018 SmartBear Software | |||
There was a problem hiding this comment.
| * Copyright 2018 SmartBear Software |
| @@ -0,0 +1,603 @@ | |||
| /* | |||
| * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) | |||
| * Copyright 2018 SmartBear Software | |||
There was a problem hiding this comment.
| * Copyright 2018 SmartBear Software |
There was a problem hiding this comment.
👍 although not sure why i should remove this a lot if not all the other codegens have this.
| import static groovyx.net.http.HttpBuilder.configure | ||
| import static java.net.URI.create | ||
|
|
||
| class ApiUtils { |
There was a problem hiding this comment.
I guess the groovy files are unrelated to this PR?
There was a problem hiding this comment.
for some reason building the projects creates all these groovy files, idk what is going on
| @@ -0,0 +1,192 @@ | |||
| /** | |||
There was a problem hiding this comment.
I guess the kotlin files are unrelated to this PR
| @@ -0,0 +1,31 @@ | |||
| #!/bin/sh | |||
There was a problem hiding this comment.
please also add an entry to
amakhrov
left a comment
There was a problem hiding this comment.
Thanks for the PR!
It looks like the generator is heavily based on the existing angular generator. I feel it needs another pass or two to clean up all the code that is only relevant to angular but not nestjs.
As a very side note. Unless nestjs requires some custom data / formatting that is not currently provided in typescript angular or other typescript generators (I'm not sure I can see such customizations in this PR) - you could achieve your goal by creating your own set of templates and passing them to one of the existing generators (via --template-dir cli argument). This would eliminate the need in a brand new generator and could save you significant amount of efforts.
| |npmRepository|Use this property to set an url your private npmRepo in the package.json| |null| | ||
| |withInterfaces|Setting this property to true will generate interfaces next to the default class implementations.| |false| | ||
| |taggedUnions|Use discriminators to create tagged unions instead of extending interfaces.| |false| | ||
| |providedInRoot|Use this property to provide Injectables in root (it is only valid in nestjs version greater or equal to 6.0.0).| |false| |
There was a problem hiding this comment.
could you please clarify how this option is used in the generated code?
There was a problem hiding this comment.
It is not used and should be removed!
| this.cliOptions.add(new CliOption(MODEL_SUFFIX, "The suffix of the generated model.")); | ||
| this.cliOptions.add(new CliOption(MODEL_FILE_SUFFIX, "The suffix of the file of the generated model (model<suffix>.ts).")); | ||
| this.cliOptions.add(new CliOption(FILE_NAMING, "Naming convention for the output files: 'camelCase', 'kebab-case'.").defaultValue(this.fileNaming)); | ||
| this.cliOptions.add(new CliOption(STRING_ENUMS, STRING_ENUMS_DESC).defaultValue(String.valueOf(this.stringEnums))); |
There was a problem hiding this comment.
Most of these cli options duplicate the ones defined in the parent class. Is there a particular reason to duplicate it here also?
There was a problem hiding this comment.
I cannot find the duplication, link?
| additionalProperties.put("injectionToken", nestVersion.atLeast("4.0.0") ? "InjectionToken" : "OpaqueToken"); | ||
| additionalProperties.put("injectionTokenTyped", nestVersion.atLeast("4.0.0")); | ||
| additionalProperties.put("useHttpClient", nestVersion.atLeast("4.3.0")); | ||
| additionalProperties.put("useRxJS6", nestVersion.atLeast("6.0.0")); |
There was a problem hiding this comment.
Looks like this logic was copied from the Angular generator. Does it apply to Nest framework with exactly same version numbers?
There was a problem hiding this comment.
No this line should be deleted
| } else { | ||
| // Nestjs v2-v4 requires typescript ">=2.1.5 <2.8" | ||
| additionalProperties.put("tsVersion", ">=2.1.5 <2.8.0"); | ||
| } |
There was a problem hiding this comment.
Is there a good reason to support old framework versions? Do you plan to use this generate in projects with old Nestjs?
| op.httpMethod = op.httpMethod.toLowerCase(Locale.ENGLISH); | ||
| } else { | ||
| // Convert httpMethod to Nestjs's RequestMethod enum | ||
| // https://nestjs.io/docs/ts/latest/api/http/index/RequestMethod-enum.html |
There was a problem hiding this comment.
broken link - seems to be copied from angular.
is this logic applicable to nestjs anyway?
There was a problem hiding this comment.
Disclaimer: I'm not a contributor here (at least not yet 😄), and I'm not familiar with the java codebase. But I have experience with Nest.js, so I'm dropping a few comments from that perspective. And I've been watching the issue for some time now, so I'm just curious how will it turn out 😃
I've added some nitpicks (like others already mentioned, some angular-related stuff needs to be cleaned up), plus one more serious issue about having multiple generated modules imported in one app. I'll try to build the generator and see what's the output for the APIs that I'm using, I'll come back with more feedback if I find anything interesting.
|
|
||
| ## CLIENT generators | ||
| * [ada](generators/ada.md) | ||
| ## CLIENT generators* [ada](generators/ada.md) |
There was a problem hiding this comment.
i think it's definitely a coincidence.
| @Module({ | ||
| imports: [ ApiModule.forRoot(apiConfigFactory) ], | ||
| declarations: [ AppComponent ], | ||
| providers: [], | ||
| bootstrap: [ AppComponent ] | ||
| }) | ||
| export class AppModule {} |
There was a problem hiding this comment.
this (and IDK how much more of this file) looks copied from angular and is not applicable to nest :)
There was a problem hiding this comment.
First part about creating a local package yes if you wanted to but ill remove. This part is is and below all apply.
| @Global | ||
| @Module({ | ||
| imports: [ HttpModule ], |
There was a problem hiding this comment.
Here is my main comment for this PR.
- Why is the module named
ApiModule? In my project, which isn't too large, I already have 8 different openapi-generated clients to connect to various 3rd party services, so I think the module should have a specific name. - Why is the module global? Did you maybe want to mark the
forRootdynamic module global instead? This way you'd importApiModule.forRoot(...)inAppModuleglobally, to provide theConfigurationin one place, and then import the plainApiModulejust in the modules that want to use the generated API client. If that's not the case, can you please show me a minimal example how do you intend this module to be used, so I can understand it better?
There was a problem hiding this comment.
the naming conflict can be avoided with renamed imports, see also #4101
There was a problem hiding this comment.
Imports can be renamed and it's not the problem -- what I'm worried is that the class name becomes the service/module's token in Nest's dependency injection system. I don't know precisely what will happen if you register two Nest modules/providers under the same name, but it most likely won't work. Example: the generated API clients inject the Configuration provider and the BASE_PATH value, which will be ambiguous since they are registered globally for all imported ApiModules.
I see it actually was fixed in Nest for module classes, but:
- the fix was made in a relatively recent Nest version (6.10),
- the fix handles module classes, so provider classes (like
Configurationhere) may still not work - IMHO it's just a bad practice, since it's very likely to import multiple different ApiModules in one app, so it decreases code readability and forces you to manually rename imports every time.
- note that the
BASE_PATHinjected config I mentioned in my other comment is a manually passed DI token, so it will always be ambiguous.
There was a problem hiding this comment.
true, so this is a bit different from the angular case.
There was a problem hiding this comment.
- Because that was a consistent name, the folder structure and the import as give a precise name. Although the cli could have an optional name to specify what you would like. This PR did not seem like the time to work out other enhancements.
- Global so that a consumer does not have to add the granularity which you are suggesting. The use I am using this fork .jar to codege for is one app initailization in a AWS lambda function. I do not need the granularity you suggest.
There was a problem hiding this comment.
@quezak class names do not become the Nestjs DI token because they are dynamic modules due to .forRoot(), if they did not have .forRoot() it would be a static module
Configuration and BASE_PATH as seen in the example readme are set by the factory pattern and not a problem as you are possibly stating.
| public static forRoot(configurationFactory: () => Configuration): DynamicModule { | ||
| return { | ||
| module: ApiModule, | ||
| providers: [ { provide: Configuration, useFactory: configurationFactory } ] |
There was a problem hiding this comment.
Same as (1.) here: if this is just named Configuration, won't the injection break if you import multiple generated modules for different APIs? Also, what if you already have a service named Configuration in your app (which seems likely), won't this break the DI?
There was a problem hiding this comment.
If classes are defined in different files (even with the same class name) - these are distinct class references from the JS perspective.
So if Nestjs uses this reference as an injection token (rather than, say, the string name of the class, then there should be no conflicts for DI.
E.g. Angular uses actual class reference for DI, not a name - so it's not an issue there.
Would you be willing to investigate how Nestjs DI works and post a comment in this PR? It can help with defining the direction for further changes
| if (!httpService) { | ||
| throw new Error('You need to import the {HttpModule in your AppModule!'); |
There was a problem hiding this comment.
Why is this check needed, if the ApiModule explicitly imports HttpModule?
| public defaultHeaders = new Map() | ||
| public configuration = new Configuration(); | ||
|
|
||
| constructor(protected httpClient: HttpService, @Inject('BASE_PATH') basePath: string, configuration: Configuration) { |
There was a problem hiding this comment.
BASE_PATH DI token will also be ambiguous if you have multiple generated clients in the project. We probably want to give it a more specific and unique name, like _OPENAPI_GENERATED_{{apiName}}_BASE_PATH, so it doesn't collide with any DI tokens that the users might already have in their Nest apps.
There was a problem hiding this comment.
Since it's a brand new generator with no need for backward compatibility, I would suggest getting rid of a dedicated "BASE_PATH" injection token altogether - in favor of Configuration (which also includes basePath)
| const form = 'multipart/form-data'; | ||
| for (const consume of consumes) { | ||
| if (form === consume) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; |
There was a problem hiding this comment.
| const form = 'multipart/form-data'; | |
| for (const consume of consumes) { | |
| if (form === consume) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| const form = 'multipart/form-data'; | |
| return consumes.includes(form); |
|
|
||
| // Set the typescript version compatible to the Nestjs version | ||
| if (nestVersion.atLeast("6.0.0")) { | ||
| additionalProperties.put("tsVersion", ">=3.4.0 <3.6.0"); |
There was a problem hiding this comment.
why <3.6.0? We're successfully running Nest 6 with TS 3.7.5 and I'm not aware of any requirements that would limit the version to <3.6.
There was a problem hiding this comment.
removed set to ">=3.4.0 <4.0.0"
There was a problem hiding this comment.
well, does it break with 4.0? :D (if not, why use max version limit?)
|
@amakhrov re Nest's management of same-name services: I've built a quick example and confirmed my version. TL;DR: Nest.js indeed uses class names for DI tokens, so the generated classes (injected in the same scope) should have unique names. Full example below. Note1: it could possibly work for Note2: even if it worked correctly, I still personally think it would be bad for DX and leading to confusing errors to export a non-meaningful ambiguous names like Example test service to be injected // dir1/test.service.ts
@Injectable()
export class TestService {
foo(): number { return 1; }
}
// dir1/test.module.ts
@Module({
providers: [TestService],
exports: [TestService],
})
export class TestModule {}
// dir2/test.module.ts copied
// dir2/test.service.ts copied, only `foo()` returns `2`Example usage 1: // a consumer module
import { TestModule as TestModule1 } from 'dir1/test.module';
import { TestModule as TestModule2 } from 'dir1/test.module';
@Module({ imports: [TestModule1, TestModule2] })
export class ConsumerModule {}
// a consumer service
import { TestService } from 'dir1/test.service'
@Injectable()
export class ConsumerService {
constructor(testService: TestService) {
console.log('foo:', testService.foo());
}
}result: console outputs Example usage 2: // a consumer module
import { TestModule } from 'dir2/test.module';
@Module({ imports: [TestModule] })
export class ConsumerModule {}
// a consumer service
import { TestService } from 'dir1/test.service'
@Injectable()
export class ConsumerService {
constructor(testService: TestService) {
console.log('foo:', testService.foo());
}
}result: console outputs |
|
@vfrank66 any chance to get this into 5.0.0? |
|
@macjohnny let me free up some time within the next 7 days to get back on this and fix all the current problems and then start address the questions, concerns here. Sorry for the delay I have been use a compiled jar of this code within nestjs projects for the post couple months and haven't come back to make sure it is the official release. |
Do you have this compiled jar as a release on your fork? I have a fairly elaborate schema that I'd like to use NestJs for. |
|
@vfrank66 is there anything we can help you with this PR? e.g. resolving merge conflicts? |
Hi, this feature would come in handy for my work. |
|
Hey, this is hanging here for a pretty long time already - anything I can do to help? What is missing in order to have it merged? |
there are some pretty serious (imo) unresolved review comments :) |
This is not valid, your example is not a dynamic module it is a static module. That is different than the generator |
|
@quezak check out nestjs/nest#1632 |
@vfrank66 can you please provide a full example of how a module generated with this would be used in a Nest app? Ideally with two different generated modules used in one app. I'm trying to understand the approach here as it looks different than what we're used to. |
|
|
||
| ## CLIENT generators | ||
| * [ada](generators/ada.md) | ||
| ## CLIENT generators* [ada](generators/ada.md) |
There was a problem hiding this comment.
i think it's definitely a coincidence.
|
|
||
| @Override | ||
| protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) { | ||
| codegenModel.additionalPropertiesType = getTypeDeclaration(ModelUtils.getAdditionalProperties(schema)); |
There was a problem hiding this comment.
use de base getAdditionalProperties function!
| * Finds and returns a path parameter of an operation by its name | ||
| * | ||
| * @param operation the operation | ||
| * @param operation the operation |
There was a problem hiding this comment.
| * @param operation the operation | |
| * @param operation the operation |
|
Can you, at the absolute least, get this to |
|
@manchuwook it would be nice to merge the current state as is with an experimental flag. if you want, you can fork the repo of @vfrank66, fix the CI, and create a separate PR. |
|
In the coming weekend, I'll try to merge this into a branch e.g. typescript-nestjs and resolve the merge conflicts as many things have changed since this PR was first filed. Then we'll see how we can move forward with this new generator (e.g. release as experimental) |
|
So what was the conclusion here? |
|
this PR was merged in #8522 to use it, you can use the current snapshot version or wait for the 5.0.1 release |
|
I tried this if i try this; I cant find nestjs |
|
@macjohnny, I will re-explain none of the snapshots has most probably a missing import in the code |
|
@msqaddura do you want to fix this? |
|
I tested with https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/5.0.1-SNAPSHOT/openapi-generator-cli-5.0.1-20210204.092149-93.jar and it works for me: java -jar openapi-generator-cli.jar generate -g typescript-nestjs -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -o /tmp/test/ |

fixes #3336 adding nestjs generator
PR checklist
./bin/(or Windows batch scripts under.\bin\windows) to update Petstore samples related to your fix. This is important, as CI jobs will verify all generator outputs of your HEAD commit, and these must match the expectations made by your contribution. You only need to run./bin/{LANG}-petstore.sh,./bin/openapi3/{LANG}-petstore.shif updating the code or mustache templates for a language ({LANG}) (e.g. php, ruby, python, etc).master,4.3.x,5.0.x. Default:master.@TiFu (2017/07) @taxpon (2017/07) @sebastianhaas (2017/07) @kenisteward (2017/07) @Vrolijkx (2017/09) @macjohnny (2018/01) @nicokoenig (2018/09) @topce (2018/10) @akehir (2019/07) @petejohansonxo (2019/11) @amakhrov (2020/02)