From 553133ba6a5b2aca1edc56b1887b58f942eb677b Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 19 Jul 2024 15:54:51 -0700 Subject: [PATCH 1/2] Reset OpenApiSchemaTransformerContext before each invocation --- .../Services/Schemas/OpenApiSchemaService.cs | 2 ++ .../Transformers/SchemaTransformerTests.cs | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs index 22a6691b3b04..1949a9af25f8 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs @@ -155,6 +155,8 @@ internal async Task ApplySchemaTransformersAsync(OpenApiSchema schema, Type type }; for (var i = 0; i < _openApiOptions.SchemaTransformers.Count; i++) { + // Reset context object to base state before running each transformer. + context.UpdateJsonTypeInfo(jsonTypeInfo, null); var transformer = _openApiOptions.SchemaTransformers[i]; // If the transformer is a type-based transformer, we need to initialize and finalize it // once in the context of the top-level assembly and not the child properties we are invoking diff --git a/src/OpenApi/test/Transformers/SchemaTransformerTests.cs b/src/OpenApi/test/Transformers/SchemaTransformerTests.cs index ece10d596de6..e990fb7da31a 100644 --- a/src/OpenApi/test/Transformers/SchemaTransformerTests.cs +++ b/src/OpenApi/test/Transformers/SchemaTransformerTests.cs @@ -19,7 +19,28 @@ public async Task SchemaTransformer_CanAccessTypeAndParameterDescriptionForParam builder.MapPost("/todo", (Todo todo) => { }); var options = new OpenApiOptions(); + var firstInvocationOnSecondTransformer = true; options.AddSchemaTransformer((schema, context, cancellationToken) => + { + ValidateContext(context); + return Task.CompletedTask; + }) + .AddSchemaTransformer((schema, context, cancellationToken) => + { + // Coverage for https://github.com/dotnet/aspnetcore/issues/56899 + if (firstInvocationOnSecondTransformer) + { + Assert.Equal(typeof(Todo), context.JsonTypeInfo.Type); + firstInvocationOnSecondTransformer = false; + } + // Rest of the state is still consistent + ValidateContext(context); + return Task.CompletedTask; + }); + + await VerifyOpenApiDocument(builder, options, document => { }); + + static void ValidateContext(OpenApiSchemaTransformerContext context) { if (context.JsonPropertyInfo == null) { @@ -42,10 +63,7 @@ public async Task SchemaTransformer_CanAccessTypeAndParameterDescriptionForParam { Assert.Equal(typeof(DateTime), context.JsonTypeInfo.Type); } - return Task.CompletedTask; - }); - - await VerifyOpenApiDocument(builder, options, document => { }); + } } [Fact] From 4c7f1727efb3ebbe2fa37fa52e705915e97ba350 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 19 Jul 2024 20:24:37 -0700 Subject: [PATCH 2/2] Only call UpdateJsonTypeInfo once --- .../src/Services/Schemas/OpenApiSchemaService.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs index 1949a9af25f8..d32a9439b8f8 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs @@ -156,7 +156,6 @@ internal async Task ApplySchemaTransformersAsync(OpenApiSchema schema, Type type for (var i = 0; i < _openApiOptions.SchemaTransformers.Count; i++) { // Reset context object to base state before running each transformer. - context.UpdateJsonTypeInfo(jsonTypeInfo, null); var transformer = _openApiOptions.SchemaTransformers[i]; // If the transformer is a type-based transformer, we need to initialize and finalize it // once in the context of the top-level assembly and not the child properties we are invoking @@ -166,7 +165,7 @@ internal async Task ApplySchemaTransformersAsync(OpenApiSchema schema, Type type var initializedTransformer = typeBasedTransformer.InitializeTransformer(serviceProvider); try { - await InnerApplySchemaTransformersAsync(schema, jsonTypeInfo, context, initializedTransformer, cancellationToken); + await InnerApplySchemaTransformersAsync(schema, jsonTypeInfo, null, context, initializedTransformer, cancellationToken); } finally { @@ -175,17 +174,19 @@ internal async Task ApplySchemaTransformersAsync(OpenApiSchema schema, Type type } else { - await InnerApplySchemaTransformersAsync(schema, jsonTypeInfo, context, transformer, cancellationToken); + await InnerApplySchemaTransformersAsync(schema, jsonTypeInfo, null, context, transformer, cancellationToken); } } } private async Task InnerApplySchemaTransformersAsync(OpenApiSchema schema, JsonTypeInfo jsonTypeInfo, + JsonPropertyInfo? jsonPropertyInfo, OpenApiSchemaTransformerContext context, IOpenApiSchemaTransformer transformer, CancellationToken cancellationToken = default) { + context.UpdateJsonTypeInfo(jsonTypeInfo, jsonPropertyInfo); await transformer.TransformAsync(schema, context, cancellationToken); // Only apply transformers on polymorphic schemas where we can resolve the derived @@ -196,12 +197,11 @@ private async Task InnerApplySchemaTransformersAsync(OpenApiSchema schema, foreach (var derivedType in jsonTypeInfo.PolymorphismOptions.DerivedTypes) { var derivedJsonTypeInfo = _jsonSerializerOptions.GetTypeInfo(derivedType.DerivedType); - context.UpdateJsonTypeInfo(derivedJsonTypeInfo, null); if (schema.AnyOf.Count <= anyOfIndex) { break; } - await InnerApplySchemaTransformersAsync(schema.AnyOf[anyOfIndex], derivedJsonTypeInfo, context, transformer, cancellationToken); + await InnerApplySchemaTransformersAsync(schema.AnyOf[anyOfIndex], derivedJsonTypeInfo, null, context, transformer, cancellationToken); anyOfIndex++; } } @@ -209,18 +209,16 @@ private async Task InnerApplySchemaTransformersAsync(OpenApiSchema schema, if (schema.Items is not null) { var elementTypeInfo = _jsonSerializerOptions.GetTypeInfo(jsonTypeInfo.ElementType!); - context.UpdateJsonTypeInfo(elementTypeInfo, null); - await InnerApplySchemaTransformersAsync(schema.Items, elementTypeInfo, context, transformer, cancellationToken); + await InnerApplySchemaTransformersAsync(schema.Items, elementTypeInfo, null, context, transformer, cancellationToken); } if (schema.Properties is { Count: > 0 }) { foreach (var propertyInfo in jsonTypeInfo.Properties) { - context.UpdateJsonTypeInfo(_jsonSerializerOptions.GetTypeInfo(propertyInfo.PropertyType), propertyInfo); if (schema.Properties.TryGetValue(propertyInfo.Name, out var propertySchema)) { - await InnerApplySchemaTransformersAsync(propertySchema, _jsonSerializerOptions.GetTypeInfo(propertyInfo.PropertyType), context, transformer, cancellationToken); + await InnerApplySchemaTransformersAsync(propertySchema, _jsonSerializerOptions.GetTypeInfo(propertyInfo.PropertyType), propertyInfo, context, transformer, cancellationToken); } } }