Skip to content

Commit 48b59c3

Browse files
committed
Apply schema transformers on properties and other subschemas
1 parent 1313a4d commit 48b59c3

File tree

6 files changed

+331
-17
lines changed

6 files changed

+331
-17
lines changed

src/OpenApi/perf/Microbenchmarks/TransformersBenchmark.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void SchemaTransformer_Setup()
7373
{
7474
_options.UseSchemaTransformer((schema, context, token) =>
7575
{
76-
if (context.Type == typeof(Todo) && context.ParameterDescription != null)
76+
if (context.JsonTypeInfo.Type == typeof(Todo) && context.ParameterDescription != null)
7777
{
7878
schema.Extensions["x-my-extension"] = new OpenApiString(context.ParameterDescription.Name);
7979
}

src/OpenApi/src/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.ApplicationServices
2020
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.ApplicationServices.init -> void
2121
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.DocumentName.get -> string!
2222
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.DocumentName.init -> void
23+
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.JsonPropertyInfo.get -> System.Text.Json.Serialization.Metadata.JsonPropertyInfo?
24+
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.JsonPropertyInfo.init -> void
25+
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.JsonTypeInfo.get -> System.Text.Json.Serialization.Metadata.JsonTypeInfo!
26+
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.JsonTypeInfo.init -> void
2327
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.OpenApiSchemaTransformerContext() -> void
2428
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.ParameterDescription.get -> Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription?
2529
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.ParameterDescription.init -> void
26-
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.Type.get -> System.Type!
27-
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.Type.init -> void
2830
Microsoft.Extensions.DependencyInjection.OpenApiServiceCollectionExtensions
2931
static Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.MapOpenApi(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern = "/openapi/{documentName}.json") -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
3032
static Microsoft.Extensions.DependencyInjection.OpenApiServiceCollectionExtensions.AddOpenApi(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!

src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,17 +140,58 @@ internal async Task<OpenApiSchema> GetOrCreateSchemaAsync(Type type, ApiParamete
140140

141141
internal async Task ApplySchemaTransformersAsync(OpenApiSchema schema, Type type, ApiParameterDescription? parameterDescription = null, CancellationToken cancellationToken = default)
142142
{
143+
var jsonTypeInfo = _jsonSerializerOptions.GetTypeInfo(type);
143144
var context = new OpenApiSchemaTransformerContext
144145
{
145146
DocumentName = documentName,
146-
Type = type,
147+
JsonTypeInfo = jsonTypeInfo,
148+
JsonPropertyInfo = null,
147149
ParameterDescription = parameterDescription,
148150
ApplicationServices = serviceProvider
149151
};
150152
for (var i = 0; i < _openApiOptions.SchemaTransformers.Count; i++)
151153
{
152154
var transformer = _openApiOptions.SchemaTransformers[i];
153-
await transformer(schema, context, cancellationToken);
155+
await InnerApplySchemaTransformersAsync(schema, jsonTypeInfo, context, transformer, cancellationToken);
156+
}
157+
}
158+
159+
private async Task InnerApplySchemaTransformersAsync(OpenApiSchema schema,
160+
JsonTypeInfo jsonTypeInfo,
161+
OpenApiSchemaTransformerContext context,
162+
Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task> transformer,
163+
CancellationToken cancellationToken = default)
164+
{
165+
await transformer(schema, context, cancellationToken);
166+
167+
// Only apply transformers on polymorphic schemas where we can resolve the derived
168+
// types associated with the base type.
169+
if (schema.AnyOf is { Count: > 0 } && jsonTypeInfo.PolymorphismOptions is not null)
170+
{
171+
var anyOfIndex = 0;
172+
foreach (var derivedType in jsonTypeInfo.PolymorphismOptions.DerivedTypes)
173+
{
174+
var derivedJsonTypeInfo = _jsonSerializerOptions.GetTypeInfo(derivedType.DerivedType);
175+
context.UpdateJsonTypeInfo(derivedJsonTypeInfo, null);
176+
await InnerApplySchemaTransformersAsync(schema.AnyOf[anyOfIndex], derivedJsonTypeInfo, context, transformer, cancellationToken);
177+
anyOfIndex++;
178+
}
179+
}
180+
181+
if (schema.Items is not null)
182+
{
183+
var elementTypeInfo = _jsonSerializerOptions.GetTypeInfo(jsonTypeInfo.ElementType!);
184+
context.UpdateJsonTypeInfo(elementTypeInfo, null);
185+
await InnerApplySchemaTransformersAsync(schema.Items, elementTypeInfo, context, transformer, cancellationToken);
186+
}
187+
188+
if (schema.Properties is { Count: > 0 })
189+
{
190+
foreach (var propertyInfo in jsonTypeInfo.Properties)
191+
{
192+
context.UpdateJsonTypeInfo(_jsonSerializerOptions.GetTypeInfo(propertyInfo.PropertyType), propertyInfo);
193+
await InnerApplySchemaTransformersAsync(schema.Properties[propertyInfo.Name], jsonTypeInfo, context, transformer, cancellationToken);
194+
}
154195
}
155196
}
156197

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text.Json.Serialization.Metadata;
45
using Microsoft.AspNetCore.Mvc.ApiExplorer;
5-
using Microsoft.OpenApi.Models;
66

77
namespace Microsoft.AspNetCore.OpenApi;
88

@@ -11,24 +11,41 @@ namespace Microsoft.AspNetCore.OpenApi;
1111
/// </summary>
1212
public sealed class OpenApiSchemaTransformerContext
1313
{
14+
private JsonTypeInfo? _jsonTypeInfo;
15+
private JsonPropertyInfo? _jsonPropertyInfo;
16+
1417
/// <summary>
1518
/// Gets the name of the associated OpenAPI document.
1619
/// </summary>
1720
public required string DocumentName { get; init; }
1821

19-
/// <summary>
20-
/// Gets the <see cref="Type" /> associated with the current <see cref="OpenApiSchema"/>.
21-
/// </summary>
22-
public required Type Type { get; init; }
23-
2422
/// <summary>
2523
/// Gets the <see cref="ApiParameterDescription"/> associated with the target schema.
2624
/// Null when processing an OpenAPI schema for a response type.
2725
/// </summary>
2826
public required ApiParameterDescription? ParameterDescription { get; init; }
2927

28+
/// <summary>
29+
/// Gets the <see cref="JsonTypeInfo"/> associated with the target schema.
30+
/// </summary>
31+
public required JsonTypeInfo JsonTypeInfo { get => _jsonTypeInfo!; init => _jsonTypeInfo = value; }
32+
33+
/// <summary>
34+
/// Gets the <see cref="JsonPropertyInfo"/> associated with the target schema if the
35+
/// target schema is a property of a parent schema.
36+
/// </summary>
37+
public required JsonPropertyInfo? JsonPropertyInfo { get => _jsonPropertyInfo; init => _jsonPropertyInfo = value; }
38+
3039
/// <summary>
3140
/// Gets the application services associated with the current document the target schema is in.
3241
/// </summary>
3342
public required IServiceProvider ApplicationServices { get; init; }
43+
44+
// Expose internal setters for the properties that only allow initializations to avoid allocating
45+
// new instances of the context for each sub-schema transformation.
46+
internal void UpdateJsonTypeInfo(JsonTypeInfo jsonTypeInfo, JsonPropertyInfo? jsonPropertyInfo)
47+
{
48+
_jsonTypeInfo = jsonTypeInfo;
49+
_jsonPropertyInfo = jsonPropertyInfo;
50+
}
3451
}

src/OpenApi/test/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ public async Task TypeModifiedWithSchemaTransformerMapsToDifferentReferenceId()
269269
var options = new OpenApiOptions();
270270
options.UseSchemaTransformer((schema, context, cancellationToken) =>
271271
{
272-
if (context.Type == typeof(Todo) && context.ParameterDescription is not null)
272+
if (context.JsonTypeInfo.Type == typeof(Todo) && context.ParameterDescription is not null)
273273
{
274274
schema.Extensions["x-my-extension"] = new OpenApiString(context.ParameterDescription.Name);
275275
}

0 commit comments

Comments
 (0)