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

Issue #10593 Fix nullable property implemented with oneOf construct #10602

Closed

Conversation

etremblay
Copy link
Contributor

@etremblay etremblay commented Oct 14, 2021

See issue #10593

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.3.0), 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.

Typescript
@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)

Kotlin
@jimschubert (2017/09) heart, @dr4ke616 (2018/08) @karismann (2019/03) @Zomzog (2019/04) @andrewemery (2019/10) @4brunu (2019/11) @yutaka0m (2020/03)

PHP
@jebentier (2017/07), @dkarlovi (2017/07), @mandrean (2017/08), @jfastnacht (2017/09), @ackintosh (2017/09) heart, @ybelenko (2018/07), @renepardon (2018/12)

Java
@bbdouglas (2017/07) @sreeshas (2017/08) @jfiala (2017/08) @lukoyanov (2017/09) @cbornet (2017/09) @jeff9finger (2018/01) @karismann (2019/03) @Zomzog (2019/04) @lwlee2608 (2019/10) @nmuesch (2021/01)

@etremblay etremblay changed the title Issue #10593 Fix nullable type handling Issue #10593 Fix nullable implemented with oneOf type handling Oct 14, 2021
@etremblay etremblay marked this pull request as ready for review October 14, 2021 13:24
@@ -846,7 +846,7 @@ export type Fruit = Apple | Banana;
* @type FruitReq
* @export
*/
export type FruitReq = AppleReq | BananaReq | Null;
export type FruitReq = AppleReq | BananaReq | null;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Look like we fixed some bug here too

@etremblay etremblay changed the title Issue #10593 Fix nullable implemented with oneOf type handling Issue #10593 Fix nullable property implemented with oneOf construct Oct 14, 2021
* @param nonNullableProperty
*/

