< Summary

Information
Class: TeleFlow.Telegram.Internal.Handlers.TelegramErrorHandlerIndex
Assembly: TeleFlow.Framework
File(s): /_/src/TeleFlow.Framework/Internal/Handlers/TelegramErrorHandlerIndex.cs
Line coverage
94%
Covered lines: 74
Uncovered lines: 4
Coverable lines: 78
Total lines: 228
Line coverage: 94.8%
Branch coverage
85%
Covered branches: 53
Total branches: 62
Branch coverage: 85.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%1010100%
GetCandidates()87.5%88100%
.ctor(...)100%1010100%
GetCandidates()91.66%1212100%
IsCompatibleErrorHandler(...)83.33%6683.33%
HasCompatibleRouteValues(...)100%66100%
HasCompatibleRouteValue(...)50%22100%
TryGetRouteValue(...)50%22100%
IsRouteValueAssignable(...)50%4475%
IsNullableRouteValue(...)0%620%

File(s)

/_/src/TeleFlow.Framework/Internal/Handlers/TelegramErrorHandlerIndex.cs

#LineLine coverage
 1namespace TeleFlow.Telegram.Internal.Handlers;
 2
 3/// <summary>
 4/// Pre-indexes Telegram error handlers so exception dispatch does not rebuild candidate groups per failure.
 5/// </summary>
 6internal sealed class TelegramErrorHandlerIndex
 7{
 8    private readonly Dictionary<string, TelegramErrorHandlerGroup> _moduleGroups;
 9    private readonly TelegramErrorHandlerGroup _globalGroup;
 10
 11    public TelegramErrorHandlerIndex(IEnumerable<TelegramErrorHandlerDescriptor> errorHandlers)
 12    {
 20113        ArgumentNullException.ThrowIfNull(errorHandlers);
 14
 20115        var moduleGroups = new Dictionary<string, List<TelegramErrorHandlerDescriptor>>(StringComparer.Ordinal);
 20116        var globalHandlers = new List<TelegramErrorHandlerDescriptor>();
 17
 64418        foreach (var handler in errorHandlers)
 19        {
 12120            if (handler.ModuleName is null)
 21            {
 7022                globalHandlers.Add(handler);
 7023                continue;
 24            }
 25
 5126            if (!moduleGroups.TryGetValue(handler.ModuleName, out var handlers))
 27            {
 5128                handlers = [];
 5129                moduleGroups.Add(handler.ModuleName, handlers);
 30            }
 31
 5132            handlers.Add(handler);
 33        }
 34
 20135        _moduleGroups = new Dictionary<string, TelegramErrorHandlerGroup>(moduleGroups.Count, StringComparer.Ordinal);
 36
 50437        foreach (var (moduleName, handlers) in moduleGroups)
 38        {
 5139            _moduleGroups.Add(moduleName, new TelegramErrorHandlerGroup(handlers));
 40        }
 41
 20142        _globalGroup = new TelegramErrorHandlerGroup(globalHandlers);
 20143        HasHandlers = _moduleGroups.Count > 0 || _globalGroup.HasHandlers;
 20144    }
 45
 46    /// <summary>
 47    /// Gets whether the index contains at least one error handler.
 48    /// </summary>
 49    public bool HasHandlers { get; }
 50
 51    /// <summary>
 52    /// Enumerates compatible error handlers in dispatcher invocation order.
 53    /// </summary>
 54    public IEnumerable<TelegramErrorHandlerDescriptor> GetCandidates(
 55        TelegramRouteSelection selection,
 56        TelegramUpdateContext telegramContext,
 57        Exception exception)
 58    {
 2159        ArgumentNullException.ThrowIfNull(selection);
 2160        ArgumentNullException.ThrowIfNull(telegramContext);
 2161        ArgumentNullException.ThrowIfNull(exception);
 62
 2163        if (selection.Handler.ModuleName is { } moduleName &&
 2164            _moduleGroups.TryGetValue(moduleName, out var moduleGroup))
 65        {
 1266            foreach (var candidate in moduleGroup.GetCandidates(selection.RouteValues, telegramContext, exception))
 67            {
 468                yield return candidate;
 69            }
 70        }
 71
 5572        foreach (var candidate in _globalGroup.GetCandidates(selection.RouteValues, telegramContext, exception))
 73        {
 1874            yield return candidate;
 75        }
 276    }
 77
 78    private sealed class TelegramErrorHandlerGroup
 79    {
 80        private readonly Dictionary<Type, TelegramErrorHandlerDescriptor[]> _typedHandlers;
 81        private readonly TelegramErrorHandlerDescriptor[] _catchAllHandlers;
 82
 83        public TelegramErrorHandlerGroup(IEnumerable<TelegramErrorHandlerDescriptor> handlers)
 84        {
 25285            ArgumentNullException.ThrowIfNull(handlers);
 86
 25287            var typedHandlers = new Dictionary<Type, List<TelegramErrorHandlerDescriptor>>();
 25288            var catchAllHandlers = new List<TelegramErrorHandlerDescriptor>();
 89
 86790            foreach (var handler in handlers.OrderBy(static handler => handler.RegistrationOrder))
 91            {
 12192                if (handler.ExceptionType is null)
 93                {
 2994                    catchAllHandlers.Add(handler);
 2995                    continue;
 96                }
 97
 9298                if (!typedHandlers.TryGetValue(handler.ExceptionType, out var typedGroup))
 99                {
 90100                    typedGroup = [];
 90101                    typedHandlers.Add(handler.ExceptionType, typedGroup);
 102                }
 103
 92104                typedGroup.Add(handler);
 105            }
 106
 252107            _typedHandlers = new Dictionary<Type, TelegramErrorHandlerDescriptor[]>(typedHandlers.Count);
 108
 684109            foreach (var (exceptionType, typedGroup) in typedHandlers)
 110            {
 90111                _typedHandlers.Add(exceptionType, typedGroup.ToArray());
 112            }
 113
 252114            _catchAllHandlers = catchAllHandlers.ToArray();
 252115            HasHandlers = _typedHandlers.Count > 0 || _catchAllHandlers.Length > 0;
 252116        }
 117
 118        public bool HasHandlers { get; }
 119
 120        public IEnumerable<TelegramErrorHandlerDescriptor> GetCandidates(
 121            IReadOnlyDictionary<string, object?> routeValues,
 122            TelegramUpdateContext telegramContext,
 123            Exception exception)
 124        {
 21125            var exceptionType = exception.GetType();
 126
 56127            while (exceptionType is not null)
 128            {
 48129                if (_typedHandlers.TryGetValue(exceptionType, out var typedHandlers))
 130                {
 51131                    foreach (var handler in typedHandlers)
 132                    {
 17133                        if (IsCompatibleErrorHandler(handler, routeValues, telegramContext))
 134                        {
 16135                            yield return handler;
 136                        }
 137                    }
 138                }
 139
 35140                exceptionType = exceptionType.BaseType;
 141            }
 142
 22143            foreach (var handler in _catchAllHandlers)
 144            {
 6145                if (IsCompatibleErrorHandler(handler, routeValues, telegramContext))
 146                {
 6147                    yield return handler;
 148                }
 149            }
 2150        }
 151    }
 152
 153    private static bool IsCompatibleErrorHandler(
 154        TelegramErrorHandlerDescriptor handler,
 155        IReadOnlyDictionary<string, object?> routeValues,
 156        TelegramUpdateContext telegramContext)
 157    {
 23158        if (handler.TelegramContextType is not null &&
 23159            !handler.TelegramContextType.IsInstanceOfType(telegramContext))
 160        {
 0161            return false;
 162        }
 163
 23164        if (!HasCompatibleRouteValues(handler, routeValues))
 165        {
 1166            return false;
 167        }
 168
 22169        return true;
 170    }
 171
 172    private static bool HasCompatibleRouteValues(
 173        TelegramErrorHandlerDescriptor handler,
 174        IReadOnlyDictionary<string, object?> routeValues)
 175    {
 161176        foreach (var parameter in handler.Parameters)
 177        {
 58178            if (parameter.Kind != TelegramErrorHandlerParameterKind.RouteValue)
 179            {
 180                continue;
 181            }
 182
 3183            if (!HasCompatibleRouteValue(parameter, routeValues))
 184            {
 1185                return false;
 186            }
 187        }
 188
 22189        return true;
 1190    }
 191
 192    private static bool HasCompatibleRouteValue(
 193        TelegramErrorHandlerParameterDescriptor parameter,
 194        IReadOnlyDictionary<string, object?> routeValues)
 195    {
 3196        return TryGetRouteValue(parameter, routeValues, out var value) &&
 3197               IsRouteValueAssignable(parameter.ParameterType, value);
 198    }
 199
 200    private static bool TryGetRouteValue(
 201        TelegramErrorHandlerParameterDescriptor parameter,
 202        IReadOnlyDictionary<string, object?> routeValues,
 203        out object? value)
 204    {
 3205        value = null;
 206
 3207        return !string.IsNullOrWhiteSpace(parameter.Name) &&
 3208               routeValues.TryGetValue(parameter.Name, out value);
 209    }
 210
 211    private static bool IsRouteValueAssignable(Type parameterType, object? value)
 212    {
 3213        if (value is null)
 214        {
 0215            return IsNullableRouteValue(parameterType);
 216        }
 217
 3218        var targetType = Nullable.GetUnderlyingType(parameterType) ?? parameterType;
 219
 3220        return targetType.IsInstanceOfType(value);
 221    }
 222
 223    private static bool IsNullableRouteValue(Type parameterType)
 224    {
 0225        return !parameterType.IsValueType ||
 0226               Nullable.GetUnderlyingType(parameterType) is not null;
 227    }
 228}