< Summary

Information
Class: TeleFlow.Telegram.Internal.Handlers.TelegramGeneratedHandlerRegistry
Assembly: TeleFlow.Framework
File(s): /_/src/TeleFlow.Framework/Internal/Handlers/TelegramGeneratedHandlerRegistry.cs
Line coverage
76%
Covered lines: 204
Uncovered lines: 62
Coverable lines: 266
Total lines: 408
Line coverage: 76.6%
Branch coverage
53%
Covered branches: 68
Total branches: 127
Branch coverage: 53.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
RegisterHandler(...)100%11100%
RegisterErrorHandler(...)100%11100%
BuildDescriptors(...)100%22100%
BuildErrorDescriptors(...)100%22100%
get_HandlerTypes()100%11100%
BuildDescriptor(...)83.33%66100%
BuildErrorDescriptor(...)100%11100%
MapAutoAnswerCallback(...)100%22100%
MapKind(...)80%5587.5%
MapRouteKind(...)63.63%141171.42%
MapFilter(...)66.66%121290%
ValidateCustomFilterType(...)41.17%2433443.47%
GetCustomFilterContextTypes(...)25%14853.84%
SupportsContextType(...)50%22100%
MapFilterKind(...)40%1763045.45%
MapParameterKind(...)66.66%6677.77%
MapErrorParameterKind(...)71.42%7780%

File(s)

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

