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

New generator: typescript-redux-query #3824

Merged

Conversation

petejohansonxo
Copy link
Contributor

@petejohansonxo petejohansonxo commented Sep 3, 2019

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, ./bin/openapi3/{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\. If contributing template-only or documentation-only changes which will change sample output, be sure to build the project first.
  • Filed the PR against the correct branch: master, 4.1.x, 5.0.x. Default: master.
  • Copied the technical committee to review the pull request if your PR is targeting a particular programming language.

Description of the PR

This PR creates a new TypeScript generator that generates functions for consuming APIs using the redux-query library. In particular, redux-query operates on the concept of a QueryConfig that is an object structure that describes a query/mutation, and then the redux-query library then handles actually making the API requests, managing state of requests in Redux, and processing responses via the transform and update callbacks.

Given that, the functions exported from this generator don't actually call the operation, but instead generate a QueryConfig that describes an API request, and that can be passed to redux-query library from there.

Authentiation

The general recommendation for auth of redux-query requests is not to add the auth details "inline" in the QueryConfig objects, but instead use a Redux middleware to update the passed in Actions and update the QueryConfig w/ the necessary auth headers, etc.

To support this, instead of "inlining" the authentication bits, we instead generate code to include in the meta property of the QueryConfig a description of the requested authenticate type for the particular config. Custom middleware can be written by consumers of the generated code to inspect this meta-info at runtime and add the correct auth headers, to the QueryConfig objects before they get passed into the redux-query library where the requests are actually sent to the API endpoints.

My first generator and contribution to this project, so if I missed anything, please let me know.

Thanks!

CC: @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)

@@ -0,0 +1,292 @@
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
* Copyright 2018 SmartBear Software
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@wing328 Since this was copied from the typescript-fetch version as a starting point, does this still hold true?

Copy link
Member

Choose a reason for hiding this comment

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

I don't know. I'm not an expert in software licensing.

@@ -0,0 +1,32 @@
#!/bin/sh

SCRIPT="$0"
Copy link
Member

Choose a reason for hiding this comment

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

please add a windows version of this script in bin/windows

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done and pushed. Haven't tested them yet, I don't have easy access to a windows machine/VM currently.

