Skip to content
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

v15: Swagger sub types selectors #17072

Open
wants to merge 8 commits into
base: contrib
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Extensions;

namespace Umbraco.Cms.Api.Common.Configuration;
Expand All @@ -14,6 +15,7 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOpt
{
private readonly IOperationIdSelector _operationIdSelector;
private readonly ISchemaIdSelector _schemaIdSelector;
private readonly ISubTypesSelector _subTypesSelector;

[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
public ConfigureUmbracoSwaggerGenOptions(
Expand All @@ -24,12 +26,21 @@ public ConfigureUmbracoSwaggerGenOptions(
{
}

[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
public ConfigureUmbracoSwaggerGenOptions(
IOperationIdSelector operationIdSelector,
ISchemaIdSelector schemaIdSelector)
: this(operationIdSelector, schemaIdSelector, StaticServiceProvider.Instance.GetRequiredService<ISubTypesSelector>())
{ }

public ConfigureUmbracoSwaggerGenOptions(
IOperationIdSelector operationIdSelector,
ISchemaIdSelector schemaIdSelector,
ISubTypesSelector subTypesSelector)
{
_operationIdSelector = operationIdSelector;
_schemaIdSelector = schemaIdSelector;
_subTypesSelector = subTypesSelector;
}

public void Configure(SwaggerGenOptions swaggerGenOptions)
Expand Down Expand Up @@ -62,6 +73,7 @@ public void Configure(SwaggerGenOptions swaggerGenOptions)
swaggerGenOptions.OrderActionsBy(ActionOrderBy);
swaggerGenOptions.SchemaFilter<EnumSchemaFilter>();
swaggerGenOptions.CustomSchemaIds(_schemaIdSelector.SchemaId);
swaggerGenOptions.SelectSubTypesUsing(_subTypesSelector.SubTypes);
swaggerGenOptions.SupportNonNullableReferenceTypes();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public static IUmbracoBuilder AddUmbracoApiOpenApiUI(this IUmbracoBuilder builde
builder.Services.AddSingleton<IOperationIdHandler, OperationIdHandler>();
builder.Services.AddSingleton<ISchemaIdSelector, SchemaIdSelector>();
builder.Services.AddSingleton<ISchemaIdHandler, SchemaIdHandler>();
builder.Services.AddSingleton<ISubTypesSelector, SubTypesSelector>();
builder.Services.AddSingleton<ISubTypesHandler, SubTypesHandler>();
builder.Services.Configure<UmbracoPipelineOptions>(options => options.AddFilter(new SwaggerRouteTemplatePipelineFilter("UmbracoApiCommon")));

return builder;
Expand Down
8 changes: 8 additions & 0 deletions src/Umbraco.Cms.Api.Common/OpenApi/ISubTypesHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Umbraco.Cms.Api.Common.OpenApi;

public interface ISubTypesHandler
{
bool CanHandle(Type type, string documentName);

IEnumerable<Type> Handle(Type type);
}
6 changes: 6 additions & 0 deletions src/Umbraco.Cms.Api.Common/OpenApi/ISubTypesSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Common.OpenApi;

public interface ISubTypesSelector
{
IEnumerable<Type> SubTypes(Type type);
}
15 changes: 15 additions & 0 deletions src/Umbraco.Cms.Api.Common/OpenApi/SubTypesHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Umbraco.Cms.Api.Common.Serialization;

namespace Umbraco.Cms.Api.Common.OpenApi;

public class SubTypesHandler(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver) : ISubTypesHandler
{
public virtual bool CanHandle(Type type)
=> type.Namespace?.StartsWith("Umbraco.Cms") is true;

public virtual bool CanHandle(Type type, string documentName)
=> CanHandle(type);

public virtual IEnumerable<Type> Handle(Type type)
=> umbracoJsonTypeInfoResolver.FindSubTypes(type);
}
38 changes: 38 additions & 0 deletions src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Extensions;

namespace Umbraco.Cms.Api.Common.OpenApi;

public class SubTypesSelector(IOptions<GlobalSettings> settings,
IHostingEnvironment hostingEnvironment,
IHttpContextAccessor httpContextAccessor,
IEnumerable<ISubTypesHandler> subTypeHandlers,
IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver) : ISubTypesSelector
{
public IEnumerable<Type> SubTypes(Type type)
{
var backOfficePath = settings.Value.GetBackOfficePath(hostingEnvironment);
if (httpContextAccessor.HttpContext?.Request.Path.StartsWithSegments($"{backOfficePath}/swagger/") ?? false)
{
// Split the path into segments
var segments = httpContextAccessor.HttpContext.Request.Path.Value!.TrimStart($"{backOfficePath}/swagger/").Split('/');

// Extract the document name from the path
var documentName = segments[0];

// Find the first handler that can handle the type / document name combination
ISubTypesHandler? handler = subTypeHandlers.FirstOrDefault(h => h.CanHandle(type, documentName));
if (handler != null)
{
return handler.Handle(type);
}
}

// Default implementation to maintain backwards compatibility
return umbracoJsonTypeInfoResolver.FindSubTypes(type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public void Configure(SwaggerGenOptions swaggerGenOptions)
});

swaggerGenOptions.OperationFilter<ResponseHeaderOperationFilter>();
swaggerGenOptions.SelectSubTypesUsing(_umbracoJsonTypeInfoResolver.FindSubTypes);
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
Expand Down
Loading