#LineLine coverage
 1namespace TeleFlow.Telegram.Internal.Handlers;
 2
 3internal sealed class TelegramGeneratedHandlerRegistry : ITelegramGeneratedHandlerRegistry
 4{
 365    private readonly List<TelegramGeneratedHandlerDescriptor> _descriptors = [];
 366    private readonly List<TelegramGeneratedErrorHandlerDescriptor> _errorDescriptors = [];
 7
 8    public void RegisterHandler(TelegramGeneratedHandlerDescriptor descriptor)
 9    {
 106210        ArgumentNullException.ThrowIfNull(descriptor);
 11
 106212        _descriptors.Add(descriptor);
 106213    }
 14
 15    public void RegisterErrorHandler(TelegramGeneratedErrorHandlerDescriptor descriptor)
 16    {
 12917        ArgumentNullException.ThrowIfNull(descriptor);
 18
 12919        _errorDescriptors.Add(descriptor);
 12920    }
 21
 22    public IReadOnlyList<TelegramHandlerDescriptor> BuildDescriptors(
 23        int firstRegistrationOrder,
 24        Func<TelegramGeneratedHandlerDescriptor, bool>? predicate = null)
 25    {
 3626        return _descriptors
 106227            .OrderBy(static descriptor => descriptor.RegistrationOrder)
 106228            .Where(descriptor => predicate?.Invoke(descriptor) ?? true)
 83229            .Select((descriptor, index) => BuildDescriptor(descriptor, firstRegistrationOrder + index))
 3630            .ToArray();
 31    }
 32
 33    public IReadOnlyList<TelegramErrorHandlerDescriptor> BuildErrorDescriptors(
 34        int firstRegistrationOrder,
 35        Func<TelegramGeneratedErrorHandlerDescriptor, bool>? predicate = null)
 36    {
 3637        return _errorDescriptors
 12938            .OrderBy(static descriptor => descriptor.RegistrationOrder)
 12939            .Where(descriptor => predicate?.Invoke(descriptor) ?? true)
 10240            .Select((descriptor, index) => BuildErrorDescriptor(descriptor, firstRegistrationOrder + index))
 3641            .ToArray();
 42    }
 43
 2944    public IReadOnlyList<Type> HandlerTypes => _descriptors
 83145        .Select(static descriptor => descriptor.HandlerType)
 10146        .Concat(_errorDescriptors.Select(static descriptor => descriptor.HandlerType))
 2947        .Distinct()
 2948        .ToArray();
 49
 50    private static TelegramHandlerDescriptor BuildDescriptor(
 51        TelegramGeneratedHandlerDescriptor descriptor,
 52        int registrationOrder)
 53    {
 83254        var kind = MapKind(descriptor.Kind);
 83255        var parameters = descriptor.Parameters
 170956            .Select(static parameter => new TelegramHandlerParameterDescriptor(
 170957                parameter.ParameterType,
 170958                MapParameterKind(parameter.Kind),
 170959                parameter.Name))
 83260            .ToArray();
 83261        var routeValues = descriptor.RouteValues.Count > 0
 83262            ? descriptor.RouteValues
 5063                .Select(static value => new TelegramRouteValueDescriptor(
 5064                    value.Name,
 5065                    value.ValueType,
 5066                    value.IsOptional))
 83267                .ToArray()
 83268            : parameters
 155969                .Where(static parameter => parameter.Kind == TelegramHandlerParameterKind.RouteValue)
 2570                .Select(static parameter => new TelegramRouteValueDescriptor(
 2571                    parameter.Name!,
 2572                    Nullable.GetUnderlyingType(parameter.ParameterType) ?? parameter.ParameterType))
 83273                .ToArray();
 83274        var route = new TelegramRouteDescriptor(
 83275            MapRouteKind(descriptor.RouteKind),
 83276            descriptor.RoutePattern ?? descriptor.Command,
 83277            new TelegramCommandPolicy(
 83278                descriptor.CommandPrefixes,
 83279                descriptor.AllowSpaceAfterPrefix,
 83280                descriptor.IgnoreCase,
 83281                descriptor.PrefixMode),
 83282            descriptor.TextFilters
 15083                .Select(static filter => new TelegramTextFilter(filter.Value, filter.Mode, filter.IgnoreCase))
 83284                .ToArray(),
 83285            descriptor.CallbackPayloadType,
 83286            routeValues,
 83287            descriptor.Filters
 57588                .Select(filter => MapFilter(filter, kind))
 83289                .ToArray(),
 83290            descriptor.ChatMemberTransitions
 2591                .Select(static transition => new TelegramChatMemberTransitionDescriptor(
 2592                    transition.OldStatus,
 2593                    transition.NewStatus))
 83294                .ToArray(),
 83295            descriptor.RoleRequirements
 2596                .Select(static requirement => new TelegramRoleRequirementDescriptor(requirement.AllowedStatuses))
 83297                .ToArray());
 98
 83299        return new TelegramHandlerDescriptor(
 832100            descriptor.HandlerType,
 832101            descriptor.MethodName,
 832102            (services, arguments, cancellationToken) =>
 56103                descriptor.Invoker(services, arguments, cancellationToken),
 832104            route,
 832105            registrationOrder,
 832106            descriptor.ModuleName,
 832107            descriptor.States.ToArray(),
 832108            parameters,
 832109            descriptor.SceneName,
 832110            MapAutoAnswerCallback(descriptor.AutoAnswerCallback));
 111    }
 112
 113    private static TelegramErrorHandlerDescriptor BuildErrorDescriptor(
 114        TelegramGeneratedErrorHandlerDescriptor descriptor,
 115        int registrationOrder)
 116    {
 102117        var parameters = descriptor.Parameters
 256118            .Select(static parameter => new TelegramErrorHandlerParameterDescriptor(
 256119                parameter.ParameterType,
 256120                MapErrorParameterKind(parameter.Kind),
 256121                parameter.Name))
 102122            .ToArray();
 123
 102124        return new TelegramErrorHandlerDescriptor(
 102125            descriptor.HandlerType,
 102126            descriptor.MethodName,
 102127            (services, arguments, cancellationToken) =>
 4128                descriptor.Invoker(services, arguments, cancellationToken),
 102129            descriptor.ExceptionType,
 102130            descriptor.TelegramContextType,
 102131            registrationOrder,
 102132            descriptor.ModuleName,
 102133            parameters);
 134    }
 135
 136    private static TelegramAutoAnswerCallbackDescriptor? MapAutoAnswerCallback(
 137        TelegramGeneratedAutoAnswerCallbackDescriptor? descriptor)
 138    {
 832139        return descriptor is null
 832140            ? null
 832141            : new TelegramAutoAnswerCallbackDescriptor(
 832142                descriptor.Enabled,
 832143                descriptor.Text,
 832144                descriptor.ShowAlert);
 145    }
 146
 147    private static TelegramHandlerKind MapKind(TelegramGeneratedHandlerKind kind)
 148    {
 832149        return kind switch
 832150        {
 305151            TelegramGeneratedHandlerKind.Command => TelegramHandlerKind.Command,
 300152            TelegramGeneratedHandlerKind.Message => TelegramHandlerKind.Message,
 202153            TelegramGeneratedHandlerKind.Callback => TelegramHandlerKind.Callback,
 25154            TelegramGeneratedHandlerKind.ChatMember => TelegramHandlerKind.ChatMember,
 0155            _ => throw new InvalidOperationException($"Unsupported generated Telegram handler kind '{kind}'.")
 832156        };
 157    }
 158
 159    private static TelegramRouteKind MapRouteKind(TelegramGeneratedRouteKind kind)
 160    {
 832161        return kind switch
 832162        {
 150163            TelegramGeneratedRouteKind.MessageAny => TelegramRouteKind.MessageAny,
 125164            TelegramGeneratedRouteKind.TextExact => TelegramRouteKind.TextExact,
 255165            TelegramGeneratedRouteKind.CommandExact => TelegramRouteKind.CommandExact,
 25166            TelegramGeneratedRouteKind.TextTemplate => TelegramRouteKind.TextTemplate,
 50167            TelegramGeneratedRouteKind.CommandTemplate => TelegramRouteKind.CommandTemplate,
 0168            TelegramGeneratedRouteKind.TextRegex => TelegramRouteKind.TextRegex,
 0169            TelegramGeneratedRouteKind.CommandRegex => TelegramRouteKind.CommandRegex,
 202170            TelegramGeneratedRouteKind.Callback => TelegramRouteKind.Callback,
 25171            TelegramGeneratedRouteKind.ChatMemberUpdated => TelegramRouteKind.ChatMemberUpdated,
 0172            TelegramGeneratedRouteKind.MyChatMemberUpdated => TelegramRouteKind.MyChatMemberUpdated,
 0173            _ => throw new InvalidOperationException($"Unsupported generated Telegram route kind '{kind}'.")
 832174        };
 175    }
 176
 177    private static TelegramFilterDescriptor MapFilter(
 178        TelegramGeneratedFilterDescriptor descriptor,
 179        TelegramHandlerKind kind)
 180    {
 575181        if (descriptor.Kind == TelegramGeneratedFilterKind.Custom)
 182        {
 50183            if (descriptor.CustomFilterType is null)
 184            {
 0185                throw new InvalidOperationException("Generated custom Telegram filter descriptor must provide a filter t
 186            }
 187
 50188            ValidateCustomFilterType(
 50189                descriptor.CustomFilterType,
 50190                descriptor.CustomFilterContextType,
 50191                descriptor.CustomFilterAttribute,
 50192                kind);
 193
 50194            if (descriptor.CustomFilterAttribute is null)
 195            {
 25196                return descriptor.CustomFilterContextType is null
 25197                    ? new TelegramFilterDescriptor(descriptor.CustomFilterType)
 25198                    : new TelegramFilterDescriptor(
 25199                        descriptor.CustomFilterType,
 25200                        descriptor.CustomFilterContextType);
 201            }
 202
 25203            return descriptor.CustomFilterContextType is null
 25204                ? new TelegramFilterDescriptor(
 25205                    descriptor.CustomFilterType,
 25206                    descriptor.CustomFilterAttribute)
 25207                : new TelegramFilterDescriptor(
 25208                    descriptor.CustomFilterType,
 25209                    descriptor.CustomFilterContextType,
 25210                    descriptor.CustomFilterAttribute);
 211        }
 212
 525213        var filterKind = MapFilterKind(descriptor.Kind);
 214
 525215        if (!TelegramFilterCompatibility.Supports(filterKind, kind))
 216        {
 0217            throw new InvalidOperationException(
 0218                $"Generated {TelegramFilterCompatibility.GetInvalidPlacementMessage(filterKind, kind)}");
 219        }
 220
 525221        return new TelegramFilterDescriptor(
 525222            filterKind,
 525223            descriptor.StringValues.ToArray(),
 525224            descriptor.LongValues.ToArray());
 225    }
 226
 227    private static void ValidateCustomFilterType(
 228        Type filterType,
 229        Type? contextType,
 230        Attribute? attribute,
 231        TelegramHandlerKind kind)
 232    {
 50233        if (filterType.IsInterface ||
 50234            filterType.IsAbstract ||
 50235            filterType.ContainsGenericParameters)
 236        {
 0237            throw new InvalidOperationException(
 0238                $"Generated custom Telegram filter type '{filterType.FullName}' must be a concrete closed type.");
 239        }
 240
 50241        var expectedContextType = kind switch
 50242        {
 0243            TelegramHandlerKind.Callback => typeof(CallbackQueryContext),
 0244            TelegramHandlerKind.ChatMember => typeof(ChatMemberUpdatedContext),
 50245            _ => typeof(MessageContext)
 50246        };
 247
 50248        if (contextType is not null)
 249        {
 25250            if (contextType != expectedContextType &&
 25251                contextType != typeof(TelegramUpdateContext))
 252            {
 0253                throw new InvalidOperationException(
 0254                    $"Generated custom Telegram filter type '{filterType.FullName}' is not compatible with {expectedCont
 255            }
 256
 25257            var requiredContract = attribute is null
 25258                ? typeof(ITelegramFilter<>).MakeGenericType(contextType)
 25259                : typeof(ITelegramFilter<,>).MakeGenericType(contextType, attribute.GetType());
 260
 25261            if (!requiredContract.IsAssignableFrom(filterType))
 262            {
 0263                throw new InvalidOperationException(
 0264                    attribute is null
 0265                        ? $"Generated custom Telegram filter type '{filterType.FullName}' must implement ITelegramFilter
 0266                        : $"Generated parameterized custom Telegram filter type '{filterType.FullName}' must implement I
 267            }
 268
 25269            return;
 270        }
 271
 25272        if (attribute is null)
 273        {
 25274            var contextTypes = GetCustomFilterContextTypes(filterType, attributeType: null);
 275
 25276            if (contextTypes.Length == 0)
 277            {
 0278                throw new InvalidOperationException(
 0279                    $"Generated custom Telegram filter type '{filterType.FullName}' must implement ITelegramFilter<TCont
 280            }
 281
 25282            if (!SupportsContextType(contextTypes, expectedContextType))
 283            {
 0284                throw new InvalidOperationException(
 0285                    $"Generated custom Telegram filter type '{filterType.FullName}' is not compatible with {expectedCont
 286            }
 287
 25288            return;
 289        }
 290
 0291        var attributeType = attribute.GetType();
 292
 0293        if (attributeType.IsGenericType)
 294        {
 0295            throw new InvalidOperationException(
 0296                $"Generated custom Telegram filter attribute type '{attributeType.FullName}' must be non-generic.");
 297        }
 298
 0299        var typedContextTypes = GetCustomFilterContextTypes(filterType, attributeType);
 300
 0301        if (typedContextTypes.Length == 0)
 302        {
 0303            throw new InvalidOperationException(
 0304                $"Generated parameterized custom Telegram filter type '{filterType.FullName}' must implement ITelegramFi
 305        }
 306
 0307        if (!SupportsContextType(typedContextTypes, expectedContextType))
 308        {
 0309            throw new InvalidOperationException(
 0310                $"Generated custom Telegram filter type '{filterType.FullName}' is not compatible with {expectedContextT
 311        }
 0312    }
 313
 314    private static Type[] GetCustomFilterContextTypes(
 315        Type filterType,
 316        Type? attributeType)
 317    {
 25318        var interfaces = filterType.GetInterfaces();
 319
 25320        if (attributeType is null)
 321        {
 25322            return interfaces
 25323                .Where(static type => type.IsGenericType &&
 25324                                      type.GetGenericTypeDefinition() == typeof(ITelegramFilter<>))
 25325                .Select(static type => type.GetGenericArguments()[0])
 25326                .ToArray();
 327        }
 328
 0329        return interfaces
 0330            .Where(type => type.IsGenericType &&
 0331                           type.GetGenericTypeDefinition() == typeof(ITelegramFilter<,>) &&
 0332                           type.GetGenericArguments()[1] == attributeType)
 0333            .Select(static type => type.GetGenericArguments()[0])
 0334            .ToArray();
 335    }
 336
 337    private static bool SupportsContextType(
 338        IReadOnlyCollection<Type> contextTypes,
 339        Type expectedContextType)
 340    {
 25341        return contextTypes.Contains(expectedContextType) ||
 25342               contextTypes.Contains(typeof(TelegramUpdateContext));
 343    }
 344
 345    private static TelegramFilterKind MapFilterKind(TelegramGeneratedFilterKind kind)
 346    {
 525347        return kind switch
 525348        {
 50349            TelegramGeneratedFilterKind.ChatType => TelegramFilterKind.ChatType,
 50350            TelegramGeneratedFilterKind.ChatId => TelegramFilterKind.ChatId,
 25351            TelegramGeneratedFilterKind.ChatUsername => TelegramFilterKind.ChatUsername,
 50352            TelegramGeneratedFilterKind.FromUser => TelegramFilterKind.FromUser,
 25353            TelegramGeneratedFilterKind.HasText => TelegramFilterKind.HasText,
 0354            TelegramGeneratedFilterKind.HasPhoto => TelegramFilterKind.HasPhoto,
 0355            TelegramGeneratedFilterKind.HasDocument => TelegramFilterKind.HasDocument,
 25356            TelegramGeneratedFilterKind.HasCaption => TelegramFilterKind.HasCaption,
 0357            TelegramGeneratedFilterKind.HasVideo => TelegramFilterKind.HasVideo,
 0358            TelegramGeneratedFilterKind.HasAnimation => TelegramFilterKind.HasAnimation,
 0359            TelegramGeneratedFilterKind.HasAudio => TelegramFilterKind.HasAudio,
 0360            TelegramGeneratedFilterKind.HasVoice => TelegramFilterKind.HasVoice,
 0361            TelegramGeneratedFilterKind.HasVideoNote => TelegramFilterKind.HasVideoNote,
 0362            TelegramGeneratedFilterKind.HasSticker => TelegramFilterKind.HasSticker,
 0363            TelegramGeneratedFilterKind.HasContact => TelegramFilterKind.HasContact,
 0364            TelegramGeneratedFilterKind.HasLocation => TelegramFilterKind.HasLocation,
 0365            TelegramGeneratedFilterKind.HasVenue => TelegramFilterKind.HasVenue,
 0366            TelegramGeneratedFilterKind.HasPoll => TelegramFilterKind.HasPoll,
 0367            TelegramGeneratedFilterKind.HasDice => TelegramFilterKind.HasDice,
 25368            TelegramGeneratedFilterKind.FromBot => TelegramFilterKind.FromBot,
 0369            TelegramGeneratedFilterKind.FromPremiumUser => TelegramFilterKind.FromPremiumUser,
 0370            TelegramGeneratedFilterKind.IsReply => TelegramFilterKind.IsReply,
 0371            TelegramGeneratedFilterKind.ReplyToBot => TelegramFilterKind.ReplyToBot,
 25372            TelegramGeneratedFilterKind.MessageThreadId => TelegramFilterKind.MessageThreadId,
 25373            TelegramGeneratedFilterKind.HasMessageThread => TelegramFilterKind.HasMessageThread,
 25374            TelegramGeneratedFilterKind.HasCallbackData => TelegramFilterKind.HasCallbackData,
 175375            TelegramGeneratedFilterKind.CallbackDataPrefix => TelegramFilterKind.CallbackDataPrefix,
 25376            TelegramGeneratedFilterKind.SenderChatType => TelegramFilterKind.SenderChatType,
 0377            TelegramGeneratedFilterKind.Custom => throw new InvalidOperationException("Custom generated Telegram filters
 0378            _ => throw new InvalidOperationException($"Unsupported generated Telegram filter kind '{kind}'.")
 525379        };
 380    }
 381
 382    private static TelegramHandlerParameterKind MapParameterKind(TelegramGeneratedHandlerParameterKind kind)
 383    {
 1709384        return kind switch
 1709385        {
 832386            TelegramGeneratedHandlerParameterKind.Context => TelegramHandlerParameterKind.Context,
 26387            TelegramGeneratedHandlerParameterKind.CallbackPayload => TelegramHandlerParameterKind.CallbackPayload,
 75388            TelegramGeneratedHandlerParameterKind.RouteValue => TelegramHandlerParameterKind.RouteValue,
 776389            TelegramGeneratedHandlerParameterKind.Service => TelegramHandlerParameterKind.Service,
 0390            TelegramGeneratedHandlerParameterKind.CancellationToken => TelegramHandlerParameterKind.CancellationToken,
 0391            _ => throw new InvalidOperationException($"Unsupported generated Telegram handler parameter kind '{kind}'.")
 1709392        };
 393    }
 394
 395    private static TelegramErrorHandlerParameterKind MapErrorParameterKind(TelegramGeneratedErrorHandlerParameterKind ki
 396    {
 256397        return kind switch
 256398        {
 26399            TelegramGeneratedErrorHandlerParameterKind.ErrorContext => TelegramErrorHandlerParameterKind.ErrorContext,
 26400            TelegramGeneratedErrorHandlerParameterKind.TelegramContext => TelegramErrorHandlerParameterKind.TelegramCont
 102401            TelegramGeneratedErrorHandlerParameterKind.Exception => TelegramErrorHandlerParameterKind.Exception,
 0402            TelegramGeneratedErrorHandlerParameterKind.RouteValue => TelegramErrorHandlerParameterKind.RouteValue,
 101403            TelegramGeneratedErrorHandlerParameterKind.Service => TelegramErrorHandlerParameterKind.Service,
 1404            TelegramGeneratedErrorHandlerParameterKind.CancellationToken => TelegramErrorHandlerParameterKind.Cancellati
 0405            _ => throw new InvalidOperationException($"Unsupported generated Telegram error handler parameter kind '{kin
 256406        };
 407    }
 408}