data class ModelWithNullableObjectProperty (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is what we tried to achieve : val propertyName31: PropertyType? = null,

* @export
* @interface ModelWithNullableObjectProperty
*/
export interface ModelWithNullableObjectProperty {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

here the fix for typescript

})
@javax.annotation.processing.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen")
public class ModelWithNullableObjectProperty {
public static final String JSON_PROPERTY_PROPERTY_NAME30 = "propertyName30";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is the result for java

Copy link
Contributor

@amakhrov amakhrov left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution!
I haven't even realized nullable was so broken :)

@@ -2092,6 +2092,10 @@ public String toAnyOfName(List<String> names, ComposedSchema composedSchema) {
* Otherwise, a name is constructed by creating a comma-separated list of all the names
* of the oneOf schemas.
*
* If the oneOf schema contains only one element and have the openapi 3.0 nullable flag,
* or one element and the openapi 3.1 'null' type, only this element is returned.
Copy link
Contributor

Choose a reason for hiding this comment

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

how about the case with more than one type in oneOf?

oneOf:
  - 'null'
  - ref: ModelA
  - ref: ModelB

How do we handle 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 didn't change that one, it will behave like before.

Since many language plugin does not seen to really support oneOf, it will still be broken.

I think typescript will be ok and produce ModelA | Model B | null

Copy link
Contributor

@amakhrov amakhrov Oct 14, 2021

Choose a reason for hiding this comment

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

Do you think it's worth adding to the test spec and see what the generated typescript code looks like?
The reason I'm specifically curious about it is that you seem to filter out the "null" part of the union. And I'm not sure whether it still preserves in some flags and resurfaces later

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 added a case in the yaml. We can see the result in this commit :

9b9f58659cefc284e9a4c0ce3c361c8c404d2292

export interface ModelWithNullableObjectProperty {
/**
*
* @type {PropertyType}
Copy link
Contributor

Choose a reason for hiding this comment

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

@type annotation is no longer correct (should be PropertyType | null).

honestly, I don't know why it's needed anyway, in the presence of the real type definitions.
but if it's there and not in sync with the actual type, I'm concerned it might confuse some tools/IDEs.
Any thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Humm, I did not change the templates, but if you want my personal advice, I would remove the @type. Not in this pr of course.


'propertyName30': PropertyTypeToJSON(value.propertyName30),
'propertyName31': PropertyTypeToJSON(value.propertyName31),
'nonNullableProperty': string | numberToJSON(value.nonNullableProperty),
Copy link
Contributor

Choose a reason for hiding this comment

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

broken typescript code

it doesn't mean it's a regression - maybe we never had a similar case in the samples, and it's been always effectively broken. but need to verify

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, this is weird. I added this property int the last minute to check non-regression on my unit tests and did not check the generated code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The template takes the dataType as is whish is string | number

I think is is indeed not a regression.

'{{baseName}}': {{datatype}}ToJSON(value.{{name}}),

Copy link
Contributor

Choose a reason for hiding this comment

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

yep, you're right! I just recalled it getting surfaced in another PR earlier: #10017 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This won't be trivial to fix. I think it should be documented in another issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

Absolutely. I just wanted to double check it's not something we're breaking in this PR - that's it :)

// Non regression on regular oneOf construct
propertyName = "nonNullableProperty";
property = codegen.fromProperty(propertyName, (Schema) schema.getProperties().get(propertyName));
assertEquals(property.openApiType, "string | number");
Copy link
Contributor

Choose a reason for hiding this comment

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

so for regular non-nullable oneOf we produce e.g string | number.
why do we need different handling for null? I feel if it could be string | null, we would have more consistent templates / generated code. for typescript, at least

Copy link
Contributor Author

@etremblay etremblay Oct 14, 2021

Choose a reason for hiding this comment

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

| null is somehow added later in the process.

It's not very easy to navigate this code, not all language plugin are made in a consistant way and some things that could have been handled in the DefaultCodegen looks hacked in some languages.

The post-precessing process is not all clear to me. Also it's not clear how to apply it in unit tests.

Copy link
Contributor

@amakhrov amakhrov left a comment

Choose a reason for hiding this comment

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

Looks good from my standpoint (typescript).

I didn't look closely into java/kotlin generators output though.

Copy link
Contributor

@TiFu TiFu left a comment

Choose a reason for hiding this comment

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

Agree with @amakhrov - TypeScript looks good from my perspective as well.

* @var string[]
*/
protected static $openAPITypes = [
'property_name30' => 'PropertyType',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We just noticed that we still have a problem for PHP.

This name must be typed with namespaces for ObjectSerializer to be able to handle it

@etremblay
Copy link
Contributor Author

Just updated this branch with master. I will put it on hold until I fix PHP serialization

@etremblay etremblay marked this pull request as draft November 10, 2021 22:20
@etremblay etremblay marked this pull request as ready for review November 11, 2021 00:55
@etremblay
Copy link
Contributor Author

PHP namespaces are now fixed. This pr is ready for re-review.

Copy link
Contributor Author

@etremblay etremblay left a comment

Choose a reason for hiding this comment

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

forgot to submit these comments days ago

return $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.

Regular non-nullable oneOf properties are not handled by this PR. It's normal to still get strange types for the two following properties

* @template TKey int|null
* @template TValue mixed|null
*/
class ModelWithNullableObjectProperty implements ModelInterface, ArrayAccess, \JsonSerializable
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Begin of the interesting part for PHP

@etremblay
Copy link
Contributor Author

etremblay commented Jun 15, 2022

@wing328

I just updated this pr on master (6.0.0) and some of my fixes does not work anymore. I could trace it to a big refacor on inline schema resolver done in #12104

I also noticed some weird stuff with 6.0.0 on some of our generated sdks.

This is not relate to this pr, but you can notice the diff on ma last commit f4043c9

there is a new model generated from one of the inline field : ModelWithNullableObjectPropertyNonNullableProperty

components:
  schemas:
    ModelWithNullableObjectProperty:
      properties:
        propertyName:
          $ref: '#/components/schemas/PropertyType'
    PropertyType:
      properties:
        foo:
          type: string

I can file a new issue for that, but for this pr I would like your advice on the best way I could handle nullable oneOf with your new resolver.

Regards,

@etremblay
Copy link
Contributor Author

@wing328 I managed to make my nullable fixes work again 7dee4c1

There are still some generated types from oneOf or anyOf but I think it was intended.

For exemple a oneOf composed of only basic types would resolve in typescript to a simple union type but now a type is created.

export type ModelWithNullableObjectPropertyNonNullableProperty = number | string;

@etremblay
Copy link
Contributor Author

Most of the features introduced by this pull request are now available in the 7.1 branch.

The only case that did not work for us was the

    Schema:
      required:
        - property
      properties:
        propety:
          oneOf:
            -
              $ref: '#/components/schemas/NullableProperty'
          nullable: true

I was able to fix it in the OpenAPI Normalizer

kronostechnologies@7ca8611

@etremblay etremblay closed this Dec 12, 2023
@etremblay etremblay deleted the fix-nullable-field branch December 12, 2023 16:03
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.

None yet

3 participants