< Summary

Information
Class: TeleFlow.Telegram.Internal.Handlers.TelegramErrorHandlerDescriptorBuilder
Assembly: TeleFlow.Framework
File(s): /_/src/TeleFlow.Framework/Internal/Handlers/TelegramErrorHandlerDescriptorBuilder.cs
Line coverage
82%
Covered lines: 86
Uncovered lines: 18
Coverable lines: 104
Total lines: 229
Line coverage: 82.6%
Branch coverage
81%
Covered branches: 52
Total branches: 64
Branch coverage: 81.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Build(...)75%201675%
GetDeclaredCandidateMethods(...)100%11100%
HasInheritedErrorMethods(...)100%11100%
GetModuleName(...)100%22100%
BuildParameterDescriptors(...)81.81%232288.88%
ValidateExceptionParameter(...)66.66%12645.45%
IsRouteValueParameterType(...)83.33%66100%
ValidateReturnType(...)100%66100%
IsErrorHandlingTaskResult(...)100%44100%
CreateSignatureException(...)50%22100%

File(s)

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

#LineLine coverage
 1using System.Reflection;
 2using TeleFlow.Annotations;
 3
 4namespace TeleFlow.Telegram.Internal.Handlers;
 5
 6internal static class TelegramErrorHandlerDescriptorBuilder
 7{
 8    public static IReadOnlyList<TelegramErrorHandlerDescriptor> Build(
 9        Type handlerType,
 10        int firstRegistrationOrder)
 11    {
 33212        ArgumentNullException.ThrowIfNull(handlerType);
 13
 33214        if (handlerType.IsAbstract || handlerType.IsInterface)
 15        {
 016            throw new InvalidOperationException(
 017                $"Telegram error handler type '{handlerType.FullName}' must be a concrete class.");
 18        }
 19
 33220        var registrationOrder = firstRegistrationOrder;
 33221        var descriptors = new List<TelegramErrorHandlerDescriptor>();
 33222        var moduleName = GetModuleName(handlerType);
 23
 133724        foreach (var method in GetDeclaredCandidateMethods(handlerType))
 25        {
 33726            var attributes = method.GetCustomAttributes<ErrorAttribute>(inherit: true).ToArray();
 27
 33728            if (attributes.Length == 0)
 29            {
 30                continue;
 31            }
 32
 2533            ValidateReturnType(method);
 34
 9635            foreach (var attribute in attributes)
 36            {
 2437                var exceptionType = attribute.ExceptionType;
 38
 2439                if (exceptionType is not null && !typeof(Exception).IsAssignableFrom(exceptionType))
 40                {
 041                    throw CreateSignatureException(
 042                        method,
 043                        $"{nameof(ErrorAttribute)} exception type must derive from {nameof(Exception)}.");
 44                }
 45
 2446                var parameters = BuildParameterDescriptors(method, exceptionType, out var telegramContextType);
 47
 2448                descriptors.Add(new TelegramErrorHandlerDescriptor(
 2449                    handlerType,
 2450                    method,
 2451                    exceptionType,
 2452                    telegramContextType,
 2453                    registrationOrder,
 2454                    moduleName,
 2455                    parameters));
 2456                registrationOrder++;
 57            }
 58        }
 59
 33160        if (HasInheritedErrorMethods(handlerType))
 61        {
 062            throw new InvalidOperationException(
 063                $"Telegram error handler type '{handlerType.FullName}' inherits error handler methods. " +
 064                "Inherited error handler methods must be overridden in the concrete handler type for generated registrat
 65        }
 66
 33167        return descriptors;
 68    }
 69
 70    private static IEnumerable<MethodInfo> GetDeclaredCandidateMethods(Type handlerType)
 71    {
 33272        return handlerType
 33273            .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
 33774            .Where(static method => !method.IsSpecialName)
 66975            .OrderBy(static method => method.MetadataToken);
 76    }
 77
 78    private static bool HasInheritedErrorMethods(Type handlerType)
 79    {
 33180        return handlerType
 33181            .GetMethods(BindingFlags.Instance | BindingFlags.Public)
 166082            .Where(method => method.DeclaringType != handlerType)
 165583            .Any(static method => method.GetCustomAttribute<ErrorAttribute>(inherit: true) is not null);
 84    }
 85
 86    private static string? GetModuleName(Type handlerType)
 87    {
 33288        return handlerType.GetCustomAttribute<TelegramModuleAttribute>(inherit: false)?.Name;
 89    }
 90
 91    private static List<TelegramErrorHandlerParameterDescriptor> BuildParameterDescriptors(
 92        MethodInfo method,
 93        Type? exceptionType,
 94        out Type? telegramContextType)
 95    {
 2496        var parameters = method.GetParameters();
 97
 8198        if (parameters.Count(static parameter => parameter.ParameterType == typeof(TelegramErrorContext)) > 1)
 99        {
 0100            throw CreateSignatureException(method, $"An error handler method can declare at most one {nameof(TelegramErr
 101        }
 102
 81103        if (parameters.Count(static parameter => parameter.ParameterType == typeof(CancellationToken)) > 1)
 104        {
 0105            throw CreateSignatureException(method, "An error handler method can declare at most one CancellationToken pa
 106        }
 107
 24108        var exceptionParameters = parameters
 57109            .Where(static parameter => typeof(Exception).IsAssignableFrom(parameter.ParameterType))
 24110            .ToArray();
 111
 24112        if (exceptionParameters.Length > 1)
 113        {
 0114            throw CreateSignatureException(method, "An error handler method can declare at most one exception parameter.
 115        }
 116
 24117        var contextParameters = parameters
 57118            .Where(static parameter => typeof(TelegramUpdateContext).IsAssignableFrom(parameter.ParameterType))
 24119            .ToArray();
 120
 24121        if (contextParameters.Length > 1)
 122        {
 0123            throw CreateSignatureException(method, "An error handler method can declare at most one Telegram context par
 124        }
 125
 24126        telegramContextType = contextParameters.FirstOrDefault()?.ParameterType;
 127
 24128        var descriptors = new List<TelegramErrorHandlerParameterDescriptor>(parameters.Length);
 129
 162130        foreach (var parameter in parameters)
 131        {
 57132            if (parameter.ParameterType == typeof(TelegramErrorContext))
 133            {
 3134                descriptors.Add(new TelegramErrorHandlerParameterDescriptor(parameter, TelegramErrorHandlerParameterKind
 3135                continue;
 136            }
 137
 54138            if (parameter.ParameterType == typeof(CancellationToken))
 139            {
 2140                descriptors.Add(new TelegramErrorHandlerParameterDescriptor(parameter, TelegramErrorHandlerParameterKind
 2141                continue;
 142            }
 143
 52144            if (typeof(Exception).IsAssignableFrom(parameter.ParameterType))
 145            {
 24146                ValidateExceptionParameter(method, exceptionType, parameter);
 24147                descriptors.Add(new TelegramErrorHandlerParameterDescriptor(parameter, TelegramErrorHandlerParameterKind
 24148                continue;
 149            }
 150
 28151            if (typeof(TelegramUpdateContext).IsAssignableFrom(parameter.ParameterType))
 152            {
 3153                descriptors.Add(new TelegramErrorHandlerParameterDescriptor(parameter, TelegramErrorHandlerParameterKind
 3154                continue;
 155            }
 156
 25157            if (IsRouteValueParameterType(parameter.ParameterType))
 158            {
 3159                descriptors.Add(new TelegramErrorHandlerParameterDescriptor(parameter, TelegramErrorHandlerParameterKind
 3160                continue;
 161            }
 162
 22163            descriptors.Add(new TelegramErrorHandlerParameterDescriptor(parameter, TelegramErrorHandlerParameterKind.Ser
 164        }
 165
 24166        return descriptors;
 167    }
 168
 169    private static void ValidateExceptionParameter(
 170        MethodInfo method,
 171        Type? exceptionType,
 172        ParameterInfo parameter)
 173    {
 24174        if (exceptionType is null)
 175        {
 5176            if (parameter.ParameterType != typeof(Exception))
 177            {
 0178                throw CreateSignatureException(
 0179                    method,
 0180                    $"Catch-all {nameof(ErrorAttribute)} methods must bind exception parameters as {nameof(Exception)}."
 181            }
 182
 5183            return;
 184        }
 185
 19186        if (!parameter.ParameterType.IsAssignableFrom(exceptionType))
 187        {
 0188            throw CreateSignatureException(
 0189                method,
 0190                $"Exception parameter '{parameter.Name}' must be assignable from {exceptionType.Name}.");
 191        }
 19192    }
 193
 194    private static bool IsRouteValueParameterType(Type type)
 195    {
 25196        var valueType = Nullable.GetUnderlyingType(type) ?? type;
 197
 25198        return valueType == typeof(string) ||
 25199               valueType == typeof(int) ||
 25200               valueType == typeof(long);
 201    }
 202
 203    private static void ValidateReturnType(MethodInfo method)
 204    {
 25205        if (method.ReturnType == typeof(TelegramErrorHandlingResult) ||
 25206            IsErrorHandlingTaskResult(method.ReturnType, typeof(Task<>)) ||
 25207            IsErrorHandlingTaskResult(method.ReturnType, typeof(ValueTask<>)))
 208        {
 24209            return;
 210        }
 211
 1212        throw CreateSignatureException(
 1213            method,
 1214            $"A Telegram error handler method must return {nameof(TelegramErrorHandlingResult)}, Task<{nameof(TelegramEr
 215    }
 216
 217    private static bool IsErrorHandlingTaskResult(Type returnType, Type taskType)
 218    {
 8219        return returnType.IsGenericType &&
 8220               returnType.GetGenericTypeDefinition() == taskType &&
 8221               returnType.GetGenericArguments()[0] == typeof(TelegramErrorHandlingResult);
 222    }
 223
 224    private static InvalidOperationException CreateSignatureException(MethodInfo method, string reason)
 225    {
 1226        return new InvalidOperationException(
 1227            $"Invalid Telegram error handler signature '{method.DeclaringType?.FullName}.{method.Name}': {reason}");
 228    }
 229}