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

[bugfix] [typescript-fetch] add oneOf string union & array support (#19909) #20193

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

thesn10
Copy link

@thesn10 thesn10 commented Nov 27, 2024

Fixes #19909

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 || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in Git BASH)
    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*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR is targeting a particular programming language, mention the technical committee members, so they are more likely to review the pull request.

@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) @davidgamero (2022/03) @mkusaka (2022/04) @joscha (2024/10)

private static String getEnumStringLiteralUnionType(Schema interfaceSchema) {
return (String) interfaceSchema.getEnum().stream()
.map(enumName -> "\"" + (String)enumName + "\"")
.collect(Collectors.joining(" | "));
Copy link
Member

@wing328 wing328 Nov 27, 2024

Choose a reason for hiding this comment

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

is that the same as https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java#L1123 ?

if overriding toOneOfName won't meet your requirement, can you please elaborate?

cc @OpenAPITools/generator-core-team

Copy link
Author

Choose a reason for hiding this comment

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

@wing328 Yea, they are pretty similar. The core difference is that toOneOfName seems to build the whole oneOf type, while mine only builds the string literal part. It would be nice to refactor them both into one method.

The Problem is, toOneOfName seems to be completely unused in the oneOf model generation.
Currently, the different oneOf union parts are just put in an array and are appended together inside the mustache, not using toOneOfName at all.

Refactoring it to build the full oneOf type inside the toOneOfName instead of inside the mustache seems to be a major change to how the code generation works and i need some input if this is the correct way, or if i misunderstood something, as i am new to the codebase

/**
*/
async testArrayRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<TestArrayResponse>> {
const queryParameters: any = {};
Copy link
Contributor

Choose a reason for hiding this comment

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

can this be at least:

Suggested change
const queryParameters: any = {};
const queryParameters: Record<string, any> = {};

?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, in fact, it can even be runtime.HTTPQuery

But i think this is outside of the scope of this PR as I did not make any changes to the api mustache, i just added a new testcase.
This PR is only about fixing the model mustache, not improving the api mustache, so I would recommend opening a new PR for this

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, sorry, I assumed because it was a newly generated block, it originated from your change, too.

if (!(json instanceof Object)){
return {} as any;
}
if (Array.isArray(json) && json.every(item => item instanceof String)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if these were type guards, Typescript can infer without the cast.
In this case for example:

function isStringArray(x: any): x is Array<string> {
    return Array.isArray(x) && x.every(item => item instanceof String);
}

see playground

Copy link
Author

Choose a reason for hiding this comment

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

Yes great idea, i'll implement it

Copy link
Contributor

Choose a reason for hiding this comment

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

item instanceof String is incorrect. you need typeof item === 'string' instead.
you can check it in the console:

> "" instanceof String
< false
> typeof "" === 'string'
< true 

Copy link
Contributor

@amakhrov amakhrov Nov 29, 2024

Choose a reason for hiding this comment

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

return {} as any;
}

export function TestArrayResponseToJSON(json: any): any {
Copy link
Contributor

Choose a reason for hiding this comment

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

can the return type be more narrow here?

Copy link
Author

Choose a reason for hiding this comment

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

I think the best we can do is runtime.Json but this is also just an alias for any so not really helpful.

Also I was also wondering why we have two methods: one typed named ToJSONTyped and one untyped named ToJSON. Do you think we can remove the untyped ToJSON?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure why, maybe a backwards compat thing? We can alias one to the other. Anything but unknown and never is a subsitute for any, so as long as we don't produce that we can remove the untyped variant implementation?

Copy link
Contributor

Choose a reason for hiding this comment

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

^ @macjohnny probably knows more?

return TestArrayResponseToJSONTyped(json, false);
}

export function TestArrayResponseToJSONTyped(value?: TestArrayResponse | null, ignoreDiscriminator: boolean = false): any {
Copy link
Contributor

Choose a reason for hiding this comment

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

^ ditto?

}
if (!(value instanceof Object)){
return {};
}
if (instanceOfTestA(value)) {
Copy link
Contributor

@joscha joscha Nov 28, 2024

Choose a reason for hiding this comment

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

Independent from your changes:

  • mental note to look at the generated instanceOf... functions to make them type guards, so we can lose some casts.

if (Array.isArray(json) && json.every(item => item instanceof String)) {
return json as Array<string>;
}
if (Array.isArray(json) && json.every(item => item instanceof Object)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

same here: instanceof Object is probably not what you need.
e.g. [] instanceof Object // true
use typeof instead

return json as Array<string>;
}
if (Array.isArray(json) && json.every(item => item instanceof Object)) {
if (json.every(item => instanceOfTestA(item as Object))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

once you have changed instanceof Object to typeof x === 'object, you will not need this type assertion (as Object)

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

Successfully merging this pull request may close these issues.

[BUG] [typescript-fetch] Broken files when mixing basic types and refs in oneOf
4 participants