< Summary - Jellyfin

Information
Class: Jellyfin.Server.Filters.CachingOpenApiProvider
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Filters/CachingOpenApiProvider.cs
Line coverage
17%
Covered lines: 5
Uncovered lines: 24
Coverable lines: 29
Total lines: 93
Line coverage: 17.2%
Branch coverage
0%
Covered branches: 0
Total branches: 16
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 12/4/2025 - 12:11:49 AM Line coverage: 88.8% (24/27) Branch coverage: 55.5% (10/18) Total lines: 8912/10/2025 - 12:13:43 AM Line coverage: 87.5% (21/24) Branch coverage: 50% (8/16) Total lines: 791/3/2026 - 12:11:48 AM Line coverage: 17.2% (5/29) Branch coverage: 0% (0/16) Total lines: 93 12/4/2025 - 12:11:49 AM Line coverage: 88.8% (24/27) Branch coverage: 55.5% (10/18) Total lines: 8912/10/2025 - 12:13:43 AM Line coverage: 87.5% (21/24) Branch coverage: 50% (8/16) Total lines: 791/3/2026 - 12:11:48 AM Line coverage: 17.2% (5/29) Branch coverage: 0% (0/16) Total lines: 93

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)100%11100%
GetSwagger(...)0%110100%
AdjustDocument(...)0%4260%

File(s)

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

#LineLine coverage
 1using System;
 2using AsyncKeyedLock;
 3using Microsoft.AspNetCore.Mvc.ApiExplorer;
 4using Microsoft.Extensions.Caching.Memory;
 5using Microsoft.Extensions.Logging;
 6using Microsoft.Extensions.Options;
 7using Microsoft.OpenApi.Models;
 8using Swashbuckle.AspNetCore.Swagger;
 9using Swashbuckle.AspNetCore.SwaggerGen;
 10
 11namespace Jellyfin.Server.Filters;
 12
 13/// <summary>
 14/// OpenApi provider with caching.
 15/// </summary>
 16internal sealed class CachingOpenApiProvider : ISwaggerProvider
 17{
 18    private const string CacheKey = "openapi.json";
 19
 020    private static readonly MemoryCacheEntryOptions _cacheOptions = new() { SlidingExpiration = TimeSpan.FromMinutes(5) 
 021    private static readonly AsyncNonKeyedLocker _lock = new(1);
 022    private static readonly TimeSpan _lockTimeout = TimeSpan.FromSeconds(1);
 23
 24    private readonly IMemoryCache _memoryCache;
 25    private readonly SwaggerGenerator _swaggerGenerator;
 26    private readonly SwaggerGeneratorOptions _swaggerGeneratorOptions;
 27    private readonly ILogger<CachingOpenApiProvider> _logger;
 28
 29    /// <summary>
 30    /// Initializes a new instance of the <see cref="CachingOpenApiProvider"/> class.
 31    /// </summary>
 32    /// <param name="optionsAccessor">The options accessor.</param>
 33    /// <param name="apiDescriptionsProvider">The api descriptions provider.</param>
 34    /// <param name="schemaGenerator">The schema generator.</param>
 35    /// <param name="memoryCache">The memory cache.</param>
 36    /// <param name="logger">The logger.</param>
 37    public CachingOpenApiProvider(
 38        IOptions<SwaggerGeneratorOptions> optionsAccessor,
 39        IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
 40        ISchemaGenerator schemaGenerator,
 41        IMemoryCache memoryCache,
 42        ILogger<CachingOpenApiProvider> logger)
 43    {
 16944        _swaggerGeneratorOptions = optionsAccessor.Value;
 16945        _swaggerGenerator = new SwaggerGenerator(_swaggerGeneratorOptions, apiDescriptionsProvider, schemaGenerator);
 16946        _memoryCache = memoryCache;
 16947        _logger = logger;
 16948    }
 49
 50    /// <inheritdoc />
 51    public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null)
 52    {
 053        if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null)
 54        {
 055            return AdjustDocument(openApiDocument, host, basePath);
 56        }
 57
 058        using var acquired = _lock.LockOrNull(_lockTimeout);
 059        if (_memoryCache.TryGetValue(CacheKey, out openApiDocument) && openApiDocument is not null)
 60        {
 061            return AdjustDocument(openApiDocument, host, basePath);
 62        }
 63
 064        if (acquired is null)
 65        {
 066            throw new InvalidOperationException("OpenApi document is generating");
 67        }
 68
 69        try
 70        {
 071        openApiDocument = _swaggerGenerator.GetSwagger(documentName);
 072        }
 073        catch (Exception ex)
 74        {
 075            _logger.LogError(ex, "OpenAPI generation error");
 076            throw;
 77        }
 78
 079        _memoryCache.Set(CacheKey, openApiDocument, _cacheOptions);
 080        return AdjustDocument(openApiDocument, host, basePath);
 081    }
 82
 83    private OpenApiDocument AdjustDocument(OpenApiDocument document, string? host, string? basePath)
 84    {
 085        document.Servers = _swaggerGeneratorOptions.Servers.Count != 0
 086            ? _swaggerGeneratorOptions.Servers
 087            : string.IsNullOrEmpty(host) && string.IsNullOrEmpty(basePath)
 088                ? []
 089                : [new OpenApiServer { Url = $"{host}{basePath}" }];
 90
 091        return document;
 92    }
 93}