< Summary - Jellyfin

Information
Class: Jellyfin.Server.Filters.AdditionalModelFilter
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Filters/AdditionalModelFilter.cs
Line coverage
100%
Covered lines: 131
Uncovered lines: 0
Coverable lines: 131
Total lines: 217
Line coverage: 100%
Branch coverage
88%
Covered branches: 30
Total branches: 34
Branch coverage: 88.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 12/6/2025 - 12:11:15 AM Line coverage: 100% (157/157) Branch coverage: 85.7% (24/28) Total lines: 2391/3/2026 - 12:11:48 AM Line coverage: 100% (149/149) Branch coverage: 85.7% (24/28) Total lines: 2302/27/2026 - 12:13:29 AM Line coverage: 100% (131/131) Branch coverage: 88.2% (30/34) Total lines: 217 12/6/2025 - 12:11:15 AM Line coverage: 100% (157/157) Branch coverage: 85.7% (24/28) Total lines: 2391/3/2026 - 12:11:48 AM Line coverage: 100% (149/149) Branch coverage: 85.7% (24/28) Total lines: 2302/27/2026 - 12:13:29 AM Line coverage: 100% (131/131) Branch coverage: 88.2% (30/34) Total lines: 217

Coverage delta

Coverage delta 3 -3

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
Apply(...)88.23%3434100%

File(s)

