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

Swagger sub types selectors (take 2) #17132

Merged
merged 7 commits into from
Sep 30, 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
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,22 +15,23 @@ 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.")]
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
public ConfigureUmbracoSwaggerGenOptions(
IOptions<ApiVersioningOptions> apiVersioningOptions,
IOperationIdSelector operationIdSelector,
ISchemaIdSelector schemaIdSelector)
: this(operationIdSelector, schemaIdSelector)
{
}
: this(operationIdSelector, schemaIdSelector, StaticServiceProvider.Instance.GetRequiredService<ISubTypesSelector>())
{ }

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

public void Configure(SwaggerGenOptions swaggerGenOptions)
Expand Down Expand Up @@ -62,6 +64,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);
}
20 changes: 20 additions & 0 deletions src/Umbraco.Cms.Api.Common/OpenApi/SubTypesHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Umbraco.Cms.Api.Common.Serialization;

namespace Umbraco.Cms.Api.Common.OpenApi;

public class SubTypesHandler : ISubTypesHandler
{
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;

public SubTypesHandler(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
=> _umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;

protected 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);
}
60 changes: 60 additions & 0 deletions src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Extensions;

namespace Umbraco.Cms.Api.Common.OpenApi;

public class SubTypesSelector : ISubTypesSelector
{
private readonly IOptions<GlobalSettings> _settings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IEnumerable<ISubTypesHandler> _subTypeHandlers;
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;

public SubTypesSelector(
IOptions<GlobalSettings> settings,
IHostingEnvironment hostingEnvironment,
IHttpContextAccessor httpContextAccessor,
IEnumerable<ISubTypesHandler> subTypeHandlers,
IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
{
_settings = settings;
_hostingEnvironment = hostingEnvironment;
_httpContextAccessor = httpContextAccessor;
_subTypeHandlers = subTypeHandlers;
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
}

public IEnumerable<Type> SubTypes(Type type)
{
var backOfficePath = _settings.Value.GetBackOfficePath(_hostingEnvironment);
var swaggerPath = $"{backOfficePath}/swagger";

if (_httpContextAccessor.HttpContext?.Request.Path.StartsWithSegments(swaggerPath) ?? false)
{
// Split the path into segments
var segments = _httpContextAccessor.HttpContext.Request.Path.Value!
.Substring(swaggerPath.Length)
.TrimStart(Constants.CharArrays.ForwardSlash)
.Split(Constants.CharArrays.ForwardSlash);

// 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