< Summary

Information
Class: MyNet.Globalization.Localization.Providers.LocalizationServiceResolver
Assembly: MyNet.Globalization
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Globalization/Localization/Providers/LocalizationServiceResolver.cs
Tag: 323_28699572109
Line coverage
97%
Covered lines: 33
Uncovered lines: 1
Coverable lines: 34
Total lines: 117
Line coverage: 97%
Branch coverage
72%
Covered branches: 13
Total branches: 18
Branch coverage: 72.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
TryGet(...)100%22100%
GetRequired(...)50%22100%
CreateCycleException(...)50%44100%
IsCycle(...)100%22100%
ResolveWithCycleCheck(...)83.33%6688.88%
Resolve(...)50%22100%
ForCulture(...)100%11100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Globalization/Localization/Providers/LocalizationServiceResolver.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="LocalizationServiceResolver.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Concurrent;
 9using System.Diagnostics.CodeAnalysis;
 10using System.Globalization;
 11using System.Linq;
 12using System.Threading;
 13using MyNet.Globalization.Localization.Providers.Registration;
 14
 15namespace MyNet.Globalization.Localization.Providers;
 16
 17/// <summary>
 18/// Resolves localization providers based on their type and culture, with caching and cycle detection to prevent circula
 19/// </summary>
 20/// <param name="registry">The localization provider registry.</param>
 21public sealed class LocalizationServiceResolver(ILocalizationFactoryRegistry registry) : ILocalizationServiceResolver
 22{
 623    private static readonly AsyncLocal<(Type Type, string CultureName)[]?> ResolutionPath = new();
 35424    private readonly ConcurrentDictionary<(Type, string), Lazy<object?>> _cache = new();
 25
 26    /// <inheritdoc />
 27    public bool TryGet<TService>(CultureInfo culture, [NotNullWhen(true)] out TService? provider)
 28        where TService : class, ICultureScoped
 29    {
 679830        ArgumentNullException.ThrowIfNull(culture);
 31
 679832        var cacheKey = (typeof(TService), culture.Name);
 33
 679834        if (IsCycle(cacheKey))
 335            throw CreateCycleException(cacheKey);
 36
 679537        var lazyProvider = _cache.GetOrAdd(cacheKey,
 679538            _ => new(() => ResolveWithCycleCheck<TService>(culture), LazyThreadSafetyMode.ExecutionAndPublication));
 39
 679540        provider = lazyProvider.Value as TService;
 678941        return lazyProvider.Value is not null;
 42    }
 43
 44    /// <inheritdoc />
 45    public TService GetRequired<TService>(CultureInfo culture)
 46        where TService : class, ICultureScoped
 672947        => !TryGet<TService>(culture, out var provider)
 672948            ? throw new InvalidOperationException($"Required localization provider of type {typeof(TService).Name} for c
 672949            : provider;
 50
 51    /// <summary>
 52    /// Creates an exception indicating a circular dependency in localization provider resolution, including the resolut
 53    /// </summary>
 54    /// <param name="requested">The requested provider and culture that caused the cycle.</param>
 55    /// <returns>An <see cref="InvalidOperationException"/> describing the circular dependency.</returns>
 56    private static InvalidOperationException CreateCycleException((Type Type, string CultureName) requested)
 57    {
 358        var path = ResolutionPath.Value ?? [];
 359        var chain = string.Join(" -> ", path.Select(x => $"{x.Type.Name}({x.CultureName})"));
 360        var requestedNode = $"{requested.Type.Name}({requested.CultureName})";
 361        var message = string.IsNullOrWhiteSpace(chain)
 362            ? $"Circular localization provider dependency detected for {requestedNode}."
 363            : $"Circular localization provider dependency detected: {chain} -> {requestedNode}.";
 64
 365        return new(message);
 66    }
 67
 68    /// <summary>
 69    /// Checks if the given provider and culture combination is already in the current resolution path, indicating a cir
 70    /// </summary>
 71    /// <param name="key">The provider and culture combination to check.</param>
 72    /// <returns>True if a circular dependency is detected; otherwise, false.</returns>
 73    private static bool IsCycle((Type Type, string CultureName) key)
 74    {
 679875        var path = ResolutionPath.Value;
 679876        return path?.Contains(key) == true;
 77    }
 78
 79    private TService? ResolveWithCycleCheck<TService>(CultureInfo culture)
 80        where TService : class, ICultureScoped
 81    {
 12682        var key = (typeof(TService), culture.Name);
 12683        var path = ResolutionPath.Value ?? [];
 84
 12685        if (path.Contains(key))
 086            throw CreateCycleException(key);
 87
 12688        ResolutionPath.Value = [.. path, key];
 89
 90        try
 91        {
 12692            return Resolve<TService>(culture);
 93        }
 94        finally
 95        {
 12696            ResolutionPath.Value = path.Length == 0 ? null : path;
 12697        }
 12098    }
 99
 100    /// <summary>
 101    /// Resolves the provider of type <typeparamref name="TService"/> for the given culture by querying the registry.
 102    /// </summary>
 103    /// <typeparam name="TService">The type of the provider to resolve.</typeparam>
 104    /// <param name="culture">The culture for which to resolve the provider.</param>
 105    /// <returns>The resolved provider instance, or null if not found.</returns>
 106    private TService? Resolve<TService>(CultureInfo culture)
 107        where TService : class, ICultureScoped
 126108        => !registry.TryGetFactory<TService>(out var factory) ? null : factory.Create(culture);
 109
 110    /// <inheritdoc />
 111    public ILocalizationServiceContext ForCulture(CultureInfo culture)
 112    {
 6711113        ArgumentNullException.ThrowIfNull(culture);
 6711114        return new LocalizationServiceContext(this, culture);
 115    }
 116}
 117