fix(normalizer): support OAS 3.1 annotated enums (oneOf + const) for consistent enum generation (C#)#23869
fix(normalizer): support OAS 3.1 annotated enums (oneOf + const) for consistent enum generation (C#)#23869nagabalaji-b wants to merge 16 commits into
Conversation
…oding on .NET 9+ On .NET 9+, HttpUtility.ParseQueryString() already URL-encodes values internally. The previous fix only checked for '.NET 9' literally, missing .NET 10+ (PowerShell 7.6). Replace the string prefix check with a numeric major version comparison (>= 9) and move the RuntimeInformation check outside the foreach loop to avoid redundant parsing. Fixes: CSCwu08056
fix(csharp): numeric .NET version check to prevent double URL-encoding on .NET 9+ (CSCwu08056)
There was a problem hiding this comment.
3 issues found across 8 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| if (subSchema.getEnum() == null || subSchema.getEnum().isEmpty()) { | ||
| // Check if this sub-schema has an enum or const value (OpenAPI 3.1 uses const for single-value enums) | ||
| List<Object> subSchemaEnumValues = subSchema.getEnum(); | ||
| if ((subSchemaEnumValues == null || subSchemaEnumValues.isEmpty()) && subSchema.getConst() == null) { |
There was a problem hiding this comment.
Rather than doing (subSchemaEnumValues == null || subSchemaEnumValues.isEmpty()) twice, could we extract this to a variable boolean definesEnum = ModelUtils.hasEnum(subSchema) and then have
if (!definesEnum && subSchema.getConst() == null) {This could allow use to skip the comments almost entirely and instead allow the variable/method names be the explanation themselves.
| |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| | ||
| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| | ||
| |sourceFolder|source folder for generated code| |OpenAPI/src| | ||
| |sourceFolder|source folder for generated code| |OpenAPI\src| |
There was a problem hiding this comment.
Are these path changes intentional?
|
Thanks for adjusting to the suggestions. 👍 I am not a maintainer of the project, so I cannot make a formal review with an approve. |
| // After normalization, oneOf with const values should be simplified to enum | ||
| assertEquals(schema16.getOneOf(), null); | ||
| assertEquals(schema16.getEnum().size(), 3); | ||
| assertEquals(schema16.getEnum().get(0), 1); |
There was a problem hiding this comment.
(I'm not repo maintainer but the PR is something I was thinking about myself: so thank you)
The test against deprecated (https://github.com/nagabalaji-b/openapi-generator/blob/3496c3b7cffbc0cdc728cc77bd64708b8ba6ae9a/modules/openapi-generator/src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml#L133) is lost here (and the information from schema too)
Here, seems it is not possible with current enum support implementation ("simple" list of values and optional list of descriptions with x-enum-descriptions) to support enum as "schema" enabling to keep all "schemas" related information with no loss.
There was a problem hiding this comment.
@nagabalaji-b can you please take a look and add back the deprecated test?
There was a problem hiding this comment.
@wing328 Done. I added back the deprecated test coverage:
Code change: Modified simplifyComposedSchemaWithEnums() to collect and preserve per-value deprecated flags from OAS 3.1 oneOf/anyOf + const sub-schemas into x-enum-deprecated extension.
Test assertion: Added explicit checks in testOpenAPINormalizerSimplifyOneOfAnyOf31Spec() for TypeIntegerWithOneOf schema:
Verifies x-enum-deprecated list size = 3
Verifies flags match expected values: [true, false, false] (first enum value was deprecated in the original YAML)
Validation: Ran the test locally and confirmed BUILD SUCCESS.
The deprecated metadata from the annotated enum pattern (oneOf + const) is now preserved through normalization, so generators can access it via the x-enum-deprecated extension.
|
please update the samples to fix https://github.com/OpenAPITools/openapi-generator/actions/runs/26996697206/job/81834665420?pr=23869 |
There was a problem hiding this comment.
1 issue found across 74 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
ddbb944 to
b45f152
Compare
There was a problem hiding this comment.
1 issue found across 74 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
@wing328 , i ran bin\generate-samples.sh and commited the generated samples. |
This reverts commit 6ef74e7.
There was a problem hiding this comment.
7 issues found across 746 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/README.md">
<violation number="1" location="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/README.md:2">
P2: Typo in README link label: `REAMDE` should be `README`. This appears to propagate from the C# generichost Mustache template (`modules/openapi-generator/src/main/resources/csharp/libraries/generichost/README.solution.mustache:2`).</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/appveyor.yml">
<violation number="1" location="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/appveyor.yml:3">
P2: AppVeyor image Visual Studio 2019 is too old for the sample's net10.0 target framework</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/docs/models/ParentWithPluralOneOfPropertyNumber.md">
<violation number="1" location="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/docs/models/ParentWithPluralOneOfPropertyNumber.md:6">
P2: Model documentation is missing properties for `ParentWithPluralOneOfPropertyNumber`</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateTimeJsonConverter.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateTimeJsonConverter.cs:26">
P2: Public mutable `static string[] Formats` exposes internal deserialization state, allowing external mutation that can silently alter accepted date formats at runtime.</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateOnlyJsonConverter.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateOnlyJsonConverter.cs:41">
P2: Custom System.Text.Json converter throws NotSupportedException for invalid/null JSON values instead of JsonException</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/docs/scripts/git_push.ps1">
<violation number="1" location="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/docs/scripts/git_push.ps1:20">
P2: Remote detection logic incorrectly checks for any remote but unconditionally uses `origin`. If a repo has a different remote (e.g. `upstream`) and no `origin`, the existence check passes but `git pull origin` will fail.</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateTimeNullableJsonConverter.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateTimeNullableJsonConverter.cs:55">
P1: Invalid date-time strings are silently coerced to null instead of throwing a deserialization exception, conflating malformed input with explicit JSON null and masking upstream data issues.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 40 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Re-trigger cubic
| /// <returns></returns> | ||
| public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { | ||
| if (reader.TokenType == JsonTokenType.Null) | ||
| return null; |
There was a problem hiding this comment.
P1: Invalid date-time strings are silently coerced to null instead of throwing a deserialization exception, conflating malformed input with explicit JSON null and masking upstream data issues.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateTimeNullableJsonConverter.cs, line 55:
<comment>Invalid date-time strings are silently coerced to null instead of throwing a deserialization exception, conflating malformed input with explicit JSON null and masking upstream data issues.</comment>
<file context>
@@ -0,0 +1,80 @@
+ /// <returns></returns>
+ public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
+ if (reader.TokenType == JsonTokenType.Null)
+ return null;
+
+ string value = reader.GetString()!;
</file context>
| @@ -0,0 +1,2 @@ | |||
| # Created with Openapi Generator | |||
| See the project's [REAMDE](src/Org.OpenAPITools/README.md) No newline at end of file | |||
There was a problem hiding this comment.
P2: Typo in README link label: REAMDE should be README. This appears to propagate from the C# generichost Mustache template (modules/openapi-generator/src/main/resources/csharp/libraries/generichost/README.solution.mustache:2).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/README.md, line 2:
<comment>Typo in README link label: `REAMDE` should be `README`. This appears to propagate from the C# generichost Mustache template (`modules/openapi-generator/src/main/resources/csharp/libraries/generichost/README.solution.mustache:2`).</comment>
<file context>
@@ -0,0 +1,2 @@
+# Created with Openapi Generator
+See the project's [REAMDE](src/Org.OpenAPITools/README.md)
\ No newline at end of file
</file context>
| @@ -0,0 +1,9 @@ | |||
| # auto-generated by OpenAPI Generator (https://github.com/OpenAPITools/openapi-generator) | |||
| # | |||
| image: Visual Studio 2019 | |||
There was a problem hiding this comment.
P2: AppVeyor image Visual Studio 2019 is too old for the sample's net10.0 target framework
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/appveyor.yml, line 3:
<comment>AppVeyor image Visual Studio 2019 is too old for the sample's net10.0 target framework</comment>
<file context>
@@ -0,0 +1,9 @@
+# auto-generated by OpenAPI Generator (https://github.com/OpenAPITools/openapi-generator)
+#
+image: Visual Studio 2019
+clone_depth: 1
+build_script:
</file context>
| image: Visual Studio 2019 | |
| image: Visual Studio 2022 |
| ## Properties | ||
|
|
||
| Name | Type | Description | Notes | ||
| ------------ | ------------- | ------------- | ------------- |
There was a problem hiding this comment.
P2: Model documentation is missing properties for ParentWithPluralOneOfPropertyNumber
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/docs/models/ParentWithPluralOneOfPropertyNumber.md, line 6:
<comment>Model documentation is missing properties for `ParentWithPluralOneOfPropertyNumber`</comment>
<file context>
@@ -0,0 +1,9 @@
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+
+[[Back to Model list]](../../README.md#documentation-for-models) [[Back to API list]](../../README.md#documentation-for-api-endpoints) [[Back to README]](../../README.md)
</file context>
| /// <summary> | ||
| /// The formats used to deserialize the date | ||
| /// </summary> | ||
| public static string[] Formats { get; } = { |
There was a problem hiding this comment.
P2: Public mutable static string[] Formats exposes internal deserialization state, allowing external mutation that can silently alter accepted date formats at runtime.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateTimeJsonConverter.cs, line 26:
<comment>Public mutable `static string[] Formats` exposes internal deserialization state, allowing external mutation that can silently alter accepted date formats at runtime.</comment>
<file context>
@@ -0,0 +1,75 @@
+ /// <summary>
+ /// The formats used to deserialize the date
+ /// </summary>
+ public static string[] Formats { get; } = {
+ "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK",
+ "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffffK",
</file context>
| /// <returns></returns> | ||
| public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { | ||
| if (reader.TokenType == JsonTokenType.Null) | ||
| throw new NotSupportedException(); |
There was a problem hiding this comment.
P2: Custom System.Text.Json converter throws NotSupportedException for invalid/null JSON values instead of JsonException
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/src/Org.OpenAPITools/Client/DateOnlyJsonConverter.cs, line 41:
<comment>Custom System.Text.Json converter throws NotSupportedException for invalid/null JSON values instead of JsonException</comment>
<file context>
@@ -0,0 +1,61 @@
+ /// <returns></returns>
+ public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
+ if (reader.TokenType == JsonTokenType.Null)
+ throw new NotSupportedException();
+
+ string value = reader.GetString()!;
</file context>
| git add . | ||
| git commit -am "${Message}" | ||
| $branchName=$(git rev-parse --abbrev-ref HEAD) | ||
| $gitRemote=$(git remote) |
There was a problem hiding this comment.
P2: Remote detection logic incorrectly checks for any remote but unconditionally uses origin. If a repo has a different remote (e.g. upstream) and no origin, the existence check passes but git pull origin will fail.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/docs/scripts/git_push.ps1, line 20:
<comment>Remote detection logic incorrectly checks for any remote but unconditionally uses `origin`. If a repo has a different remote (e.g. `upstream`) and no `origin`, the existence check passes but `git pull origin` will fail.</comment>
<file context>
@@ -0,0 +1,75 @@
+ git add .
+ git commit -am "${Message}"
+ $branchName=$(git rev-parse --abbrev-ref HEAD)
+ $gitRemote=$(git remote)
+
+ if([string]::IsNullOrWhiteSpace($gitRemote)){
</file context>
| $gitRemote=$(git remote) | |
| $gitRemote=$(git remote | Where-Object { $_ -eq "origin" }) |
Summary
This PR addresses normalization for OpenAPI 3.1 annotated enums, where enum values are expressed using
oneOf+const. The main focus is on ensuring C# behavior aligns with traditional enum handling.Problem
OpenAPI 3.0 typically utilizes enum arrays. In contrast, OpenAPI 3.1 often adopts an annotated enum style with
oneOf+constand per-value descriptions. Prior to this fix, const-based composed schemas were not consistently simplified into enum-like schemas, potentially impacting strong enum generation in C#.Example (OAS 3.1)
Equivalent OAS 3.0 Shape
C# Before vs After
Before Fix (String Property)
The property is a plain string because
oneOf+constwas not normalized to enum semantics.After Fix (Strong Enum)
After normalization, the schema is recognized as an enum and generates a strongly-typed enum with the appropriate JSON converter and member attributes.
Actual Behavior Before
OAS 3.1
oneOf+constwas not consistently normalized to enum semantics. C# output could revert to non-enum-style modeling for this pattern.Expected Behavior
OAS 3.1
oneOf+constshould normalize in the same manner as OAS 3.0 enums. C# should consistently produce strongly typed enum output.Changes in This PR
constas a single enum value when an enum is absent in composed sub-schemas.oneOfandanyOfenum-like paths.C# Result After Fix (Illustrative)
Validation
The targeted normalizer test for OAS 3.1 simplifying
oneOf/anyOfpaths passed. Samples have been regenerated for Java configurations. Documentation for the generator has been exported.Compatibility
This is a bug fix only. No intended breaking behavior changes. The normalization improvement is generator-agnostic; C# is the primary verified use case.
PR Checklist
@muttleyxd @devhl-labs @lucasheim @shibayan (C# generators)
Summary by cubic
Normalize OpenAPI 3.1 annotated enums (
oneOf+const) as true enums. This ensures that C# code generation produces strong enums consistently, matching OAS 3.0 behavior.constas a single enum value when an enum is missing in sub-schemas.oneOf/anyOfenum-like schemas into a single enum in the parent, preserving types and per-value descriptions.Written for commit 51e0b0f. Summary will update with new commits. Review in cubic