Skip to content

fix: [csharp] JsonConverter anyOf creates uncompilable code #21137

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

Conversation

david-marconis
Copy link
Contributor

Using an anyOf with a discriminator creates uncompilable code with cshapr generichost. The issue is that the generated code tries to call a constructor with only one parameter, but the constructor will have multiple parameters.

Steps to reproduce:

# create openapi spec
cat << 'EOF' > modules/openapi-generator/src/test/resources/3_0/anyOfDiscriminatorSimple.yaml
openapi: 3.0.1
info:
   title: fruity
   version: 0.0.1
paths:
   /:
      get:
         responses:
            '200':
               description: desc
               content:
                  application/json:
                     schema:
                        $ref: '#/components/schemas/FruitAnyOfDisc'
components:
   schemas:
    FruitAnyOfDisc:
      anyOf:
        - $ref: '#/components/schemas/AppleAnyOfDisc'
        - $ref: '#/components/schemas/BananaAnyOfDisc'
      discriminator:
        propertyName: fruitType
    AppleAnyOfDisc:
      type: object
      required:
        - fruitType
      properties:
        fruitType:
          type: string
    BananaAnyOfDisc:
      type: object
      required:
        - fruitType
      properties:
        fruitType:
          type: string
EOF
# Generate csharp code
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g csharp -i modules/openapi-generator/src/test/resources/3_0/anyOfDiscriminatorSimple.yaml -o test
# Build csharp code
dotnet build test

Output:

❯ dotnet build test
Restore complete (0.4s)
  Org.OpenAPITools failed with 2 error(s) and 1 warning(s) (0.3s)
    /home/david/git/openapi-generator/test/src/Org.OpenAPITools/Model/FruitAnyOfDisc.cs(192,28): error CS7036: There is no argument given that corresponds to the required parameter 'bananaAnyOfDisc' of 'FruitAnyOfDisc.FruitAnyOfDisc(Option<AppleAnyOfDisc?>, Option<BananaAnyOfDisc?>)'
    /home/david/git/openapi-generator/test/src/Org.OpenAPITools/Model/FruitAnyOfDisc.cs(195,28): error CS7036: There is no argument given that corresponds to the required parameter 'bananaAnyOfDisc' of 'FruitAnyOfDisc.FruitAnyOfDisc(Option<AppleAnyOfDisc?>, Option<BananaAnyOfDisc?>)'
    /home/david/git/openapi-generator/test/src/Org.OpenAPITools/Model/FruitAnyOfDisc.cs(198,13): warning CS0162: Unreachable code detected

Build failed with 2 error(s) and 1 warning(s) in 0.8s

@mandrean (2017/08) @shibayan (2020/02) @Blackclaws (2021/03) @lucamazzanti (2021/05) @iBicha (2023/07)

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.

@wing328
Copy link
Member

wing328 commented Apr 24, 2025

thanks for the PR

cc @devhl-labs

@devhl-labs
Copy link
Contributor

I believe mappedModels is used for the discriminator. I also believe anyOf can have a discriminator so I am not sure this is the right fix.

@david-marconis
Copy link
Contributor Author

david-marconis commented Apr 25, 2025

I believe mappedModels is used for the discriminator. I also believe anyOf can have a discriminator so I am not sure this is the right fix.

anyOf can have a discriminator (see my example) and that is the case when this generates uncompilable code.

Right after this in the template there is a special handling for anyOf to set the correct Options. It doesnt't make sense to have the early return and then generate code after as well as it will be unreachable:

            {{#model.composedSchemas.anyOf}}
            Option<{{baseType}}{{>NullConditionalProperty}}> {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}ParsedValue = {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} == null
                ? default
                : new Option<{{baseType}}{{>NullConditionalProperty}}>({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}});
            {{/model.composedSchemas.anyOf}}

If this is not the correct way to solve it, what do you propose @devhl-labs?

@david-marconis
Copy link
Contributor Author

For context here is the generated code before and after the fix:

            if (!fruitType.IsSet)
                throw new ArgumentException("Property is required for class FruitAnyOfDisc.", nameof(fruitType));

            if (fruitType.IsSet && fruitType.Value == null)
                throw new ArgumentNullException(nameof(fruitType), "Property is not nullable for class FruitAnyOfDisc.");

            if (appleAnyOfDisc != null)
                return new FruitAnyOfDisc(appleAnyOfDisc);

            if (bananaAnyOfDisc != null)
                return new FruitAnyOfDisc(bananaAnyOfDisc);

            throw new JsonException();
            Option<AppleAnyOfDisc?> appleAnyOfDiscParsedValue = appleAnyOfDisc == null
                ? default
                : new Option<AppleAnyOfDisc?>(appleAnyOfDisc);
            Option<BananaAnyOfDisc?> bananaAnyOfDiscParsedValue = bananaAnyOfDisc == null
                ? default
                : new Option<BananaAnyOfDisc?>(bananaAnyOfDisc);

            return new FruitAnyOfDisc(appleAnyOfDiscParsedValue, bananaAnyOfDiscParsedValue);
            if (!fruitType.IsSet)
                throw new ArgumentException("Property is required for class FruitAnyOfDisc.", nameof(fruitType));

            if (fruitType.IsSet && fruitType.Value == null)
                throw new ArgumentNullException(nameof(fruitType), "Property is not nullable for class FruitAnyOfDisc.");

            Option<AppleAnyOfDisc?> appleAnyOfDiscParsedValue = appleAnyOfDisc == null
                ? default
                : new Option<AppleAnyOfDisc?>(appleAnyOfDisc);
            Option<BananaAnyOfDisc?> bananaAnyOfDiscParsedValue = bananaAnyOfDisc == null
                ? default
                : new Option<BananaAnyOfDisc?>(bananaAnyOfDisc);

            return new FruitAnyOfDisc(appleAnyOfDiscParsedValue, bananaAnyOfDiscParsedValue);

@devhl-labs
Copy link
Contributor

You have a discriminator but no mapping. I think we have some issues with mappings anyway so lets merge this so you get unblocked.

@wing328 wing328 merged commit 02204d0 into OpenAPITools:master Apr 26, 2025
14 checks passed
@wing328 wing328 added this to the 7.13.0 milestone Apr 26, 2025
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.

3 participants