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

[typescript][axios] Handle boolean enum correctly #9025

Merged
merged 2 commits into from Jun 18, 2021

Conversation

haedaal
Copy link
Contributor

@haedaal haedaal commented Mar 20, 2021

closes #9024

with following schema

    "schemas": {
      "BooleanEnum": {
        "enum": ["true", "false"],
        "type": "boolean"
      },

https://gist.github.com/haedaal/a771d6f3df337b624ae8a2c2d176c0db

typescript-fetch currently generates

export enum BooleanEnum {
    True = 'true',
    False = 'false'
}

with this pr

export type BooleanEnum = true | false

by using 'type' rather than 'enum' we breaks consistency, but TS18033 does not allow this

export enum BooleanEnum {
    True = true, // Only numeric enums can have computed members, but this expression has type 'true' ...
    False = false
}

this pr has 2 changes.

  1. by adding isBoolean, generate model correctly
  2. fix typescript-axios modelEnum.mustache

accidentally typescript-angular is fixed without template changes, but many others need their own fixes on template.

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package 
    ./bin/generate-samples.sh
    ./bin/utils/export_docs_generators.sh
    
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    For Windows users, please run the script in Git BASH.
  • File the PR against the correct branch: master, 5.1.x, 6.0.x
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

cc @TiFu (2017/07) @taxpon (2017/07) @sebastianhaas (2017/07) @kenisteward (2017/07) @Vrolijkx (2017/09) @macjohnny (2018/01) @topce (2018/10) @akehir (2019/07) @petejohansonxo (2019/11) @amakhrov (2020/02)

can't use enum because of TS18033
@auto-labeler
Copy link

auto-labeler bot commented Mar 20, 2021

👍 Thanks for opening this issue!
🏷 I have applied any labels matching special text in your issue.

The team will review the labels and make any necessary changes.

Copy link

@tusharchutani tusharchutani left a comment

Choose a reason for hiding this comment

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

This looks good

@macjohnny
Copy link
Member

What is the use case to have an enum with two boolean values?

@haedaal
Copy link
Contributor Author

haedaal commented Apr 7, 2021

I think it's main use case is boolean literal type, not an enum with two boolean value (though I took the example above)

type FooResponse = FooSucessResponse | FooFailureResponse
type FooSuccessResponse = {
  success: true,
  body: {
    foo: string
  }
}
type FooFailureResponse = {
  success: false,
  body: {
    reason: string
  }
}

this case needs boolean enum with single boolean value.
by handling this case correctly, typescript can infer body type if value of success is given

@haedaal
Copy link
Contributor Author

haedaal commented May 6, 2021

@tusharchutani any plan to merge it..?

@amakhrov
Copy link
Contributor

amakhrov commented May 7, 2021

Is it even a valid or reasonable schema in the first place?
I know it satisfies the validator - but the following one also does.

type: boolean
enum: [true, false, fuzzy]

So it tells us more about strictness of the validator, rather than correctness of the spec.

type: boolean already unambiguously indicates that the only two possible values are true and false (but not "true" or "false"!)

In this case we just need to decide whether we give priority to type attribute or enum attribute. In the former case, the generated code should not emit an additional type at all - just use boolean for the model property. In the latter case a string enum, as it's generated today, sounds correct.

@kenisteward
Copy link
Contributor

kenisteward commented May 7, 2021 via email

@kenisteward
Copy link
Contributor

kenisteward commented May 7, 2021 via email

@haedaal
Copy link
Contributor Author

haedaal commented May 7, 2021

as a result of assigning string value to the value of boolean enum,
following pattern is required for now

if (response.success === 'true') { }

even though you've specified the type {success: boolean}

@haedaal
Copy link
Contributor Author

haedaal commented Jun 7, 2021

@amakhrov does it make sense? I'm wondering what's blocking point.

@amakhrov
Copy link
Contributor

amakhrov commented Jun 7, 2021

@haedaal I still struggle to understand the use case, honestly. Why define a boolean value in the spec as an enum? Why are the enum values are defined as strings in the spec - even though you clearly want them to be boolean in the generated code.

@haedaal
Copy link
Contributor Author

haedaal commented Jun 8, 2021

@haedaal I still struggle to understand the use case, honestly. Why define a boolean value in the spec as an enum? Why are the enum values are defined as strings in the spec - even though you clearly want them to be boolean in the generated code.

@amakhrov

  1. Why define a boolean value in the spec as an enum?
type GetNextOrderResponse = {success: true, data: Order} | {success: false, data?: unknown} // or any other FailReason type for data in fail case
  1. Why are the enum values are defined as strings in the spec - even though you clearly want them to be boolean in the generated code.

that's the whole point of this pr. now codegen is generating string enum value rather than boolean enum value even though I clearly specify them as a boolean! this pr is a bugfix (if not a hotfix)

I've made it an issue in #9024. I've also put a sample schema that generates wrong result.

@haedaal
Copy link
Contributor Author

haedaal commented Jun 8, 2021

This is true the original schema is not valid. If you want to use the enum properly it should not be 'true' but just true

that's exactly what I thought is a problem, but if you try it, making schema like this

    "schemas": {
      "BooleanEnum": {
        "enum": [true, false],
        "type": "boolean"
      },
    }

does not fix the wrong result.

it's because we check only for number type and unconditionally wrap it's value with double quote as you can see here.
And this pr add "boolean" type to the cases where you have to use value as it is.

public String toEnumValue(String value, String datatype) {
if ("number".equalsIgnoreCase(datatype)) {
return value;
} else {
return "\"" + escapeText(value) + "\"";
}
}

if ("number".equalsIgnoreCase(datatype) || "boolean".equalsIgnoreCase(datatype)) {
return value;
} else {
return "\"" + escapeText(value) + "\"";
}

@amakhrov
Copy link
Contributor

amakhrov commented Jun 8, 2021

Ok, I think now I see what you're trying to do (sorry, it took me a while :) ).

You don't actually need BooleanEnum = true | false (since it's redundant). You want smth like TrueBooleanEnum = true and FalseBooleanEnum = false - is that correct?

