From 49bdeaf14644d7d325a92c73ad6ec5b39d13e77c Mon Sep 17 00:00:00 2001 From: Brennan Date: Tue, 27 May 2025 11:30:20 -0700 Subject: [PATCH 1/2] Fix OpenApiJsonSchema array parsing (#62051) --- .../src/Schemas/OpenApiJsonSchema.Helpers.cs | 2 + .../OpenApiSchemaService.ParameterSchemas.cs | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs b/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs index 830b0375d396..f05c595b5c2b 100644 --- a/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs +++ b/src/OpenApi/src/Schemas/OpenApiJsonSchema.Helpers.cs @@ -136,6 +136,8 @@ internal sealed partial class OpenApiJsonSchema { type = "array"; var array = new OpenApiArray(); + // Read to process JsonTokenType.StartArray before advancing + reader.Read(); while (reader.TokenType != JsonTokenType.EndArray) { array.Add(ReadOpenApiAny(ref reader)); diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs index 1c7cb1ba5746..3793c18cd8ce 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs @@ -722,4 +722,93 @@ public static bool TryParse(string value, out Student result) return true; } } + + // Regression test for https://github.com/dotnet/aspnetcore/issues/62023 + // Testing that the array parsing in our OpenApiJsonSchema works + [Fact] + public async Task CustomConverterThatOutputsArrayWithDefaultValue() + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.ConfigureHttpJsonOptions(options => + { + options.SerializerOptions.Converters.Add(new EnumArrayTypeConverter()); + }); + var builder = CreateBuilder(serviceCollection); + + // Act + builder.MapPost("/api", (EnumArrayType e = EnumArrayType.None) => { }); + + // Assert + await VerifyOpenApiDocument(builder, document => + { + var operation = document.Paths["/api"].Operations[HttpMethod.Post]; + var param = Assert.Single(operation.Parameters); + Assert.NotNull(param.Schema); + Assert.IsType(param.Schema.Default); + // Type is null, it's up to the user to configure this via a custom schema + // transformer for types with a converter. + Assert.Null(param.Schema.Type); + }); + } + + [Fact] + public async Task CustomConverterThatOutputsObjectWithDefaultValue() + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.ConfigureHttpJsonOptions(options => + { + options.SerializerOptions.Converters.Add(new EnumObjectTypeConverter()); + }); + var builder = CreateBuilder(serviceCollection); + + // Act + builder.MapPost("/api", (EnumArrayType e = EnumArrayType.None) => { }); + + // Assert + await VerifyOpenApiDocument(builder, document => + { + var operation = document.Paths["/api"].Operations[HttpMethod.Post]; + var param = Assert.Single(operation.Parameters); + Assert.NotNull(param.Schema); + Assert.IsType(param.Schema.Default); + // Type is null, it's up to the user to configure this via a custom schema + // transformer for types with a converter. + Assert.Null(param.Schema.Type); + }); + } + + public enum EnumArrayType + { + None = 1 + } + + public class EnumArrayTypeConverter : JsonConverter + { + public override EnumArrayType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new EnumArrayType(); + } + + public override void Write(Utf8JsonWriter writer, EnumArrayType value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + writer.WriteEndArray(); + } + } + + public class EnumObjectTypeConverter : JsonConverter + { + public override EnumArrayType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new EnumArrayType(); + } + + public override void Write(Utf8JsonWriter writer, EnumArrayType value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteEndObject(); + } + } } From 5a082f0e454039d7d0cb488c182cd51a5af8daf8 Mon Sep 17 00:00:00 2001 From: Brennan Date: Tue, 27 May 2025 12:37:07 -0700 Subject: [PATCH 2/2] translate --- .../OpenApiSchemaService.ParameterSchemas.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs index 3793c18cd8ce..6f87374b1c92 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ParameterSchemas.cs @@ -742,10 +742,10 @@ public async Task CustomConverterThatOutputsArrayWithDefaultValue() // Assert await VerifyOpenApiDocument(builder, document => { - var operation = document.Paths["/api"].Operations[HttpMethod.Post]; + var operation = document.Paths["/api"].Operations[OperationType.Post]; var param = Assert.Single(operation.Parameters); Assert.NotNull(param.Schema); - Assert.IsType(param.Schema.Default); + Assert.IsType(param.Schema.Default); // Type is null, it's up to the user to configure this via a custom schema // transformer for types with a converter. Assert.Null(param.Schema.Type); @@ -769,10 +769,10 @@ public async Task CustomConverterThatOutputsObjectWithDefaultValue() // Assert await VerifyOpenApiDocument(builder, document => { - var operation = document.Paths["/api"].Operations[HttpMethod.Post]; + var operation = document.Paths["/api"].Operations[OperationType.Post]; var param = Assert.Single(operation.Parameters); Assert.NotNull(param.Schema); - Assert.IsType(param.Schema.Default); + Assert.IsType(param.Schema.Default); // Type is null, it's up to the user to configure this via a custom schema // transformer for types with a converter. Assert.Null(param.Schema.Type);