Skip to content

Fix OpenAPI enum schema for non-body parameters with naming policy#66228

Open
mikekistler wants to merge 5 commits intodotnet:mainfrom
mikekistler:fix/65026-openapi-enum-query-params
Open

Fix OpenAPI enum schema for non-body parameters with naming policy#66228
mikekistler wants to merge 5 commits intodotnet:mainfrom
mikekistler:fix/65026-openapi-enum-query-params

Conversation

@mikekistler
Copy link
Copy Markdown
Contributor

@mikekistler mikekistler commented Apr 8, 2026

Summary

Fixes #65026

When a global JsonStringEnumConverter with a naming policy (e.g. KebabCaseLower) is configured via ConfigureHttpJsonOptions, the OpenAPI spec can emit naming-policy-transformed enum values (e.g. my-value) for non-body parameters. However, query/path/header/form parameter binding use Enum.TryParse, which only accepts the original C# member names (e.g. MyValue). This causes clients following the spec to send values that the server rejects with 400.

Changes

JsonNodeSchemaExtensions.ApplyParameterInfo — For non-body enum parameters (query, path, header, and form), replaces the naming-policy-transformed enum values with Enum.GetNames() so the schema matches what Enum.TryParse accepts.

OpenApiSchemaService.GetOrCreateSchemaAsync — When a naming policy transforms enum values, skips schema componentization for non-body enum parameters and returns an inline schema instead. This prevents the shared component (which retains naming-policy values for body serialization) from overwriting the corrected parameter schema.

These two changes work together so that:

  • Query/path/header/form params show PascalCase enum values (matching Enum.TryParse behavior)
  • Body params/responses keep naming-policy-transformed values (matching JSON serialization)
  • Enums with attribute-level converters (no global naming policy) are unaffected

Tests

  • GetOpenApiParameters_EnumWithGlobalNamingPolicy_UsesOriginalMemberNames — verifies query param enum values are PascalCase when a global KebabCaseLower policy is configured
  • GetOpenApiParameters_EnumWithGlobalNamingPolicy_HandlesQueryAndBodyUsage — verifies the same enum type produces PascalCase for query params and kebab-case for body responses
  • GetOpenApiRequestBody_EnumFormFieldWithGlobalNamingPolicy_UsesOriginalMemberNames — verifies [FromForm] enum values stay in their original C# member form

All 222 OpenApiSchemaServiceTests pass.

mikekistler and others added 2 commits April 8, 2026 13:28
When a global JsonStringEnumConverter with a naming policy (e.g.
KebabCaseLower) is configured, the OpenAPI spec now emits the
original C# enum member names for query/path/header parameters
instead of the naming-policy-transformed values. This matches the
actual binding behavior, which uses Enum.TryParse and only accepts
the original member names.

The fix adds logic in ApplyParameterInfo to detect non-body enum
parameters and replace their schema enum values with Enum.GetNames().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When an enum with a global naming policy is used as both a query
parameter and a body property/return type, the query param needs
PascalCase values (for Enum.TryParse) while the body needs the
naming-policy-transformed values (for JSON serialization).

Skip componentization for non-body enum parameters when a naming
policy transforms the values, returning an inline schema instead.
This preserves the component schema with naming-policy values for
body use while giving query/path/header parameters their own inline
schema with the original C# member names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mikekistler mikekistler requested a review from a team as a code owner April 8, 2026 20:40
Copilot AI review requested due to automatic review settings April 8, 2026 20:40
@github-actions github-actions Bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Apr 8, 2026
@mikekistler mikekistler added area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Apr 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes an OpenAPI/spec mismatch where global JsonStringEnumConverter naming policies were being applied to non-body parameters (query/path/header), even though runtime binding uses Enum.TryParse and only accepts the original C# enum member names.

Changes:

  • Adjust enum parameter schemas for non-body parameters to emit original enum member names.
  • Avoid schema componentization for affected non-body enum parameters to prevent shared component schemas (used for body serialization) from overwriting corrected parameter schemas.
  • Add tests covering query-parameter enums with a global naming policy, including mixed query + response usage of the same enum.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Shared/SharedTypes.cs Adds a new test enum (Priority) used for global-naming-policy scenarios.
src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs Adds coverage ensuring query parameter enums use original member names while response bodies keep naming-policy output.
src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs Skips componentization for affected non-body enum parameters when naming policy transforms enum values.
src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs Rewrites enum values for applicable parameter schemas to match Enum.TryParse expectations.

Comment thread src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs Outdated
Comment thread src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs Outdated
Add BindingSource.Form and BindingSource.FormFile to IsNonBodyBindingSource
so that enum form fields also use original C# member names instead of
naming-policy-transformed values. Minimal APIs report [FromForm] enum
parameters with BindingSource.FormFile, so both sources are needed.

Add test: GetOpenApiRequestBody_EnumFormFieldWithGlobalNamingPolicy_UsesOriginalMemberNames

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mikekistler and others added 2 commits April 14, 2026 14:54
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
{
enumArray.Add((JsonNode)name);
}
schema[OpenApiSchemaKeywords.EnumKeyword] = enumArray;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we also need to consider the default/x-ref-default values that will still be kebab-case?

var memberNames = Enum.GetNames(enumType);
for (var i = 0; i < memberNames.Length && i < rawEnum.Count; i++)
{
if (rawEnum[i]?.GetValue<string>() != memberNames[i])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are the enum values always going to line up with the member names if they are the 'correct' case already?

if (rawNode[OpenApiSchemaKeywords.EnumKeyword] is JsonArray rawEnum && rawEnum.Count > 0)
{
var memberNames = Enum.GetNames(enumType);
for (var i = 0; i < memberNames.Length && i < rawEnum.Count; i++)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI doesn't like this as there might be a count mismatch if names have the same value. e.g. enum E { A = 0, B = 1, C = 1} It says schema generation usually collapses duplicate values so you'd get ["A", "B"] but be comparing against ["A", "B", "C"].

I'm not sure how true that is, but we might want to test and handle that?

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime.
To make sure no conflicting changes have occurred, please rerun validation before merging. You can do this by leaving an /azp run comment here (requires commit rights), or by simply closing and reopening.

@dotnet-policy-service dotnet-policy-service Bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mismatch between OpenAPI spec and enum deserialization

3 participants