diff --git a/Directory.Packages.props b/Directory.Packages.props
index 14ff3fadbe..f71027e1a7 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -80,8 +80,8 @@
-
-
+
+
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 04dd19eda6..8373fd50fd 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -255,6 +255,7 @@ namespace Jellyfin.Server.Extensions
c.AddSwaggerTypeMappings();
c.SchemaFilter();
+ c.SchemaFilter();
c.OperationFilter();
c.OperationFilter();
c.OperationFilter();
@@ -342,25 +343,6 @@ namespace Jellyfin.Server.Extensions
}
});
- /*
- * Support BlurHash dictionary
- */
- options.MapType>>(() =>
- new OpenApiSchema
- {
- Type = "object",
- Properties = typeof(ImageType).GetEnumNames().ToDictionary(
- name => name,
- _ => new OpenApiSchema
- {
- Type = "object",
- AdditionalProperties = new OpenApiSchema
- {
- Type = "string"
- }
- })
- });
-
// Support dictionary with nullable string value.
options.MapType>(() =>
new OpenApiSchema
@@ -373,21 +355,6 @@ namespace Jellyfin.Server.Extensions
}
});
- // Manually describe Flags enum.
- options.MapType(() =>
- new OpenApiSchema
- {
- Type = "array",
- Items = new OpenApiSchema
- {
- Reference = new OpenApiReference
- {
- Id = nameof(TranscodeReason),
- Type = ReferenceType.Schema,
- }
- }
- });
-
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
options.MapType(() => new OpenApiSchema
{
diff --git a/Jellyfin.Server/Filters/AdditionalModelFilter.cs b/Jellyfin.Server/Filters/AdditionalModelFilter.cs
index 58d37db5a5..7407bd2eb7 100644
--- a/Jellyfin.Server/Filters/AdditionalModelFilter.cs
+++ b/Jellyfin.Server/Filters/AdditionalModelFilter.cs
@@ -225,15 +225,6 @@ namespace Jellyfin.Server.Filters
context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository);
}
-
- context.SchemaRepository.AddDefinition(nameof(TranscodeReason), new OpenApiSchema
- {
- Type = "string",
- Enum = Enum.GetNames()
- .Select(e => new OpenApiString(e))
- .Cast()
- .ToArray()
- });
}
}
}
diff --git a/Jellyfin.Server/Filters/CachingOpenApiProvider.cs b/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
index b560ec50ef..833b684444 100644
--- a/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
+++ b/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
@@ -2,6 +2,7 @@ using System;
using AsyncKeyedLock;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Swagger;
@@ -23,6 +24,7 @@ internal sealed class CachingOpenApiProvider : ISwaggerProvider
private readonly IMemoryCache _memoryCache;
private readonly SwaggerGenerator _swaggerGenerator;
private readonly SwaggerGeneratorOptions _swaggerGeneratorOptions;
+ private readonly ILogger _logger;
///
/// Initializes a new instance of the class.
@@ -31,15 +33,18 @@ internal sealed class CachingOpenApiProvider : ISwaggerProvider
/// The api descriptions provider.
/// The schema generator.
/// The memory cache.
+ /// The logger.
public CachingOpenApiProvider(
IOptions optionsAccessor,
IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
ISchemaGenerator schemaGenerator,
- IMemoryCache memoryCache)
+ IMemoryCache memoryCache,
+ ILogger logger)
{
_swaggerGeneratorOptions = optionsAccessor.Value;
_swaggerGenerator = new SwaggerGenerator(_swaggerGeneratorOptions, apiDescriptionsProvider, schemaGenerator);
_memoryCache = memoryCache;
+ _logger = logger;
}
///
@@ -61,7 +66,16 @@ internal sealed class CachingOpenApiProvider : ISwaggerProvider
throw new InvalidOperationException("OpenApi document is generating");
}
+ try
+ {
openApiDocument = _swaggerGenerator.GetSwagger(documentName);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "OpenAPI generation error");
+ throw;
+ }
+
_memoryCache.Set(CacheKey, openApiDocument, _cacheOptions);
return AdjustDocument(openApiDocument, host, basePath);
}
diff --git a/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs b/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs
new file mode 100644
index 0000000000..3e0b69d017
--- /dev/null
+++ b/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs
@@ -0,0 +1,53 @@
+using System;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Jellyfin.Server.Filters;
+
+///
+/// Schema filter to ensure flags enums are represented correctly in OpenAPI.
+///
+///
+/// For flags enums:
+/// - The enum schema definition is set to type "string" (not integer).
+/// - Properties using flags enums are transformed to arrays referencing the enum schema.
+///
+public class FlagsEnumSchemaFilter : ISchemaFilter
+{
+ ///
+ public void Apply(OpenApiSchema schema, SchemaFilterContext context)
+ {
+ var type = context.Type.IsEnum ? context.Type : Nullable.GetUnderlyingType(context.Type);
+ if (type is null || !type.IsEnum)
+ {
+ return;
+ }
+
+ // Check if enum has [Flags] attribute
+ if (!type.IsDefined(typeof(FlagsAttribute), false))
+ {
+ return;
+ }
+
+ if (context.MemberInfo is null)
+ {
+ // Processing the enum definition itself - ensure it's type "string" not "integer"
+ schema.Type = "string";
+ schema.Format = null;
+ }
+ else
+ {
+ // Processing a property that uses the flags enum - transform to array
+ // Generate the enum schema to ensure it exists in the repository
+ var enumSchema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
+
+ // Flags enums should be represented as arrays referencing the enum schema
+ // since multiple values can be combined
+ schema.Type = "array";
+ schema.Format = null;
+ schema.Enum = null;
+ schema.AllOf = null;
+ schema.Items = enumSchema;
+ }
+ }
+}