aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Directory.Packages.props4
-rw-r--r--Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs2
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs21
-rw-r--r--Jellyfin.Server/Filters/AdditionalModelFilter.cs101
-rw-r--r--Jellyfin.Server/Filters/CachingOpenApiProvider.cs4
-rw-r--r--Jellyfin.Server/Filters/FileRequestFilter.cs5
-rw-r--r--Jellyfin.Server/Filters/FileResponseFilter.cs11
-rw-r--r--Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs23
-rw-r--r--Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs17
-rw-r--r--Jellyfin.Server/Filters/ParameterObsoleteFilter.cs12
-rw-r--r--Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs10
-rw-r--r--Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs15
-rw-r--r--Jellyfin.Server/Filters/SecuritySchemeReferenceFixupFilter.cs56
13 files changed, 168 insertions, 113 deletions
diff --git a/Directory.Packages.props b/Directory.Packages.props
index a520b87e2b..cee12a48ae 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -75,8 +75,8 @@
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
- <PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.9.0" />
- <PackageVersion Include="Swashbuckle.AspNetCore" Version="7.3.2" />
+ <PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="10.1.4" />
+ <PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.4" />
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="z440.atl.core" Version="7.11.0" />
diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
index 9fd853cf2e..2aadedfa61 100644
--- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
@@ -3,7 +3,7 @@ using Jellyfin.Api.Middleware;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Builder;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
namespace Jellyfin.Server.Extensions
{
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 9df24fa0d7..c71c193e2e 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Claims;
+using System.Text.Json.Nodes;
using Emby.Server.Implementations;
using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.AnonymousLanAccessPolicy;
@@ -26,7 +26,6 @@ using Jellyfin.Server.Filters;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
@@ -34,9 +33,7 @@ using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Interfaces;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
@@ -208,7 +205,7 @@ namespace Jellyfin.Server.Extensions
{
{
"x-jellyfin-version",
- new OpenApiString(version)
+ new JsonNodeExtension(JsonValue.Create(version))
}
}
});
@@ -262,6 +259,7 @@ namespace Jellyfin.Server.Extensions
c.OperationFilter<FileRequestFilter>();
c.OperationFilter<ParameterObsoleteFilter>();
c.DocumentFilter<AdditionalModelFilter>();
+ c.DocumentFilter<SecuritySchemeReferenceFixupFilter>();
})
.Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
}
@@ -333,10 +331,10 @@ namespace Jellyfin.Server.Extensions
options.MapType<Dictionary<ImageType, string>>(() =>
new OpenApiSchema
{
- Type = "object",
+ Type = JsonSchemaType.Object,
AdditionalProperties = new OpenApiSchema
{
- Type = "string"
+ Type = JsonSchemaType.String
}
});
@@ -344,18 +342,17 @@ namespace Jellyfin.Server.Extensions
options.MapType<Dictionary<string, string?>>(() =>
new OpenApiSchema
{
- Type = "object",
+ Type = JsonSchemaType.Object,
AdditionalProperties = new OpenApiSchema
{
- Type = "string",
- Nullable = true
+ Type = JsonSchemaType.String | JsonSchemaType.Null
}
});
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
options.MapType<Version>(() => new OpenApiSchema
{
- Type = "string"
+ Type = JsonSchemaType.String
});
}
}
diff --git a/Jellyfin.Server/Filters/AdditionalModelFilter.cs b/Jellyfin.Server/Filters/AdditionalModelFilter.cs
index 7407bd2eb7..efa2f4cca5 100644
--- a/Jellyfin.Server/Filters/AdditionalModelFilter.cs
+++ b/Jellyfin.Server/Filters/AdditionalModelFilter.cs
@@ -3,18 +3,17 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
+using System.Text.Json.Nodes;
using Jellyfin.Extensions;
using Jellyfin.Server.Migrations;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Net.WebSocketMessages;
-using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
using MediaBrowser.Model.ApiClient;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.SyncPlay;
-using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters
@@ -25,7 +24,7 @@ namespace Jellyfin.Server.Filters
public class AdditionalModelFilter : IDocumentFilter
{
// Array of options that should not be visible in the api spec.
- private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions) };
+ private static readonly Type[] _ignoredConfigurations = [typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions)];
private readonly IServerConfigurationManager _serverConfigurationManager;
/// <summary>
@@ -48,8 +47,8 @@ namespace Jellyfin.Server.Filters
&& t != typeof(WebSocketMessageInfo))
.ToList();
- var inboundWebSocketSchemas = new List<OpenApiSchema>();
- var inboundWebSocketDiscriminators = new Dictionary<string, string>();
+ var inboundWebSocketSchemas = new List<IOpenApiSchema>();
+ var inboundWebSocketDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t)))
{
var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
@@ -60,18 +59,16 @@ namespace Jellyfin.Server.Filters
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
inboundWebSocketSchemas.Add(schema);
- inboundWebSocketDiscriminators[messageType.ToString()!] = schema.Reference.ReferenceV3;
+ if (schema is OpenApiSchemaReference schemaRef)
+ {
+ inboundWebSocketDiscriminators[messageType.ToString()!] = schemaRef;
+ }
}
var inboundWebSocketMessageSchema = new OpenApiSchema
{
- Type = "object",
+ Type = JsonSchemaType.Object,
Description = "Represents the list of possible inbound websocket types",
- Reference = new OpenApiReference
- {
- Id = nameof(InboundWebSocketMessage),
- Type = ReferenceType.Schema
- },
OneOf = inboundWebSocketSchemas,
Discriminator = new OpenApiDiscriminator
{
@@ -82,8 +79,8 @@ namespace Jellyfin.Server.Filters
context.SchemaRepository.AddDefinition(nameof(InboundWebSocketMessage), inboundWebSocketMessageSchema);
- var outboundWebSocketSchemas = new List<OpenApiSchema>();
- var outboundWebSocketDiscriminators = new Dictionary<string, string>();
+ var outboundWebSocketSchemas = new List<IOpenApiSchema>();
+ var outboundWebSocketDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t)))
{
var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
@@ -94,58 +91,55 @@ namespace Jellyfin.Server.Filters
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
outboundWebSocketSchemas.Add(schema);
- outboundWebSocketDiscriminators.Add(messageType.ToString()!, schema.Reference.ReferenceV3);
+ if (schema is OpenApiSchemaReference schemaRef)
+ {
+ outboundWebSocketDiscriminators.Add(messageType.ToString()!, schemaRef);
+ }
}
// Add custom "SyncPlayGroupUpdateMessage" schema because Swashbuckle cannot generate it for us
var syncPlayGroupUpdateMessageSchema = new OpenApiSchema
{
- Type = "object",
+ Type = JsonSchemaType.Object,
Description = "Untyped sync play command.",
- Properties = new Dictionary<string, OpenApiSchema>
+ Properties = new Dictionary<string, IOpenApiSchema>
{
{
"Data", new OpenApiSchema
{
- AllOf =
- [
- new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(GroupUpdate<object>) } }
- ],
+ AllOf = new List<IOpenApiSchema>
+ {
+ new OpenApiSchemaReference(nameof(GroupUpdate<object>), null, null)
+ },
Description = "Group update data",
- Nullable = false,
}
},
- { "MessageId", new OpenApiSchema { Type = "string", Format = "uuid", Description = "Gets or sets the message id." } },
+ { "MessageId", new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid", Description = "Gets or sets the message id." } },
{
"MessageType", new OpenApiSchema
{
- Enum = Enum.GetValues<SessionMessageType>().Select(type => new OpenApiString(type.ToString())).ToList<IOpenApiAny>(),
- AllOf =
- [
- new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(SessionMessageType) } }
- ],
+ Enum = Enum.GetValues<SessionMessageType>().Select(type => (JsonNode)JsonValue.Create(type.ToString())!).ToList(),
+ AllOf = new List<IOpenApiSchema>
+ {
+ new OpenApiSchemaReference(nameof(SessionMessageType), null, null)
+ },
Description = "The different kinds of messages that are used in the WebSocket api.",
- Default = new OpenApiString(nameof(SessionMessageType.SyncPlayGroupUpdate)),
+ Default = JsonValue.Create(nameof(SessionMessageType.SyncPlayGroupUpdate)),
ReadOnly = true
}
},
},
AdditionalPropertiesAllowed = false,
- Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "SyncPlayGroupUpdateMessage" }
};
context.SchemaRepository.AddDefinition("SyncPlayGroupUpdateMessage", syncPlayGroupUpdateMessageSchema);
- outboundWebSocketSchemas.Add(syncPlayGroupUpdateMessageSchema);
- outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayGroupUpdateMessageSchema.Reference.ReferenceV3;
+ var syncPlayRef = new OpenApiSchemaReference("SyncPlayGroupUpdateMessage", null, null);
+ outboundWebSocketSchemas.Add(syncPlayRef);
+ outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayRef;
var outboundWebSocketMessageSchema = new OpenApiSchema
{
- Type = "object",
+ Type = JsonSchemaType.Object,
Description = "Represents the list of possible outbound websocket types",
- Reference = new OpenApiReference
- {
- Id = nameof(OutboundWebSocketMessage),
- Type = ReferenceType.Schema
- },
OneOf = outboundWebSocketSchemas,
Discriminator = new OpenApiDiscriminator
{
@@ -159,17 +153,12 @@ namespace Jellyfin.Server.Filters
nameof(WebSocketMessage),
new OpenApiSchema
{
- Type = "object",
+ Type = JsonSchemaType.Object,
Description = "Represents the possible websocket types",
- Reference = new OpenApiReference
- {
- Id = nameof(WebSocketMessage),
- Type = ReferenceType.Schema
- },
- OneOf = new[]
+ OneOf = new List<IOpenApiSchema>
{
- inboundWebSocketMessageSchema,
- outboundWebSocketMessageSchema
+ new OpenApiSchemaReference(nameof(InboundWebSocketMessage), null, null),
+ new OpenApiSchemaReference(nameof(OutboundWebSocketMessage), null, null)
}
});
@@ -180,8 +169,8 @@ namespace Jellyfin.Server.Filters
&& t.BaseType.GetGenericTypeDefinition() == typeof(GroupUpdate<>))
.ToList();
- var groupUpdateSchemas = new List<OpenApiSchema>();
- var groupUpdateDiscriminators = new Dictionary<string, string>();
+ var groupUpdateSchemas = new List<IOpenApiSchema>();
+ var groupUpdateDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
foreach (var type in groupUpdateTypes)
{
var groupUpdateType = (GroupUpdateType?)type.GetProperty(nameof(GroupUpdate<object>.Type))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
@@ -192,18 +181,16 @@ namespace Jellyfin.Server.Filters
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
groupUpdateSchemas.Add(schema);
- groupUpdateDiscriminators[groupUpdateType.ToString()!] = schema.Reference.ReferenceV3;
+ if (schema is OpenApiSchemaReference schemaRef)
+ {
+ groupUpdateDiscriminators[groupUpdateType.ToString()!] = schemaRef;
+ }
}
var groupUpdateSchema = new OpenApiSchema
{
- Type = "object",
+ Type = JsonSchemaType.Object,
Description = "Represents the list of possible group update types",
- Reference = new OpenApiReference
- {
- Id = nameof(GroupUpdate<object>),
- Type = ReferenceType.Schema
- },
OneOf = groupUpdateSchemas,
Discriminator = new OpenApiDiscriminator
{
diff --git a/Jellyfin.Server/Filters/CachingOpenApiProvider.cs b/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
index 833b684444..fdc49a9840 100644
--- a/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
+++ b/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
@@ -48,7 +48,7 @@ internal sealed class CachingOpenApiProvider : ISwaggerProvider
}
/// <inheritdoc />
- public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null)
+ public OpenApiDocument GetSwagger(string documentName, string host, string basePath)
{
if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null)
{
diff --git a/Jellyfin.Server/Filters/FileRequestFilter.cs b/Jellyfin.Server/Filters/FileRequestFilter.cs
index 86dbf7657e..3d5b1fdf1f 100644
--- a/Jellyfin.Server/Filters/FileRequestFilter.cs
+++ b/Jellyfin.Server/Filters/FileRequestFilter.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using Jellyfin.Api.Attributes;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters
@@ -28,10 +28,11 @@ namespace Jellyfin.Server.Filters
{
Schema = new OpenApiSchema
{
- Type = "string",
+ Type = JsonSchemaType.String,
Format = "binary"
}
};
+ body.Content ??= new System.Collections.Generic.Dictionary<string, OpenApiMediaType>();
foreach (var contentType in contentTypes)
{
body.Content.Add(contentType, mediaType);
diff --git a/Jellyfin.Server/Filters/FileResponseFilter.cs b/Jellyfin.Server/Filters/FileResponseFilter.cs
index cd0acadf32..64aea62519 100644
--- a/Jellyfin.Server/Filters/FileResponseFilter.cs
+++ b/Jellyfin.Server/Filters/FileResponseFilter.cs
@@ -1,7 +1,7 @@
using System;
using System.Linq;
using Jellyfin.Api.Attributes;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters
@@ -14,7 +14,7 @@ namespace Jellyfin.Server.Filters
{
Schema = new OpenApiSchema
{
- Type = "string",
+ Type = JsonSchemaType.String,
Format = "binary"
}
};
@@ -22,6 +22,11 @@ namespace Jellyfin.Server.Filters
/// <inheritdoc />
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
+ if (operation.Responses is null)
+ {
+ return;
+ }
+
foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata)
{
if (attribute is ProducesFileAttribute producesFileAttribute)
@@ -31,7 +36,7 @@ namespace Jellyfin.Server.Filters
.FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal));
// Operation doesn't have a response.
- if (response.Value is null)
+ if (response.Value?.Content is null)
{
continue;
}
diff --git a/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs b/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs
index 3e0b69d017..0c1f4197ce 100644
--- a/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs
+++ b/Jellyfin.Server/Filters/FlagsEnumSchemaFilter.cs
@@ -1,5 +1,5 @@
using System;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters;
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Filters;
public class FlagsEnumSchemaFilter : ISchemaFilter
{
/// <inheritdoc />
- public void Apply(OpenApiSchema schema, SchemaFilterContext context)
+ public void Apply(IOpenApiSchema schema, SchemaFilterContext context)
{
var type = context.Type.IsEnum ? context.Type : Nullable.GetUnderlyingType(context.Type);
if (type is null || !type.IsEnum)
@@ -29,11 +29,16 @@ public class FlagsEnumSchemaFilter : ISchemaFilter
return;
}
+ if (schema is not OpenApiSchema concreteSchema)
+ {
+ return;
+ }
+
if (context.MemberInfo is null)
{
// Processing the enum definition itself - ensure it's type "string" not "integer"
- schema.Type = "string";
- schema.Format = null;
+ concreteSchema.Type = JsonSchemaType.String;
+ concreteSchema.Format = null;
}
else
{
@@ -43,11 +48,11 @@ public class FlagsEnumSchemaFilter : ISchemaFilter
// 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;
+ concreteSchema.Type = JsonSchemaType.Array;
+ concreteSchema.Format = null;
+ concreteSchema.Enum = null;
+ concreteSchema.AllOf = null;
+ concreteSchema.Items = enumSchema;
}
}
}
diff --git a/Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs b/Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs
index eb9ad03c21..3dcf29d9c2 100644
--- a/Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs
+++ b/Jellyfin.Server/Filters/IgnoreEnumSchemaFilter.cs
@@ -2,9 +2,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Text.Json.Nodes;
using Jellyfin.Data.Attributes;
-using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters;
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Filters;
public class IgnoreEnumSchemaFilter : ISchemaFilter
{
/// <inheritdoc />
- public void Apply(OpenApiSchema schema, SchemaFilterContext context)
+ public void Apply(IOpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type.IsEnum || (Nullable.GetUnderlyingType(context.Type)?.IsEnum ?? false))
{
@@ -25,18 +25,23 @@ public class IgnoreEnumSchemaFilter : ISchemaFilter
return;
}
- var enumOpenApiStrings = new List<IOpenApiAny>();
+ if (schema is not OpenApiSchema concreteSchema)
+ {
+ return;
+ }
+
+ var enumOpenApiNodes = new List<JsonNode>();
foreach (var enumName in Enum.GetNames(type))
{
var member = type.GetMember(enumName)[0];
if (!member.GetCustomAttributes<OpenApiIgnoreEnumAttribute>().Any())
{
- enumOpenApiStrings.Add(new OpenApiString(enumName));
+ enumOpenApiNodes.Add(JsonValue.Create(enumName)!);
}
}
- schema.Enum = enumOpenApiStrings;
+ concreteSchema.Enum = enumOpenApiNodes;
}
}
}
diff --git a/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs b/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs
index 98a8dc0f18..90bca884b1 100644
--- a/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs
+++ b/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs
@@ -1,7 +1,7 @@
using System;
using System.Linq;
using Jellyfin.Api.Attributes;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters
@@ -21,11 +21,17 @@ namespace Jellyfin.Server.Filters
.OfType<ParameterObsoleteAttribute>()
.Any())
{
+ if (operation.Parameters is null)
+ {
+ continue;
+ }
+
foreach (var parameter in operation.Parameters)
{
- if (parameter.Name.Equals(parameterDescription.Name, StringComparison.Ordinal))
+ if (parameter is OpenApiParameter concreteParam
+ && string.Equals(concreteParam.Name, parameterDescription.Name, StringComparison.Ordinal))
{
- parameter.Deprecated = true;
+ concreteParam.Deprecated = true;
break;
}
}
diff --git a/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs b/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs
index 8b7268513a..435f55496a 100644
--- a/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs
+++ b/Jellyfin.Server/Filters/RetryOnTemporarilyUnavailableFilter.cs
@@ -1,5 +1,5 @@
using System.Collections.Generic;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters;
@@ -8,12 +8,12 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
- operation.Responses.TryAdd(
+ operation.Responses?.TryAdd(
"503",
new OpenApiResponse
{
Description = "The server is currently starting or is temporarily not available.",
- Headers = new Dictionary<string, OpenApiHeader>
+ Headers = new Dictionary<string, IOpenApiHeader>
{
{
"Retry-After", new OpenApiHeader
@@ -23,7 +23,7 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
Description = "A hint for when to retry the operation in full seconds.",
Schema = new OpenApiSchema
{
- Type = "integer",
+ Type = JsonSchemaType.Integer,
Format = "int32"
}
}
@@ -36,7 +36,7 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
Description = "A short plain-text reason why the server is not available.",
Schema = new OpenApiSchema
{
- Type = "string",
+ Type = JsonSchemaType.String,
Format = "text"
}
}
diff --git a/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs b/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs
index 8f57572696..5b048be913 100644
--- a/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs
+++ b/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs
@@ -5,7 +5,7 @@ using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Extensions;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Jellyfin.Server.Filters;
@@ -66,17 +66,10 @@ public class SecurityRequirementsOperationFilter : IOperationFilter
return;
}
- operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
- operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
+ operation.Responses?.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
+ operation.Responses?.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
- var scheme = new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = AuthenticationSchemes.CustomAuthentication
- },
- };
+ var scheme = new OpenApiSecuritySchemeReference(AuthenticationSchemes.CustomAuthentication, null, null);
// Add DefaultAuthorization scope to any endpoint that has a policy with a requirement that is a subset of DefaultAuthorization.
if (!requiredScopes.Contains(DefaultAuthPolicy.AsSpan(), StringComparison.Ordinal))
diff --git a/Jellyfin.Server/Filters/SecuritySchemeReferenceFixupFilter.cs b/Jellyfin.Server/Filters/SecuritySchemeReferenceFixupFilter.cs
new file mode 100644
index 0000000000..e4eb5be2b9
--- /dev/null
+++ b/Jellyfin.Server/Filters/SecuritySchemeReferenceFixupFilter.cs
@@ -0,0 +1,56 @@
+using Microsoft.OpenApi;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Jellyfin.Server.Filters;
+
+/// <summary>
+/// Document filter that fixes security scheme references after document generation.
+/// </summary>
+/// <remarks>
+/// In Microsoft.OpenApi v2, <see cref="OpenApiSecuritySchemeReference"/> requires a resolved
+/// <c>Target</c> to serialize correctly. References created without a host document (as in
+/// operation filters) serialize as empty objects. This filter re-creates all security scheme
+/// references with the document context so they resolve properly during serialization.
+/// </remarks>
+internal class SecuritySchemeReferenceFixupFilter : IDocumentFilter
+{
+ /// <inheritdoc />
+ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
+ {
+ swaggerDoc.RegisterComponents();
+
+ if (swaggerDoc.Paths is null)
+ {
+ return;
+ }
+
+ foreach (var pathItem in swaggerDoc.Paths.Values)
+ {
+ if (pathItem.Operations is null)
+ {
+ continue;
+ }
+
+ foreach (var operation in pathItem.Operations.Values)
+ {
+ if (operation.Security is null)
+ {
+ continue;
+ }
+
+ for (int i = 0; i < operation.Security.Count; i++)
+ {
+ var oldReq = operation.Security[i];
+ var newReq = new OpenApiSecurityRequirement();
+ foreach (var kvp in oldReq)
+ {
+ var fixedRef = new OpenApiSecuritySchemeReference(kvp.Key.Reference.Id!, swaggerDoc);
+ newReq[fixedRef] = kvp.Value;
+ }
+
+ operation.Security[i] = newReq;
+ }
+ }
+ }
+ }
+}