Support OpenAPI polymorphic output with JsonDerivedType (#16144)

This commit is contained in:
Kenn Jacobsen
2024-04-25 08:26:44 +02:00
committed by GitHub
parent fc9b47a2ea
commit c4958365fe
5 changed files with 36 additions and 4 deletions

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// Marker interface that ensure the type have a "$type" discriminator in the open api schema.

View File

@@ -5,4 +5,6 @@ namespace Umbraco.Cms.Api.Common.Serialization;
public interface IUmbracoJsonTypeInfoResolver : IJsonTypeInfoResolver
{
IEnumerable<Type> FindSubTypes(Type type);
string? GetTypeDiscriminatorValue(Type type);
}

View File

@@ -1,7 +1,10 @@
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.Composing;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.Serialization;
@@ -15,6 +18,14 @@ public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, I
public IEnumerable<Type> FindSubTypes(Type type)
{
JsonDerivedTypeAttribute[] explicitJsonDerivedTypes = type
.GetCustomAttributes<JsonDerivedTypeAttribute>(false)
.ToArray();
if (explicitJsonDerivedTypes.Any())
{
return explicitJsonDerivedTypes.Select(a => a.DerivedType);
}
if (type.IsInterface is false)
{
// IMPORTANT: do NOT return an empty enumerable here. it will cause nullability to fail on reference
@@ -33,6 +44,24 @@ public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, I
return result;
}
public string? GetTypeDiscriminatorValue(Type type)
{
JsonDerivedTypeAttribute? jsonDerivedTypeAttribute = type
.GetBaseTypes(false)
.WhereNotNull()
.SelectMany(baseType => baseType.GetCustomAttributes<JsonDerivedTypeAttribute>(false))
.FirstOrDefault(attr => attr.DerivedType == type);
if (jsonDerivedTypeAttribute is not null)
{
// IMPORTANT: do NOT perform fallback to type.Name here - it will work for the schema generation,
// but not for the actual serialization, and then it's only going to cause confusion.
return jsonDerivedTypeAttribute.TypeDiscriminator?.ToString();
}
return typeof(IOpenApiDiscriminator).IsAssignableFrom(type) ? type.Name : null;
}
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo result = base.GetTypeInfo(type, options);

View File

@@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Api.Management.OpenApi;
@@ -34,8 +35,8 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<
swaggerGenOptions.UseOneOfForPolymorphism();
// Ensure all types that implements the IOpenApiDiscriminator have a $type property in the OpenApi schema with the default value (The class name) that is expected by the server
swaggerGenOptions.SelectDiscriminatorNameUsing(type => typeof(IOpenApiDiscriminator).IsAssignableFrom(type) ? "$type" : null);
swaggerGenOptions.SelectDiscriminatorValueUsing(type => typeof(IOpenApiDiscriminator).IsAssignableFrom(type) ? type.Name : null);
swaggerGenOptions.SelectDiscriminatorNameUsing(type => _umbracoJsonTypeInfoResolver.GetTypeDiscriminatorValue(type) is not null ? "$type" : null);
swaggerGenOptions.SelectDiscriminatorValueUsing(_umbracoJsonTypeInfoResolver.GetTypeDiscriminatorValue);
swaggerGenOptions.AddSecurityDefinition(

View File

@@ -1,4 +1,4 @@
using Umbraco.Cms.Api.Management.OpenApi;
using Umbraco.Cms.Api.Common.OpenApi;
namespace Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;