Skip to content

fix(normalizer): support OAS 3.1 annotated enums (oneOf + const) for consistent enum generation (C#)#23869

Open
nagabalaji-b wants to merge 3 commits into
OpenAPITools:masterfrom
nagabalaji-b:openapitools-master
Open

fix(normalizer): support OAS 3.1 annotated enums (oneOf + const) for consistent enum generation (C#)#23869
nagabalaji-b wants to merge 3 commits into
OpenAPITools:masterfrom
nagabalaji-b:openapitools-master

Conversation

@nagabalaji-b
Copy link
Copy Markdown

@nagabalaji-b nagabalaji-b commented May 25, 2026

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 + const and 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)

StatusType:
type: string
oneOf:
- const: ""
description: Unknown status
- const: ACTIVE
description: Active status
- const: INACTIVE
description: Inactive status
default: ""
x-omitempty: true

Equivalent OAS 3.0 Shape

StatusType:
type: string
enum:
- ""
- ACTIVE
- INACTIVE
default: ""
x-omitempty: true

C# Before vs After

Before Fix (String Property)

[DataMember(Name = "statusType", EmitDefaultValue = true)]
public string StatusType { get; set; }

private string _statusType;
private bool _flagStatusType;

The property is a plain string because oneOf + const was not normalized to enum semantics.

After Fix (Strong Enum)

[JsonConverter(typeof(StringEnumConverter))]
public enum StatusTypeEnum
{
/// /// Enum Empty for value: 
/// [EnumMember(Value = "")]
Empty = 1,

/// /// Enum ACTIVE for value: ACTIVE
/// [EnumMember(Value = "ACTIVE")]
ACTIVE = 2,

/// /// Enum INACTIVE for value: INACTIVE
/// [EnumMember(Value = "INACTIVE")]
INACTIVE = 3
}

[DataMember(Name = "statusType", EmitDefaultValue = true)]
public StatusTypeEnum? StatusType { get; set; }

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 + const was not consistently normalized to enum semantics. C# output could revert to non-enum-style modeling for this pattern.

Expected Behavior

OAS 3.1 oneOf + const should normalize in the same manner as OAS 3.0 enums. C# should consistently produce strongly typed enum output.

Changes in This PR

  • Treat const as a single enum value when an enum is absent in composed sub-schemas.
  • Apply composed enum simplification consistently for oneOf and anyOf enum-like paths.
  • Update OAS 3.1 normalizer test expectations for the new normalized shape.
  • Regenerate samples and documentation for the generator.

C# Result After Fix (Illustrative)

[JsonConverter(typeof(StringEnumConverter))]
public enum ComponentTypeEnum
{
/// /// Enum Empty for value: 
/// [EnumMember(Value = "")]
Empty = 1,

/// /// Enum CORE for value: CORE
/// [EnumMember(Value = "CORE")]
CORE = 2,

/// /// Enum EDGE for value: EDGE
/// [EnumMember(Value = "EDGE")]
EDGE = 3
}

public class SampleModel
{
[DataMember(Name = "componentType", EmitDefaultValue = true)]
public ComponentTypeEnum? ComponentType { get; set; }
}

Validation

The targeted normalizer test for OAS 3.1 simplifying oneOf/anyOf paths 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

  • Read the contribution guidelines.
  • The Pull Request title clearly describes the work in the pull request, and the Pull Request description provides details on how to validate the work. Missing information here may result in a delayed response from the community.
  • Run the following commands to build the project and update samples:
./mvnw clean package -DskipTests
./bin/generate-samples.sh bin/configs/csharp-restsharp*.yaml bin/configs/csharp-httpclient*.yaml
./bin/utils/export_docs_generators.sh
  • File the PR against the correct branch: master.
  • If your PR targets a specific programming language, @mention the technical committee members for that language:
    @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.

  • Bug Fixes
  • Treat const as a single enum value when an enum is missing in sub-schemas.
  • Collapse oneOf/anyOf enum-like schemas into a single enum in the parent, preserving types and per-value descriptions.
  • Update tests to expect enums after normalization; regenerate documentation and samples.
    Written for commit 51e0b0f. Summary will update with new commits. Review in cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 8 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread docs/generators/swift6.md Outdated
Comment thread docs/generators/swift6.md Outdated
Comment thread docs/generators/java-inflector.md Outdated
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) {
Copy link
Copy Markdown
Contributor

@Mattias-Sehlstedt Mattias-Sehlstedt May 25, 2026

Choose a reason for hiding this comment

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

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|
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are these path changes intentional?

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.

2 participants