diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/IOpenApiDiscriminator.cs b/src/Umbraco.Cms.Api.Common/OpenApi/IOpenApiDiscriminator.cs similarity index 87% rename from src/Umbraco.Cms.Api.Management/OpenApi/IOpenApiDiscriminator.cs rename to src/Umbraco.Cms.Api.Common/OpenApi/IOpenApiDiscriminator.cs index 6bf39b6c92cf..f60ad54c278b 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi/IOpenApiDiscriminator.cs +++ b/src/Umbraco.Cms.Api.Common/OpenApi/IOpenApiDiscriminator.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Api.Management.OpenApi; +namespace Umbraco.Cms.Api.Common.OpenApi; /// /// Marker interface that ensure the type have a "$type" discriminator in the open api schema. diff --git a/src/Umbraco.Cms.Api.Common/Serialization/IUmbracoJsonTypeInfoResolver.cs b/src/Umbraco.Cms.Api.Common/Serialization/IUmbracoJsonTypeInfoResolver.cs index 009f05e4e485..fe47f4ebeeac 100644 --- a/src/Umbraco.Cms.Api.Common/Serialization/IUmbracoJsonTypeInfoResolver.cs +++ b/src/Umbraco.Cms.Api.Common/Serialization/IUmbracoJsonTypeInfoResolver.cs @@ -5,4 +5,6 @@ namespace Umbraco.Cms.Api.Common.Serialization; public interface IUmbracoJsonTypeInfoResolver : IJsonTypeInfoResolver { IEnumerable FindSubTypes(Type type); + + string? GetTypeDiscriminatorValue(Type type); } diff --git a/src/Umbraco.Cms.Api.Common/Serialization/UmbracoJsonTypeInfoResolver.cs b/src/Umbraco.Cms.Api.Common/Serialization/UmbracoJsonTypeInfoResolver.cs index 5fc8bcf5f614..38d6df93e592 100644 --- a/src/Umbraco.Cms.Api.Common/Serialization/UmbracoJsonTypeInfoResolver.cs +++ b/src/Umbraco.Cms.Api.Common/Serialization/UmbracoJsonTypeInfoResolver.cs @@ -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 UmbracoJsonTypeInfoResolver(ITypeFinder typeFinder) public IEnumerable FindSubTypes(Type type) { + JsonDerivedTypeAttribute[] explicitJsonDerivedTypes = type + .GetCustomAttributes(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 IEnumerable FindSubTypes(Type type) return result; } + public string? GetTypeDiscriminatorValue(Type type) + { + JsonDerivedTypeAttribute? jsonDerivedTypeAttribute = type + .GetBaseTypes(false) + .WhereNotNull() + .SelectMany(baseType => baseType.GetCustomAttributes(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); diff --git a/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs index 7281b86b7b2c..6b67c805af25 100644 --- a/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs +++ b/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs @@ -2,6 +2,7 @@ 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 void Configure(SwaggerGenOptions swaggerGenOptions) 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( diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/UserGroup/Permissions/IPermissionPresentationModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroup/Permissions/IPermissionPresentationModel.cs index 5bbeeae9b378..8f3ca6377272 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/UserGroup/Permissions/IPermissionPresentationModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroup/Permissions/IPermissionPresentationModel.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Api.Management.OpenApi; +using Umbraco.Cms.Api.Common.OpenApi; namespace Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;