/srv/git/jellyfin/Jellyfin.Server/Filters/AdditionalModelFilter.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.ComponentModel;
 4using System.Linq;
 5using System.Reflection;
 6using System.Text.Json.Nodes;
 7using Jellyfin.Extensions;
 8using Jellyfin.Server.Migrations;
 9using MediaBrowser.Common.Plugins;
 10using MediaBrowser.Controller.Configuration;
 11using MediaBrowser.Controller.Net;
 12using MediaBrowser.Controller.Net.WebSocketMessages;
 13using MediaBrowser.Model.ApiClient;
 14using MediaBrowser.Model.Session;
 15using MediaBrowser.Model.SyncPlay;
 16using Microsoft.OpenApi;
 17using Swashbuckle.AspNetCore.SwaggerGen;
 18
 19namespace Jellyfin.Server.Filters
 20{
 21    /// <summary>
 22    /// Add models not directly used by the API, but used for discovery and websockets.
 23    /// </summary>
 24    public class AdditionalModelFilter : IDocumentFilter
 25    {
 26        // Array of options that should not be visible in the api spec.
 127        private static readonly Type[] _ignoredConfigurations = [typeof(MigrationOptions), typeof(MediaBrowser.Model.Bra
 28        private readonly IServerConfigurationManager _serverConfigurationManager;
 29
 30        /// <summary>
 31        /// Initializes a new instance of the <see cref="AdditionalModelFilter"/> class.
 32        /// </summary>
 33        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface
 34        public AdditionalModelFilter(IServerConfigurationManager serverConfigurationManager)
 35        {
 2036            _serverConfigurationManager = serverConfigurationManager;
 2037        }
 38
 39        /// <inheritdoc />
 40        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
 41        {
 142            context.SchemaGenerator.GenerateSchema(typeof(IPlugin), context.SchemaRepository);
 43
 144            var webSocketTypes = typeof(WebSocketMessage).Assembly.GetTypes()
 145                .Where(t => t.IsSubclassOf(typeof(WebSocketMessage))
 146                            && !t.IsGenericType
 147                            && t != typeof(WebSocketMessageInfo))
 148                .ToList();
 49
 150            var inboundWebSocketSchemas = new List<IOpenApiSchema>();
 151            var inboundWebSocketDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
 1852            foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t)))
 53            {
 854                var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustom
 855                if (messageType is null)
 56                {
 57                    continue;
 58                }
 59
 760                var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
 761                inboundWebSocketSchemas.Add(schema);
 762                if (schema is OpenApiSchemaReference schemaRef)
 63                {
 764                    inboundWebSocketDiscriminators[messageType.ToString()!] = schemaRef;
 65                }
 66            }
 67
 168            var inboundWebSocketMessageSchema = new OpenApiSchema
 169            {
 170                Type = JsonSchemaType.Object,
 171                Description = "Represents the list of possible inbound websocket types",
 172                OneOf = inboundWebSocketSchemas,
 173                Discriminator = new OpenApiDiscriminator
 174                {
 175                    PropertyName = nameof(WebSocketMessage.MessageType),
 176                    Mapping = inboundWebSocketDiscriminators
 177                }
 178            };
 79
 180            context.SchemaRepository.AddDefinition(nameof(InboundWebSocketMessage), inboundWebSocketMessageSchema);
 81
 182            var outboundWebSocketSchemas = new List<IOpenApiSchema>();
 183            var outboundWebSocketDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
 5884            foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t)))
 85            {
 2886                var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustom
 2887                if (messageType is null)
 88                {
 89                    continue;
 90                }
 91
 2792                var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
 2793                outboundWebSocketSchemas.Add(schema);
 2794                if (schema is OpenApiSchemaReference schemaRef)
 95                {
 2796                    outboundWebSocketDiscriminators.Add(messageType.ToString()!, schemaRef);
 97                }
 98            }
 99
 100            // Add custom "SyncPlayGroupUpdateMessage" schema because Swashbuckle cannot generate it for us
 1101            var syncPlayGroupUpdateMessageSchema = new OpenApiSchema
 1102            {
 1103                Type = JsonSchemaType.Object,
 1104                Description = "Untyped sync play command.",
 1105                Properties = new Dictionary<string, IOpenApiSchema>
 1106                {
 1107                    {
 1108                        "Data", new OpenApiSchema
 1109                        {
 1110                            AllOf = new List<IOpenApiSchema>
 1111                            {
 1112                                new OpenApiSchemaReference(nameof(GroupUpdate<object>), null, null)
 1113                            },
 1114                            Description = "Group update data",
 1115                        }
 1116                    },
 1117                    { "MessageId", new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid", Description = "Get
 1118                    {
 1119                        "MessageType", new OpenApiSchema
 1120                        {
 1121                            Enum = Enum.GetValues<SessionMessageType>().Select(type => (JsonNode)JsonValue.Create(type.T
 1122                            AllOf = new List<IOpenApiSchema>
 1123                            {
 1124                                new OpenApiSchemaReference(nameof(SessionMessageType), null, null)
 1125                            },
 1126                            Description = "The different kinds of messages that are used in the WebSocket api.",
 1127                            Default = JsonValue.Create(nameof(SessionMessageType.SyncPlayGroupUpdate)),
 1128                            ReadOnly = true
 1129                        }
 1130                    },
 1131                },
 1132                AdditionalPropertiesAllowed = false,
 1133            };
 1134            context.SchemaRepository.AddDefinition("SyncPlayGroupUpdateMessage", syncPlayGroupUpdateMessageSchema);
 1135            var syncPlayRef = new OpenApiSchemaReference("SyncPlayGroupUpdateMessage", null, null);
 1136            outboundWebSocketSchemas.Add(syncPlayRef);
 1137            outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayRef;
 138
 1139            var outboundWebSocketMessageSchema = new OpenApiSchema
 1140            {
 1141                Type = JsonSchemaType.Object,
 1142                Description = "Represents the list of possible outbound websocket types",
 1143                OneOf = outboundWebSocketSchemas,
 1144                Discriminator = new OpenApiDiscriminator
 1145                {
 1146                    PropertyName = nameof(WebSocketMessage.MessageType),
 1147                    Mapping = outboundWebSocketDiscriminators
 1148                }
 1149            };
 150
 1151            context.SchemaRepository.AddDefinition(nameof(OutboundWebSocketMessage), outboundWebSocketMessageSchema);
 1152            context.SchemaRepository.AddDefinition(
 1153                nameof(WebSocketMessage),
 1154                new OpenApiSchema
 1155                {
 1156                    Type = JsonSchemaType.Object,
 1157                    Description = "Represents the possible websocket types",
 1158                    OneOf = new List<IOpenApiSchema>
 1159                    {
 1160                        new OpenApiSchemaReference(nameof(InboundWebSocketMessage), null, null),
 1161                        new OpenApiSchemaReference(nameof(OutboundWebSocketMessage), null, null)
 1162                    }
 1163                });
 164
 165            // Manually generate sync play GroupUpdate messages.
 1166            var groupUpdateTypes = typeof(GroupUpdate<>).Assembly.GetTypes()
 1167                .Where(t => t.BaseType is not null
 1168                            && t.BaseType.IsGenericType
 1169                            && t.BaseType.GetGenericTypeDefinition() == typeof(GroupUpdate<>))
 1170                .ToList();
 171
 1172            var groupUpdateSchemas = new List<IOpenApiSchema>();
 1173            var groupUpdateDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
 20174            foreach (var type in groupUpdateTypes)
 175            {
 9176                var groupUpdateType = (GroupUpdateType?)type.GetProperty(nameof(GroupUpdate<object>.Type))?.GetCustomAtt
 9177                if (groupUpdateType is null)
 178                {
 179                    continue;
 180                }
 181
 9182                var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
 9183                groupUpdateSchemas.Add(schema);
 9184                if (schema is OpenApiSchemaReference schemaRef)
 185                {
 9186                    groupUpdateDiscriminators[groupUpdateType.ToString()!] = schemaRef;
 187                }
 188            }
 189
 1190            var groupUpdateSchema = new OpenApiSchema
 1191            {
 1192                Type = JsonSchemaType.Object,
 1193                Description = "Represents the list of possible group update types",
 1194                OneOf = groupUpdateSchemas,
 1195                Discriminator = new OpenApiDiscriminator
 1196                {
 1197                    PropertyName = nameof(GroupUpdate<object>.Type),
 1198                    Mapping = groupUpdateDiscriminators
 1199                }
 1200            };
 201
 1202            context.SchemaRepository.Schemas[nameof(GroupUpdate<object>)] = groupUpdateSchema;
 203
 1204            context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository);
 205
 18206            foreach (var configuration in _serverConfigurationManager.GetConfigurationStores())
 207            {
 8208                if (_ignoredConfigurations.IndexOf(configuration.ConfigurationType) != -1)
 209                {
 210                    continue;
 211                }
 212
 7213                context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository);
 214            }
 1215        }
 216    }
 217}