If so, I would rather try to avoid generating an enum in the first place. The ideal generated model would be exactly as in your example

{success: true, data: Order}

(no enum whatsoever)

However, I admit this would probably be a bigger change in the templates. So as a quick solution your PR makes total sense to me

@haedaal
Copy link
Contributor Author

haedaal commented Jun 9, 2021

You don't actually need BooleanEnum = true | false (since it's redundant). You want smth like TrueBooleanEnum = true and FalseBooleanEnum = false - is that correct?

Yes! Exactly! :)

@@ -64,7 +64,7 @@
public String defaultValue;
public String arrayModelType;
public boolean isAlias; // Is this effectively an alias of another simple type
public boolean isString, isInteger, isLong, isNumber, isNumeric, isFloat, isDouble, isDate, isDateTime, isShort, isUnboundedInteger;
public boolean isString, isInteger, isLong, isNumber, isNumeric, isFloat, isDouble, isDate, isDateTime, isShort, isUnboundedInteger, isBoolean;
Copy link
Member

Choose a reason for hiding this comment

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

The following function also needs to be updated as well:

I'll update these in another PR after merging this one.

Copy link
Member

Choose a reason for hiding this comment

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

Filed #9802

@wing328
Copy link
Member

wing328 commented Jun 18, 2021

cc @OpenAPITools/generator-core-team as the change impacts the codegen model class.

@wing328 wing328 added this to the 5.2.0 milestone Jun 18, 2021
@wing328 wing328 merged commit 41afcd0 into OpenAPITools:master Jun 18, 2021
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.

[BUG] typescript-axios - boolean enum is generated as string enum
6 participants