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

Add typescript-inversify code generator #7885

Merged

Conversation

gualtierim
Copy link
Contributor

@gualtierim gualtierim commented Mar 21, 2018

PR checklist

  • Read the contribution guidelines.
  • Ran the shell script under ./bin/ to update Petstore sample so that CIs can verify the change. (For instance, only need to run ./bin/{LANG}-petstore.sh and ./bin/security/{LANG}-petstore.sh if updating the {LANG} (e.g. php, ruby, python, etc) code generator or {LANG} client's mustache templates). Windows batch files can be found in .\bin\windows\.
  • Filed the PR against the correct branch: 3.0.0 branch for changes related to OpenAPI spec 3.0. Default: master.
  • Copied the technical committee to review the pull request if your PR is targeting a particular programming language.

Description of the PR

We added a code generator called typescript-inversify to support InversifyJS: a powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript. Inside the README is explained how to integrate services generated in an existing project.

@gualtierim gualtierim changed the title Add typescript-inversify language Add typescript-inversify code generator Mar 21, 2018
Copy link
Contributor

@macjohnny macjohnny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general, this looks very nice.

@@ -0,0 +1,31 @@
#!/bin/sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a .cmd version of this file for windows-users

@@ -0,0 +1,31 @@
#!/bin/sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a .cmd version of this file for windows-users

@@ -0,0 +1,62 @@
import IHttpClient from "./IHttpClient";
import * as Rx from "rx";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't it be better to

import { Observable } from "rxjs/Observable";

?

if ({{paramName}}) {
{{#isCollectionFormatMulti}}
{{paramName}}.forEach((element) => {
queryParameters.push("{{paramName}}="+String({{paramName}}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to encodeURIComponent(),

queryParameters.push("{{paramName}}="+encodeURIComponent(String({{paramName}})));

{{^isListContainer}}
if ({{paramName}} !== undefined) {
{{#isDateTime}}
queryParameters.push("{{paramName}}="+<any>{{paramName}}.toISOString());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to encodeURIComponent(),

queryParameters.push("{{paramName}}="+encodeURIComponent(<any>{{paramName}}.toISOString()));

{{/isKeyInHeader}}
{{#isKeyInQuery}}
if (this.APIConfiguration.apiKeys["{{keyParamName}}"]) {
queryParameters.set('{{keyParamName}}', this.APIConfiguration.apiKeys["{{keyParamName}}"]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how can queryParameters.set() work on a string[] ?
should be

queryParameters.push("{{keyParamName}}="+encodeURIComponent(this.APIConfiguration.apiKeys["{{keyParamName}}"]));


{{/hasFormParams}}

return this.httpClient.{{httpMethod}}(`${this.basePath}{{{path}}}{{#hasQueryParams}}?${encodeURIComponent(queryParameters.join('&'))}{{/hasQueryParams}}`{{#bodyParam}}, {{paramName}} {{/bodyParam}}{{#hasFormParams}}, body{{/hasFormParams}}, headers)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encodeURIComponent(queryParameters.join('&'))`` should be queryParameters.join('&'), otherwise the =between the parameter name and the value will be converted to%3D`

* {{notes}}
{{#allParams}}* @param {{paramName}} {{description}}
{{/allParams}}*/
{{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}extraHttpRequestParams?: any): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}{}{{/returnType}}>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the signature should correspond to the api service, i.e. should be

{{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}headers: Dictionary<string> = {}): Promise<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>

"dependencies": {
"inversify": "^4.3.0",
"lodash": "^4.17.5",
"rx": "~4.1.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't you want to have rxjs: "~5.5.2" ?

import mockit.Expectations;
import mockit.Tested;

public class TypeScriptInversifyClientOptionsTest extends AbstractOptionsTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice

@macjohnny
Copy link
Contributor

@gualtierim
Copy link
Contributor Author

Hi, thanks for all yours suggestions. I should have solved all the problems listed.

import { Dictionary } from "lodash";

interface IHttpClient {
get(url:string, headers?:Dictionary<string>):Rx.Observable<HttpResponse>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you really want to use a lodash dictionary? why not use a plain JavaScript object?

Copy link
Contributor Author

@gualtierim gualtierim Mar 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lodash dictionary restrict the type of values and not permit nested Object. Also Angular's HttpClient allows only string as value (https://angular.io/api/http/Headers)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can restrict the value of an object as follows:

let myObject: {[key: string]: string}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have reason, i'm removing lodash.

headers['Accept'] = 'application/xml';


return this.httpClient.get(`${this.basePath}/pet/findByStatus?${encodeURIComponent(queryParameters.join('&'))}`, headers)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like you need to re-generate the file, since the encodeURIComponent should be removed here

headers['Content-Type'] = 'application/json';


return this.httpClient.post(`${this.basePath}/store/order`, body , headers)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure you want to convert all response observables to promises?
returning them as observables would make it convenient to chain with RxJS, similar to the Angular HttpClient.
As a drawback, you always need to subscribe to the API call observable, otherwise the request is not performed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this subject I was undecided, because the observable returned by the HttpClient complete immediately and returns only a value (as every Promise). But you have reason that returing a observable would make convenient to chain with RxJS, but you can do it with the operator fromPromise if necessary... I'm very undecided about this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to adapt the interface of the Angular HttpClient with Observables.
Anyone else got an opinion on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a configuration variable that allow the user to decide if use Promise or Observable. What about this solution?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gualtierim this sounds like a great idea!

Copy link
Contributor Author

@gualtierim gualtierim Mar 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@macjohnny I have added the usePromise config's variable.

@macjohnny
Copy link
Contributor

in general, I would not use lodash here, as it is not needed for any functionality and it adds a dependency. moreover, not everyone wants to use lodash.

*/
{{/description}}
export interface {{classname}}Interface {
[others: string]: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you set the additional parameters withInterfaces, the apiInterface should be necessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was referring to [others: string]: any;

@@ -1,79 +0,0 @@
export interface ConfigurationParameters {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gualtierim why did you remove the configuration template?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because i only use the IApiConfiguration that you have to implement and bind inside the container.

@macjohnny
Copy link
Contributor

@gualtierim your code looks great.
One thing that I think would be useful is to be able to access the response headers, etc, too. Maybe you could implement this similar to the typescript-angular generator?

@gualtierim
Copy link
Contributor Author

@macjohnny i have added the possibility to receive all the httpResponse. I tried to implement it as in typescript-angular generator.

/**
* Add a new pet to the store
*
* @param body Pet object that needs to be added to the store

*/

public addPet(body: Pet, headers: Headers = {}): Observable<any> {
public addPet(body: Pet, observe?: 'body', headers?: Headers): Observable<any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gualtierim can you find out why the response is any? it should be Pet, shouldn't it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, it's because the addPet endpoint does not return anything, so it is ok

return this.httpClient.{{httpMethod}}(`${this.basePath}{{{path}}}{{#hasQueryParams}}?${queryParameters.join('&')}{{/hasQueryParams}}`{{#bodyParam}}, {{paramName}} {{/bodyParam}}{{#hasFormParams}}, body{{/hasFormParams}}, headers)
.map(httpResponse => <{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>(httpResponse.response)){{#usePromise}}.toPromise(){{/usePromise}};
const response: Observable<HttpResponse<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>> = this.httpClient.{{httpMethod}}(`${this.basePath}{{{path}}}{{#hasQueryParams}}?${queryParameters.join('&')}{{/hasQueryParams}}`{{#bodyParam}}, {{paramName}} {{/bodyParam}}{{#hasFormParams}}, body{{/hasFormParams}}, headers);
if(observe == 'body')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please wrap the if block with curly braces { and }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I done it

@macjohnny
Copy link
Contributor

@gualtierim looks great!

@gualtierim
Copy link
Contributor Author

@macjohnny thanks to your support

Copy link
Contributor

@macjohnny macjohnny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested it, but the code looks good

@macjohnny
Copy link
Contributor

@gualtierim did you test the generated code in an actual project?

@gualtierim
Copy link
Contributor Author

Yes, i have tested it inside a project and it seems to work.

@wing328
Copy link
Contributor

wing328 commented Mar 30, 2018

I'll take another look and merge this weekend. Thanks for the new generator!

@wing328 wing328 added this to the v2.4.0 milestone Apr 3, 2018
@wing328 wing328 merged commit e2c58fa into swagger-api:master Apr 3, 2018
@wing328
Copy link
Contributor

wing328 commented Apr 6, 2018

It seems like there's an issue with as I got the following errors when running "mvn clean test " locally:

Failed tests: 
  SpringOptionsTest>AbstractOptionsTest.checkOptionsHelp:39 » NullPointer
  SpringOptionsTest>AbstractOptionsTest.checkOptionsProcessing:28 » NullPointer

(which seems totally unrelated)
I've removed it via #7983 for the time being. Please take a look when you've time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants