Skip to content

Add support for CreateSchemaReferenceId option #56753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ internal static void ApplyDefaultValue(this JsonNode schema, object? defaultValu
/// </remarks>
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
/// <param name="context">The <see cref="JsonSchemaExporterContext"/> associated with the <see paramref="schema"/>.</param>
internal static void ApplyPrimitiveTypesAndFormats(this JsonNode schema, JsonSchemaExporterContext context)
/// <param name="createSchemaReferenceId">A delegate that generates the reference ID to create for a type.</param>
internal static void ApplyPrimitiveTypesAndFormats(this JsonNode schema, JsonSchemaExporterContext context, Func<JsonTypeInfo, string?> createSchemaReferenceId)
{
var type = context.TypeInfo.Type;
var underlyingType = Nullable.GetUnderlyingType(type);
Expand All @@ -177,7 +178,7 @@ internal static void ApplyPrimitiveTypesAndFormats(this JsonNode schema, JsonSch
schema[OpenApiSchemaKeywords.NullableKeyword] = openApiSchema.Nullable || (schema[OpenApiSchemaKeywords.TypeKeyword] is JsonArray schemaType && schemaType.GetValues<string>().Contains("null"));
schema[OpenApiSchemaKeywords.TypeKeyword] = openApiSchema.Type;
schema[OpenApiSchemaKeywords.FormatKeyword] = openApiSchema.Format;
schema[OpenApiConstants.SchemaId] = context.TypeInfo.GetSchemaReferenceId();
schema[OpenApiConstants.SchemaId] = createSchemaReferenceId(context.TypeInfo);
schema[OpenApiSchemaKeywords.NullableKeyword] = underlyingType != null;
// Clear out patterns that the underlying JSON schema generator uses to represent
// validations for DateTime, DateTimeOffset, and integers.
Expand Down Expand Up @@ -323,7 +324,8 @@ internal static void ApplyParameterInfo(this JsonNode schema, ApiParameterDescri
/// </summary>
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
/// <param name="context">The <see cref="JsonSchemaExporterContext"/> associated with the current type.</param>
internal static void ApplyPolymorphismOptions(this JsonNode schema, JsonSchemaExporterContext context)
/// <param name="createSchemaReferenceId">A delegate that generates the reference ID to create for a type.</param>
internal static void ApplyPolymorphismOptions(this JsonNode schema, JsonSchemaExporterContext context, Func<JsonTypeInfo, string?> createSchemaReferenceId)
{
// The `context.Path.Length == 0` check is used to ensure that we only apply the polymorphism options
// to the top-level schema and not to any nested schemas that are generated.
Expand All @@ -340,7 +342,7 @@ internal static void ApplyPolymorphismOptions(this JsonNode schema, JsonSchemaEx
// that we hardcode here. We could use `OpenApiReference` to construct the reference and
// serialize it but we use a hardcoded string here to avoid allocating a new object and
// working around Microsoft.OpenApi's serialization libraries.
mappings[$"{discriminator}"] = $"#/components/schemas/{context.TypeInfo.GetSchemaReferenceId()}{jsonDerivedType.GetSchemaReferenceId()}";
mappings[$"{discriminator}"] = $"#/components/schemas/{createSchemaReferenceId(context.TypeInfo)}{createSchemaReferenceId(jsonDerivedType)}";
}
}
schema[OpenApiSchemaKeywords.DiscriminatorKeyword] = polymorphismOptions.TypeDiscriminatorPropertyName;
Expand All @@ -353,9 +355,10 @@ internal static void ApplyPolymorphismOptions(this JsonNode schema, JsonSchemaEx
/// </summary>
/// <param name="schema">The <see cref="JsonNode"/> produced by the underlying schema generator.</param>
/// <param name="context">The <see cref="JsonSchemaExporterContext"/> associated with the current type.</param>
internal static void ApplySchemaReferenceId(this JsonNode schema, JsonSchemaExporterContext context)
/// <param name="createSchemaReferenceId">A delegate that generates the reference ID to create for a type.</param>
internal static void ApplySchemaReferenceId(this JsonNode schema, JsonSchemaExporterContext context, Func<JsonTypeInfo, string?> createSchemaReferenceId)
{
if (context.TypeInfo.GetSchemaReferenceId() is { } schemaReferenceId)
if (createSchemaReferenceId(context.TypeInfo) is { } schemaReferenceId)
{
schema[OpenApiConstants.SchemaId] = schemaReferenceId;
}
Expand Down
3 changes: 3 additions & 0 deletions src/OpenApi/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Microsoft.AspNetCore.OpenApi.IOpenApiDocumentTransformer.TransformAsync(Microsof
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.Description.get -> Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription!
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.Description.init -> void
Microsoft.AspNetCore.OpenApi.OpenApiOptions
Microsoft.AspNetCore.OpenApi.OpenApiOptions.CreateSchemaReferenceId.get -> System.Func<System.Text.Json.Serialization.Metadata.JsonTypeInfo!, string?>!
Microsoft.AspNetCore.OpenApi.OpenApiOptions.CreateSchemaReferenceId.set -> void
Microsoft.AspNetCore.OpenApi.OpenApiOptions.DocumentName.get -> string!
Microsoft.AspNetCore.OpenApi.OpenApiOptions.OpenApiOptions() -> void
Microsoft.AspNetCore.OpenApi.OpenApiOptions.OpenApiVersion.get -> Microsoft.OpenApi.OpenApiSpecVersion
Expand All @@ -27,6 +29,7 @@ Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.Type.get -> System.
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.Type.init -> void
Microsoft.Extensions.DependencyInjection.OpenApiServiceCollectionExtensions
static Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.MapOpenApi(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern = "/openapi/{documentName}.json") -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
static Microsoft.AspNetCore.OpenApi.OpenApiOptions.CreateDefaultSchemaReferenceId(System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo) -> string?
static Microsoft.Extensions.DependencyInjection.OpenApiServiceCollectionExtensions.AddOpenApi(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
static Microsoft.Extensions.DependencyInjection.OpenApiServiceCollectionExtensions.AddOpenApi(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! documentName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
static Microsoft.Extensions.DependencyInjection.OpenApiServiceCollectionExtensions.AddOpenApi(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! documentName, System.Action<Microsoft.AspNetCore.OpenApi.OpenApiOptions!>! configureOptions) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Expand Down
17 changes: 17 additions & 0 deletions src/OpenApi/src/Services/OpenApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Models;
Expand All @@ -16,6 +17,13 @@ public sealed class OpenApiOptions
internal readonly List<IOpenApiDocumentTransformer> DocumentTransformers = [];
internal readonly List<Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task>> SchemaTransformers = [];

/// <summary>
/// A default implementation for creating a schema reference ID for a given <see cref="JsonTypeInfo"/>.
/// </summary>
/// <param name="jsonTypeInfo">The <see cref="JsonTypeInfo"/> associated with the schema we are generating a reference ID for.</param>
/// <returns>The reference ID to use for the schema or <see langword="null"/> if the schema should always be inlined.</returns>
public static string? CreateDefaultSchemaReferenceId(JsonTypeInfo jsonTypeInfo) => jsonTypeInfo.GetSchemaReferenceId();

/// <summary>
/// Initializes a new instance of the <see cref="OpenApiOptions"/> class
/// with the default <see cref="ShouldInclude"/> predicate.
Expand All @@ -40,6 +48,15 @@ public OpenApiOptions()
/// </summary>
public Func<ApiDescription, bool> ShouldInclude { get; set; }

/// <summary>
/// A delegate to determine how reference IDs should be created for schemas associated with types in the given OpenAPI document.
/// </summary>
/// <remarks>
/// The default implementation uses the <see cref="CreateDefaultSchemaReferenceId"/> method to generate reference IDs. When
/// the provided delegate returns <see langword="null"/>, the schema associated with the <see cref="JsonTypeInfo"/> will always be inlined.
/// </remarks>
public Func<JsonTypeInfo, string?> CreateSchemaReferenceId { get; set; } = CreateDefaultSchemaReferenceId;

/// <summary>
/// Registers a new document transformer on the current <see cref="OpenApiOptions"/> instance.
/// </summary>
Expand Down
7 changes: 4 additions & 3 deletions src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ internal sealed class OpenApiSchemaService(
{
schema = new JsonObject();
}
schema.ApplyPrimitiveTypesAndFormats(context);
schema.ApplySchemaReferenceId(context);
schema.ApplyPolymorphismOptions(context);
var createSchemaReferenceId = optionsMonitor.Get(documentName).CreateSchemaReferenceId;
Copy link
Member

Choose a reason for hiding this comment

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

Use _openApiOptions?

Copy link
Member Author

Choose a reason for hiding this comment

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

_openApiOptions is an instance field so we can't use it in the initializer for _confguration. I could switch to standard constructors instead of primary constructors and initialize everything there but.... 😅 🤪

schema.ApplyPrimitiveTypesAndFormats(context, createSchemaReferenceId);
schema.ApplySchemaReferenceId(context, createSchemaReferenceId);
schema.ApplyPolymorphismOptions(context, createSchemaReferenceId);
if (context.PropertyInfo is { } jsonPropertyInfo)
{
// Short-circuit STJ's handling of nested properties, which uses a reference to the
Expand Down
Loading
Loading