return "{ [key: string]: " + this.getTypeDeclaration(inner) + "; }";
} else if (ModelUtils.isFileSchema(p)) {
return "Blob";
} else if (ModelUtils.isBinarySchema(p)) {
Copy link
Member

Choose a reason for hiding this comment

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

overriding with a custom --type-mappings date=string,date-time=string would not work here I guess, see also #3842

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exploring this. I can definitely see the allure of avoiding the JS Date and leveraging something like date-fns or moment.js instead.

Adding that parameter definitely doesn't work, and still produce Date typed properties on the various models.

I would ask if this is in scope of this initial PR, or if you'd be ok w/ merging without this pieces working, and then a follow up PR to add this ability.

Copy link
Member

Choose a reason for hiding this comment

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

Its ok, I just wanted to point out a potential problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it definitely is, and one I would like to see addressed in the generator at some point.

{{^isListContainer}}
if (requestParameters.{{paramName}} !== undefined) {
{{#isDateTime}}
queryParameters['{{baseName}}'] = (requestParameters.{{paramName}} as any).toISOString();
Copy link
Member

Choose a reason for hiding this comment

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

add null-check

Copy link
Contributor Author

@petejohansonxo petejohansonxo Oct 9, 2019

Choose a reason for hiding this comment

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

I'm not sure I'm following this request...

  1. queryParameters gets initialized on line 52 if we're inside this {{#queryParameters}} block.
  2. The call to toISOString() only occurs inside the {{#isDateTime}} check.

Is this a risk for "non-required DateTime parameters" that at the TS level get typed as foo?: Date and about case # 2 here?

Copy link
Member

Choose a reason for hiding this comment

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

requestParameters.{{paramName}} could be null or undefined, so it would be better to have

if (requestParameters.{{paramName}} !== undefined && requestParameters.{{paramName}} !== null) {

{{/isDateTime}}
{{^isDateTime}}
{{#isDate}}
queryParameters['{{baseName}}'] = (requestParameters.{{paramName}} as any).toISOString().substr(0,10);
Copy link
Member

Choose a reason for hiding this comment

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

add null-check


{{/consumes}}
{{#consumes.0}}
headerParameters['Content-Type'] = '{{{mediaType}}}';
Copy link
Member

Choose a reason for hiding this comment

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

support for multiple media types could be added, see #3849

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean around providing a specific Accept header here? It seems like this code producing the Content-Type for the request body generated is working as expected, and matches the code in #3849.

Copy link
Member

Choose a reason for hiding this comment

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

ah, yes, I was talking about the Accept header.

@macjohnny
Copy link
Member

@macjohnny
Copy link
Member

@petejohansonxo do you want to add yourself to the typescript technical committee (https://github.com/OpenAPITools/openapi-generator#62---openapi-generator-technical-committee) to keep an eye on future changes of the typescript-redux-query generator?

@macjohnny
Copy link
Member

@petejohansonxo any update here?

@petejohansonxo
Copy link
Contributor Author

@macjohnny Sorry for not getting back to this! I appreciate the review, and will work on addressing the comments this week.

@micahcraig
Copy link

Hi, apologies for barging in, but I came across this PR just as I was about to give up looking for a good way to integrate Redux with a Swagger specified API, so I'm very excited about it!

Unfortunately, I am running in to some errors while trying to build the generated sample:

mcraig43@C02TJ329HF1R:[~/projects/openapi-generator/samples/client/petstore/typescript-redux-query/builds/default] (generators/typescript-redux-query *)$ npm install

> @openapitools/[email protected] prepare /Users/mcraig43/projects/openapi-generator/samples/client/petstore/typescript-redux-query/builds/default
> npm run build


> @openapitools/[email protected] build /Users/mcraig43/projects/openapi-generator/samples/client/petstore/typescript-redux-query/builds/default
> tsc

node_modules/redux-query/index.d.ts:2:65 - error TS2307: Cannot find module 'redux'.

2   import { Action, AnyAction, Middleware, Reducer, Store } from 'redux';
                                                                  ~~~~~~~

node_modules/redux-query/index.d.ts:257:78 - error TS2744: Type parameter defaults can only reference previously declared type parameters.

257   export interface ReduxQueryDispatch<A extends AnyAction = ReduxQueryAction<TEntities>, TEntities = Entities> {
                                                                                 ~~~~~~~~~

The first error is easily resolved by adding redux to the dependencies in package.json.

I'm coming fresh to Typescript, so I'm less clear what can be done about the second error. I suspect there's some configuration I could be missing or another dependency I should install, but I'm not sure what or who.

If there's anything else I should try, or any way I could be of help in testing, please let me know! Thanks

@macjohnny
Copy link
Member

concerning

node_modules/redux-query/index.d.ts:257:78 - error TS2744: Type parameter defaults can only reference previously declared type parameters.

257   export interface ReduxQueryDispatch<A extends AnyAction = ReduxQueryAction<TEntities>, TEntities = Entities> {

maybe this could be avoided by switching the two generic parameters, i.e.

 export interface ReduxQueryDispatch<TEntities = Entities, A extends AnyAction = ReduxQueryAction<TEntities>> {

@micahcraig
Copy link

@macjohnny your suggested change on node_modules/redux-query/index.d.ts does fix the compilation error, but that file, obviously, is part of an upstream dependency. The dependency, redux-query, does compile successfully without modification in isolation.

@petejohansonxo Any other suggestions on getting through the tsc step with redux-query?

Thanks again!

@petejohansonxo
Copy link
Contributor Author

Yeah, that typescript error is an issue with the original TS generics improvements I'd sent over to the upstream of redux-query. I'm working on a fix for that upstream now.

@petejohansonxo
Copy link
Contributor Author

Upstream PR that should address this: amplitude/redux-query#155

@micahcraig
Copy link

With the compilation issues squared away, I've been able to begin integrating the generated client in my project, which is awesome and has allowed me very quickly start sending requests to my API. Thank you!

I have a couple of usage questions that might be more obvious to me if I'd spent more time with redux-query and/or TypeScript, so please forgive me if these are naive.

  1. We have a few different instances of our API for different environments which we enumerate in the 'servers' section of the spec (Open API 3.0). I see that the first server is reflected in the generated runtime.ts as BASE_PATH, but I don't see BASE_PATH referenced anywhere. Is there a good way to prepend the base path onto QueryConfig.url at runtime, or should this be done in the templates and generate time?
  2. It seems as though I need to provide transform and update functions in my requestConfig in order for my request results to end up in the Redux store. I appreciate this level of flexibility, but my expectation was that I would be able to invoke the functions exported from .../apis/ directly and that request results would automatically update the store. Am I missing a DRYer path for leveraging these functions, or are my expectations out of line with generator's intended usage?

Thanks again for all your hard work on this. Really appreciate the effort!

@petejohansonxo
Copy link
Contributor Author

@micahcraig Thanks for the testing!

  1. I haven't had a need for the server selection at all in our uses. Some options might be to add it as extra information in the meta of the passed in requestConfig, and then use a middleware to update the QueryConfig<T> url property to prepend the server URL before the request is handled by the redux-query middleware.
  2. Based on the way redux-query works, the most flexible approach to me seemed to be generating "QueryConfig generator functions" that would generate a QueryConfig<T> for each published operation that would:
    1. Strongly type the input types to generate the config,
    2. Allow strongly typed processing of the responses, which in redux-query land means an update and transform that account for the schema of the operation response type.

It didn't seem possible to have the generator presume where in the entities to place the responses, nor what update approach should be taken, so it requires the consumer of the generated generator functions to provide that.

If someone has some ideas for how you might automate those decisions in the generator, as maybe an optional mode, I'd be open to it, but it wasn't something we needed.

Our usage so far has been to invoke the generator, then do something like (pseudo-code):

compose(
  connectRequest(props => operationGeneratorFunction({ input_arg: props.arg }, {
    transform: (response: GeneratedResponseTypeSchema) => {
      return { entityProp: response.values };
    }
    update: {
      entityProp: secondArgFunctionSoWeUseNextValue
    }
  })
)(SomeComponent);

So we can benefit from the strong typing for input + output (transform), and we can also use the generator functions to instead pass to the useRequest/useMutation hook or pass the results to dispatch(mutateAsync(someGeneratorFunction())).then(foo => console.log(foo)).

@macjohnny
Copy link
Member

@petejohansonxo do you consider this ready to be merged so that an initial version can be released?

@macjohnny
Copy link
Member

@petejohansonxo can you please merge the current master?

@petejohansonxo petejohansonxo force-pushed the generators/typescript-redux-query branch from 5288926 to 337a0c2 Compare November 14, 2019 17:42
@petejohansonxo
Copy link
Contributor Author

@macjohnny Merged up with master. I would consider the basics here ready for testing. We're using this successfully w/ my latest version at work.

@macjohnny
Copy link
Member

If the CI completes successfully I will merge it

@micahcraig
Copy link

Got a bit further in my testing, and noticing an issue with generated mutations with multiple optional parameters. In that case, the generated Raw function gets:

    let queryParameters = null;

    queryParameters = {};

    if (requestParameters.startDate !== undefined) {
        queryParameters['start_date'] = (requestParameters.startDate as any).toISOString();
    }

    queryParameters = {};

    if (requestParameters.endDate !== undefined) {
        queryParameters['end_date'] = (requestParameters.endDate as any).toISOString();
    }

    queryParameters = {};

    if (requestParameters.types) {
        queryParameters['types'] = requestParameters.types;
    }

The upshot of which is that only the last included optional parameter is actually included on the request.

@petejohansonxo
Copy link
Contributor Author

@micahcraig Thanks for the testing. I just pushed a fix for this in a37b9a7

@macjohnny macjohnny added this to the 4.2.2 milestone Nov 22, 2019
@macjohnny macjohnny merged commit 70954ca into OpenAPITools:master Nov 22, 2019
@wing328
Copy link
Member

wing328 commented Dec 2, 2019

@petejohansonxo thanks for the PR, which has been included in the v4.2.2 release: https://twitter.com/oas_generator/status/1201432648544972800

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.

4 participants