diff --git a/src/OpenApi/src/Services/OpenApiDocumentService.cs b/src/OpenApi/src/Services/OpenApiDocumentService.cs index 394e248afff0..2745d64770a7 100644 --- a/src/OpenApi/src/Services/OpenApiDocumentService.cs +++ b/src/OpenApi/src/Services/OpenApiDocumentService.cs @@ -382,7 +382,7 @@ private async Task GetResponseAsync( .SelectMany(attr => attr.ContentTypes); foreach (var contentType in explicitContentTypes) { - response.Content[contentType] = new OpenApiMediaType(); + response.Content.TryAdd(contentType, new OpenApiMediaType()); } return response; diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs index 13964943c347..d7ea158b919a 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs @@ -32,6 +32,7 @@ internal sealed class OpenApiSchemaService( IOptionsMonitor optionsMonitor) { private readonly OpenApiSchemaStore _schemaStore = serviceProvider.GetRequiredKeyedService(documentName); + private readonly OpenApiJsonSchemaContext _jsonSchemaContext = new OpenApiJsonSchemaContext(new(jsonOptions.Value.SerializerOptions)); private readonly JsonSerializerOptions _jsonSerializerOptions = new(jsonOptions.Value.SerializerOptions) { // In order to properly handle the `RequiredAttribute` on type properties, add a modifier to support @@ -135,7 +136,9 @@ internal async Task GetOrCreateSchemaAsync(Type type, IServicePro { schemaAsJsonObject.ApplyParameterInfo(parameterDescription, _jsonSerializerOptions.GetTypeInfo(type)); } - var deserializedSchema = JsonSerializer.Deserialize(schemaAsJsonObject, OpenApiJsonSchemaContext.Default.OpenApiJsonSchema); + // Use _jsonSchemaContext constructed from _jsonSerializerOptions to respect shared config set by end-user, + // particularly in the case of maxDepth. + var deserializedSchema = JsonSerializer.Deserialize(schemaAsJsonObject, _jsonSchemaContext.OpenApiJsonSchema); Debug.Assert(deserializedSchema != null, "The schema should have been deserialized successfully and materialize a non-null value."); var schema = deserializedSchema.Schema; await ApplySchemaTransformersAsync(schema, type, scopedServiceProvider, schemaTransformers, parameterDescription, cancellationToken); diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaStore.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaStore.cs index ab597b85ee7a..88f1dd4633af 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaStore.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaStore.cs @@ -77,34 +77,38 @@ public JsonNode GetOrAdd(OpenApiSchemaKey key, Func /// been encountered more than once in the document to avoid unnecessarily capturing unique /// schemas into the top-level document. /// + /// + /// We don't do a depth check in the recursion call here since we assume that + /// System.Text.Json has already validate the depth of the schema based on + /// the configured JsonSerializerOptions.MaxDepth value. + /// /// The to add to the schemas-with-references cache. /// if schema should always be referenced instead of inlined. public void PopulateSchemaIntoReferenceCache(OpenApiSchema schema, bool captureSchemaByRef) { - // Only capture top-level schemas by ref. Nested schemas will follow the - // reference by duplicate rules. AddOrUpdateSchemaByReference(schema, captureSchemaByRef: captureSchemaByRef); AddOrUpdateAnyOfSubSchemaByReference(schema); + if (schema.AdditionalProperties is not null) { - AddOrUpdateSchemaByReference(schema.AdditionalProperties); + PopulateSchemaIntoReferenceCache(schema.AdditionalProperties, captureSchemaByRef); } if (schema.Items is not null) { - AddOrUpdateSchemaByReference(schema.Items); + PopulateSchemaIntoReferenceCache(schema.Items, captureSchemaByRef); } if (schema.AllOf is not null) { foreach (var allOfSchema in schema.AllOf) { - AddOrUpdateSchemaByReference(allOfSchema); + PopulateSchemaIntoReferenceCache(allOfSchema, captureSchemaByRef); } } if (schema.Properties is not null) { foreach (var property in schema.Properties.Values) { - AddOrUpdateSchemaByReference(property); + PopulateSchemaIntoReferenceCache(property, captureSchemaByRef); } } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/CreateSchemaReferenceIdTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/CreateSchemaReferenceIdTests.cs index 0a66bda8cc03..f488a6ffc737 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/CreateSchemaReferenceIdTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/CreateSchemaReferenceIdTests.cs @@ -65,7 +65,7 @@ public async Task GeneratesSchemaForPoco_WithSchemaReferenceIdCustomization() // Act builder.MapPost("/", (Todo todo) => { }); - var options = new OpenApiOptions { CreateSchemaReferenceId = (type) => $"{type.Type.Name}Schema" }; + var options = new OpenApiOptions { CreateSchemaReferenceId = (type) => type.Type.Name == "Todo" ? $"{type.Type.Name}Schema" : OpenApiOptions.CreateDefaultSchemaReferenceId(type) }; // Assert await VerifyOpenApiDocument(builder, options, document => @@ -114,7 +114,7 @@ public async Task GeneratesInlineSchemaForPoco_WithCustomNullId() // Act builder.MapPost("/", (Todo todo) => { }); - var options = new OpenApiOptions { CreateSchemaReferenceId = (type) => type.Type.Name == "Todo" ? null : $"{type.Type.Name}Schema" }; + var options = new OpenApiOptions { CreateSchemaReferenceId = (type) => type.Type.Name == "Todo" ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type) }; // Assert await VerifyOpenApiDocument(builder, options, document => diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs index bdc840c6c106..e773ebf5ff89 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; -using System.Runtime.InteropServices; +using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.OpenApi; @@ -19,8 +20,10 @@ using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; using Microsoft.OpenApi.Models; using Moq; +using Xunit.Sdk; using static Microsoft.AspNetCore.OpenApi.Tests.OpenApiOperationGeneratorTests; public abstract class OpenApiDocumentServiceTestBase @@ -53,7 +56,10 @@ internal static OpenApiDocumentService CreateDocumentService(IEndpointRouteBuild ApplicationName = nameof(OpenApiDocumentServiceTests) }; - var options = new MvcOptions(); + var options = new MvcOptions + { + OutputFormatters = { new TestJsonOutputFormatter() } + }; var optionsAccessor = Options.Create(options); var constraintResolver = new Mock(); @@ -87,6 +93,22 @@ internal static OpenApiDocumentService CreateDocumentService(IEndpointRouteBuild return documentService; } + private class TestJsonOutputFormatter : TextOutputFormatter + { + public TestJsonOutputFormatter() + { + SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); + SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json")); + + SupportedEncodings.Add(Encoding.UTF8); + } + + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + return Task.FromResult(0); + } + } + internal static OpenApiDocumentService CreateDocumentService(IEndpointRouteBuilder builder, OpenApiOptions openApiOptions) { var context = new ApiDescriptionProviderContext([]); @@ -203,6 +225,14 @@ public ControllerActionDescriptor CreateActionDescriptor(string methodName = nul action.RouteValues.Add("controller", "Test"); action.RouteValues.Add("action", action.MethodInfo.Name); action.ActionConstraints = [new HttpMethodActionConstraint(["GET"])]; + action.EndpointMetadata = [..action.MethodInfo.GetCustomAttributes()]; + if (controllerType is not null) + { + foreach (var attribute in controllerType.GetCustomAttributes()) + { + action.EndpointMetadata.Add(attribute); + } + } action.Parameters = []; foreach (var parameter in action.MethodInfo.GetParameters()) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs index 52d4020189c0..5ec980b6296c 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; @@ -292,8 +293,9 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("value", property.Key); - Assert.Equal("object", property.Value.Type); - Assert.Collection(property.Value.Properties, + var propertyValue = property.Value.GetEffective(document); + Assert.Equal("object", propertyValue.Type); + Assert.Collection(propertyValue.Properties, property => { Assert.Equal("id", property.Key); @@ -317,8 +319,9 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("error", property.Key); - Assert.Equal("object", property.Value.Type); - Assert.Collection(property.Value.Properties, property => + var propertyValue = property.Value.GetEffective(document); + Assert.Equal("object", propertyValue.Type); + Assert.Collection(propertyValue.Properties, property => { Assert.Equal("code", property.Key); Assert.Equal("integer", property.Value.Type); @@ -404,8 +407,10 @@ await VerifyOpenApiDocument(builder, document => property => { Assert.Equal("todo", property.Key); - Assert.Equal("object", property.Value.Type); - Assert.Collection(property.Value.Properties, + Assert.NotNull(property.Value.Reference); + var propertyValue = property.Value.GetEffective(document); + Assert.Equal("object", propertyValue.Type); + Assert.Collection(propertyValue.Properties, property => { Assert.Equal("id", property.Key); @@ -529,8 +534,10 @@ await VerifyOpenApiDocument(builder, document => Assert.Equal("items", property.Key); Assert.Equal("array", property.Value.Type); Assert.NotNull(property.Value.Items); - Assert.Equal("object", property.Value.Items.Type); - Assert.Collection(property.Value.Items.Properties, + Assert.NotNull(property.Value.Items.Reference); + Assert.Equal("object", property.Value.Items.GetEffective(document).Type); + var itemsValue = property.Value.Items.GetEffective(document); + Assert.Collection(itemsValue.Properties, property => { Assert.Equal("id", property.Key); @@ -653,6 +660,53 @@ await VerifyOpenApiDocument(builder, document => }); } + [Fact] + public async Task GetOpenApiResponse_SupportsProducesWithProducesResponseTypeOnController() + { + var actionDescriptor = CreateActionDescriptor(nameof(TestController.Get), typeof(TestController)); + + await VerifyOpenApiDocument(actionDescriptor, document => + { + var operation = document.Paths["/"].Operations[OperationType.Get]; + var responses = Assert.Single(operation.Responses); + var response = responses.Value; + Assert.True(response.Content.TryGetValue("application/json", out var mediaType)); + var schema = mediaType.Schema.GetEffective(document); + Assert.Equal("object", schema.Type); + Assert.Collection(schema.Properties, + property => + { + Assert.Equal("id", property.Key); + Assert.Equal("integer", property.Value.Type); + }, + property => + { + Assert.Equal("title", property.Key); + Assert.Equal("string", property.Value.Type); + }, + property => + { + Assert.Equal("completed", property.Key); + Assert.Equal("boolean", property.Value.Type); + }, + property => + { + Assert.Equal("createdAt", property.Key); + Assert.Equal("string", property.Value.Type); + Assert.Equal("date-time", property.Value.Format); + }); + }); + } + + [ApiController] + [Produces("application/json")] + public class TestController + { + [Route("/")] + [ProducesResponseType(typeof(Todo), StatusCodes.Status200OK)] + internal Todo Get() => new(1, "Write test", false, DateTime.Now); + } + private class ClassWithObjectProperty { public object Object { get; set; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Shared/SharedTypes.DeeplyNestedType.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Shared/SharedTypes.DeeplyNestedType.cs new file mode 100644 index 000000000000..d1f4f06685e7 --- /dev/null +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Shared/SharedTypes.DeeplyNestedType.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +internal class DeeplyNestedLevel1 +{ + public DeeplyNestedLevel2 Item2 { get; set; } +} + +internal class DeeplyNestedLevel2 +{ + public DeeplyNestedLevel3 Item3 { get; set; } +} + +internal class DeeplyNestedLevel3 +{ + public DeeplyNestedLevel4 Item4 { get; set; } +} + +internal class DeeplyNestedLevel4 +{ + public DeeplyNestedLevel5 Item5 { get; set; } +} + +internal class DeeplyNestedLevel5 +{ + public DeeplyNestedLevel6 Item6 { get; set; } +} + +internal class DeeplyNestedLevel6 +{ + public DeeplyNestedLevel7 Item7 { get; set; } +} + +internal class DeeplyNestedLevel7 +{ + public DeeplyNestedLevel8 Item8 { get; set; } +} + +internal class DeeplyNestedLevel8 +{ + public DeeplyNestedLevel9 Item9 { get; set; } +} + +internal class DeeplyNestedLevel9 +{ + public DeeplyNestedLevel10 Item10 { get; set; } +} + +internal class DeeplyNestedLevel10 +{ + public DeeplyNestedLevel11 Item11 { get; set; } +} + +internal class DeeplyNestedLevel11 +{ + public DeeplyNestedLevel12 Item12 { get; set; } +} + +internal class DeeplyNestedLevel12 +{ + public DeeplyNestedLevel13 Item13 { get; set; } +} + +internal class DeeplyNestedLevel13 +{ + public DeeplyNestedLevel14 Item14 { get; set; } +} + +internal class DeeplyNestedLevel14 +{ + public DeeplyNestedLevel15 Item15 { get; set; } +} + +internal class DeeplyNestedLevel15 +{ + public DeeplyNestedLevel16 Item16 { get; set; } +} + +internal class DeeplyNestedLevel16 +{ + public DeeplyNestedLevel17 Item17 { get; set; } +} + +internal class DeeplyNestedLevel17 +{ + public DeeplyNestedLevel18 Item18 { get; set; } +} + +internal class DeeplyNestedLevel18 +{ + public DeeplyNestedLevel19 Item19 { get; set; } +} + +internal class DeeplyNestedLevel19 +{ + public DeeplyNestedLevel20 Item20 { get; set; } +} + +internal class DeeplyNestedLevel20 +{ + public DeeplyNestedLevel21 Item21 { get; set; } +} + +internal class DeeplyNestedLevel21 +{ + public DeeplyNestedLevel22 Item22 { get; set; } +} + +internal class DeeplyNestedLevel22 +{ + public DeeplyNestedLevel23 Item23 { get; set; } +} + +internal class DeeplyNestedLevel23 +{ + public DeeplyNestedLevel24 Item24 { get; set; } +} + +internal class DeeplyNestedLevel24 +{ + public DeeplyNestedLevel25 Item25 { get; set; } +} + +internal class DeeplyNestedLevel25 +{ + public DeeplyNestedLevel26 Item26 { get; set; } +} + +internal class DeeplyNestedLevel26 +{ + public DeeplyNestedLevel27 Item27 { get; set; } +} + +internal class DeeplyNestedLevel27 +{ + public DeeplyNestedLevel28 Item28 { get; set; } +} + +internal class DeeplyNestedLevel28 +{ + public DeeplyNestedLevel29 Item29 { get; set; } +} + +internal class DeeplyNestedLevel29 +{ + public DeeplyNestedLevel30 Item30 { get; set; } +} + +internal class DeeplyNestedLevel30 +{ + public DeeplyNestedLevel31 Item31 { get; set; } +} + +internal class DeeplyNestedLevel31 +{ + public DeeplyNestedLevel32 Item32 { get; set; } +} + +internal class DeeplyNestedLevel32 +{ + public DeeplyNestedLevel33 Item33 { get; set; } +} + +internal class DeeplyNestedLevel33 +{ + public DeeplyNestedLevel34 Item34 { get; set; } +} + +internal class DeeplyNestedLevel34 +{ + public DeeplyNestedLevel35 Item35 { get; set; } +} + +internal class DeeplyNestedLevel35 +{ + public DeeplyNestedLevel36 Item36 { get; set; } +} + +internal class DeeplyNestedLevel36 +{ +} diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs index 17c7e5e3e9c1..eecb520c1bb0 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OpenApi; +using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; @@ -359,4 +360,94 @@ private class TodoListContainer { public ICollection Todos { get; set; } = []; } + + [Fact] + public async Task SupportsRefMappingInDeeplyNestedTypes() + { + // Arrange + var builder = CreateBuilder(); + + builder.MapPost("/", (Level1 item) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var operation = document.Paths["/"].Operations[OperationType.Post]; + var requestSchema = operation.RequestBody.Content["application/json"].Schema; + + // Assert $ref used for top-level + Assert.Equal("Level1", requestSchema.Reference.Id); + + // Assert that $ref is used for Level1.Item2 + var level1Schema = requestSchema.GetEffective(document); + Assert.Equal("Level2", level1Schema.Properties["item2"].Reference.Id); + + // Assert that $ref is used for Level2.Item3 + var level2Schema = level1Schema.Properties["item2"].GetEffective(document); + Assert.Equal("Level3", level2Schema.Properties["item3"].Reference.Id); + + // Assert that no $ref is used for string property + var level3Schema = level2Schema.Properties["item3"].GetEffective(document); + Assert.Null(level3Schema.Properties["terminate"].Reference); + }); + } + + private class Level1 + { + public Level2 Item2 { get; set; } + } + + private class Level2 + { + public Level3 Item3 { get; set; } + } + + private class Level3 + { + public string Terminate { get; set; } + } + + [Fact] + public async Task ThrowsForOverlyNestedSchemas() + { + // Arrange + var builder = CreateBuilder(); + + builder.MapPost("/", (DeeplyNestedLevel1 item) => { }); + + // Act & Assert + var exception = await Assert.ThrowsAsync(() => VerifyOpenApiDocument(builder, _ => { })); + Assert.Equal("The depth of the generated JSON schema exceeds the JsonSerializerOptions.MaxDepth setting.", exception.Message); + } + + [Fact] + public async Task SupportsDeeplyNestedSchemaWithConfiguredMaxDepth() + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.ConfigureHttpJsonOptions(options => + { + options.SerializerOptions.MaxDepth = 124; + }); + var builder = CreateBuilder(serviceCollection); + + builder.MapPost("/", (DeeplyNestedLevel1 item) => { }); + + // Act & Assert + await VerifyOpenApiDocument(builder, document => + { + var operation = document.Paths["/"].Operations[OperationType.Post]; + var requestSchema = operation.RequestBody.Content["application/json"].Schema; + + // Assert $ref used for top-level + Assert.Equal("DeeplyNestedLevel1", requestSchema.Reference.Id); + + // Assert that $ref is used for all nested levels + var levelSchema = requestSchema.GetEffective(document); + for (var level = 2; level < 36; level++) + { + Assert.Equal($"DeeplyNestedLevel{level}", levelSchema.Properties[$"item{level}"].Reference.Id); + levelSchema = levelSchema.Properties[$"item{level}"].GetEffective(document); + } + }); + } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/SchemaTransformerTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/SchemaTransformerTests.cs index 7c23c3b08d48..5d20c810d6fa 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/SchemaTransformerTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/SchemaTransformerTests.cs @@ -531,14 +531,14 @@ await VerifyOpenApiDocument(builder, options, document => var path = document.Paths["/list-of-todo"]; var getOperation = path.Operations[OperationType.Get]; var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema; - var itemSchema = responseSchema.GetEffective(document).Items; + var itemSchema = responseSchema.GetEffective(document).Items.GetEffective(document); Assert.Equal("modified-number-format", itemSchema.Properties["id"].Format); // Assert that the integer type within the list has been updated var otherPath = document.Paths["/list-of-int"]; var otherGetOperation = otherPath.Operations[OperationType.Get]; var otherResponseSchema = otherGetOperation.Responses["200"].Content["application/json"].Schema; - var otherItemSchema = otherResponseSchema.GetEffective(document).Items; + var otherItemSchema = otherResponseSchema.GetEffective(document).Items.GetEffective(document); Assert.Equal("modified-number-format", otherItemSchema.Format); }); } @@ -611,7 +611,7 @@ await VerifyOpenApiDocument(builder, options, document => var path = document.Paths["/list"]; var getOperation = path.Operations[OperationType.Get]; var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema; - var someShapeSchema = responseSchema.GetEffective(document).Properties["someShape"]; + var someShapeSchema = responseSchema.GetEffective(document).Properties["someShape"].GetEffective(document); var triangleSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => s.Reference.Id == "ShapeTriangle")); // Assert that the x-my-extension type is set to this-is-a-triangle Assert.True(triangleSubschema.GetEffective(document).Extensions.TryGetValue("x-my-extension", out var triangleExtension)); @@ -652,7 +652,7 @@ await VerifyOpenApiDocument(builder, options, document => var path = document.Paths["/list"]; var getOperation = path.Operations[OperationType.Get]; var responseSchema = getOperation.Responses["200"].Content["application/json"].Schema; - var someShapeSchema = responseSchema.GetEffective(document).Items.GetEffective(document).Properties["someShape"]; + var someShapeSchema = responseSchema.GetEffective(document).Items.GetEffective(document).Properties["someShape"].GetEffective(document); var triangleSubschema = Assert.Single(someShapeSchema.AnyOf.Where(s => s.Reference.Id == "ShapeTriangle")); // Assert that the x-my-extension type is set to this-is-a-triangle Assert.True(triangleSubschema.GetEffective(document).Extensions.TryGetValue("x-my-extension", out var triangleExtension));