Skip to content

Command handler matcher

Mikhail edited this page Jan 11, 2021 · 2 revisions

Command handler matcher

For matching commands (choosing which should be called or not), framework has ICommandHandlerMatcher<TEntity, TCommandHandlerAttribute> which does this work, where:

  • TEntity - entity type. E.g. Message, CallbackQuery, InlineQuery.
  • TCommandHandlerAttribute - an attribute, which is used to mark command method should be registered. Could be an abstract class. E.g. we have CommandHandlerAttributeBase, and attributes MessageHandlerAttribute and CallbackQueryHandlerAttribute which inherites CommandHandlerAttributeBase.

Command handler matcher could be set in a few ways:

  • Pass manually created instance.
  • Pass type, which describes it. In this case, matcher will be activated through reflection.

Command handler matcher configuring example:

IUpdateEntityProcessorBuilder<Message, PositionedEntityHandlerAttribute> updateEntityProcessorBuilder = ...;

updateEntityProcessorBuilder
    .WithCommandHandler<Message, 
        PositionedEntityHandlerAttribute, 
        MessageCommandHandlerAttribute, 
        MessageCommandHandlerMatcher>
    (new MessageUpdateEntityHandlerAttribute(
            5,
            DefaultProcessorId),
        ConfigureMessageCommandProcessorBuilder);

Where:

  • Message - entity type.
  • PositionedEntityHandlerAttribute - attribute (could be abstract or serve as a base class for other attributes), which shows, in which order entity handlers should be invoked.
  • MessageCommandHandlerAttribute - attribute (could be abstract or serve as a base class for other attributes), which shows, in which order command handlers should be invoked.
  • MessageCommandHandlerMatcher type of our command handler matcher we want to set.

Note, you could get command handler descriptors collection on command handler matcher activation step (into constructor), just set one of the arguments to type IEnumerable<IActivatedCommandHandlerDescriptor<MessageCommandHandlerAttribute>>

Here is a simple example of command handler matcher related to Message processing:

Note, some types will be used from Creating your first command page.

public class MessageCommandHandlerMatcher : ICommandHandlerMatcher<Message, MessageCommandHandlerAttribute>
{
    public MessageCommandHandlerMatcher(
        IEnumerable<IActivatedCommandHandlerDescriptor<MessageCommandHandlerAttribute>> descriptors) //Collection of all command descriptors registered to the current enitty type.
    {
        
    }

    public ValueTask<ICommandHandlerMatchingResult<Message>> CanHandle(IHandlerContext<Message> handlerContext,
        ICommandHandlerDescriptor<MessageCommandHandlerAttribute> commandHandlerDescriptor,
        IContainer serviceContainer)
    {
        string commandFormat = commandHandlerDescriptor.Attribute.CommandFormat;

        string messageText = handlerContext.Entity.Text;
        
        bool isMatches = Regex.IsMatch(commandFormat, messageText);
        
        if (isMatches)
        {
            ICommandContext<Message> messageCommandContext = new CommandContext<Message>(
                Enumerable.Empty<ICommandParameterValue>(),
                handlerContext);

            return new ValueTask<ICommandHandlerMatchingResult<Message>>(
                CommandHandlerMatchingResult<Message>.Success(messageCommandContext));
        }

        return new ValueTask<ICommandHandlerMatchingResult<Message>>(
            CommandHandlerMatchingResult<Message>.Fail());
    }
}

This command handler matcher matches commands without parsing specified parameters, more detailed example with parsing, which is described below, shown here.

Parsing command parameters

Overview

So, how to parse command parameters, for example for the command random {MinValue} {MaxValue}, where both of MinValue and MaxValue are numbers?

Here comes this framework part, which comes from Spire.Core.Commands.Parsing, and serves for parameter parsing in type: {ParameterName:Type:Optionality?Options}. For example: {MinValue:number:optional?min=0&max=10}.

Where:

  • MinValue - parameter name.
  • number - parameter type. All available types could be found here.
  • optional - marks that parameter could be optional. Takes values true or optional.
  • min=0 and max=10 - parameter options, which should be checked on the parsing step. You could specify option handlers on the configuration step, which will be described below.

Configuration

For creating command parser could be used 'ICommandParserBuilder'. Here's simple example of creating and configuring it:

ICommandParserBuilder commandParserBuilder = CommandParserBuilder.New;

commandParserBuilder.WithDefaults();

commandParserBuilder.WithParameterOptionHandler(new CommandParameterOptionHandler<int, int>(
    "min",
    (min, value) => value >= min)); //Adding option handler for 'min' option.

commandParserBuilder.WithParameterOptionHandler(new CommandParameterOptionHandler<int, int>(
    "max",
    (max, value) => value <= max)); //Adding option handler for 'max' option.

ICommandParser commandParser = commandParserBuilder.Build();

Basically, option handler registration supports these three ways:

  • Adding option handler without trying to parse option value, and current parameter value to the specified type.
  • Adding option handler with trying to parse only option value, and current parameter value will be string.
  • Adding option handler with trying to parse both of option and parameter values.

E.g. This code will try to parse both of option and parameter value and convert it to int:

commandParserBuilder.WithParameterOptionHandler(new CommandParameterOptionHandler<int, int>(
    "min",
    (min, value) => value >= min));

And here, for example, we need to check parameter length, so, this code will convert only option value:

commandParserBuilder.WithParameterOptionHandler(new CommandParameterOptionHandler<int>(
    "minParameterLength",
    (min, value) => value.Length >= min));