Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: improvements and logs #15

Merged
merged 10 commits into from
Aug 22, 2024
2 changes: 1 addition & 1 deletion src/Navigator/Actions/BotAction.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Navigator.Actions;

/// <summary>
/// A <see cref="BotAction" /> is a representation of an action that can be executed by a navigator bot. It is used to encapsulate a
/// A <see cref="BotAction" /> is ah representation of an action that can be executed by a navigator bot. It is used to encapsulate a
/// condition and a handler. The condition is a delegate that is checked at runtime and if it evaluates to true, the handler is executed.
/// The condition delegate should return a boolean or a Task that resolves to a boolean. The handler delegate should return void or a Task
/// that resolves to void.
Expand Down
12 changes: 12 additions & 0 deletions src/Navigator/Actions/BotActionInformation.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Telegram.Bot.Types.Enums;

namespace Navigator.Actions;

/// <summary>
Expand All @@ -10,6 +12,11 @@ public record BotActionInformation
/// </summary>
public required UpdateCategory Category;

/// <summary>
/// The <see cref="ChatAction" /> associtated with the <see cref="BotAction" />. Optional.
/// </summary>
public required ChatAction? ChatAction;

/// <summary>
/// The input types of the condition delegate of the <see cref="BotAction" />.
/// </summary>
Expand All @@ -25,6 +32,11 @@ public record BotActionInformation
/// </summary>
public required Type[] HandlerInputTypes;

/// <summary>
/// The name of the <see cref="BotAction" />. If no name is set, the id is used.
/// </summary>
public required string Name;

/// <summary>
/// The priority of the <see cref="BotAction" />. Optional.
/// </summary>
Expand Down
104 changes: 77 additions & 27 deletions src/Navigator/Actions/Builder/BotActionBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,43 +1,32 @@
using Telegram.Bot.Types.Enums;

namespace Navigator.Actions.Builder;

/// <summary>
/// Builder for <see cref="BotAction" />.
/// </summary>
public class BotActionBuilder
{
private readonly Delegate _condition;
private readonly Type[] _conditionInputTypes;
private readonly Delegate _handler;
private readonly Type[] _handlerInputTypes;
private readonly Guid _id;

/// <summary>
/// Initializes a new instance of the <see cref="BotActionBuilder" /> class.
/// </summary>
/// <param name="condition">
/// A delegate representing the condition under which the handler should be invoked.
/// Must return <see cref="bool" /> or <see cref="Task{TResult}" /> where TResult is <see cref="bool" />.
/// </param>
/// <param name="handler">
/// A delegate representing the action to take when the condition is met.
/// </param>
public BotActionBuilder(Delegate condition, Delegate handler)
public BotActionBuilder()
{
_id = Guid.NewGuid();

if (!(condition.Method.ReturnType != typeof(Task<bool>) || condition.Method.ReturnType != typeof(bool)))
throw new NavigatorException("The condition delegate must return Task<bool> or bool");

_condition = condition;
_conditionInputTypes = condition.Method.GetParameters().Select(info => info.ParameterType).ToArray();
_handler = handler;
_handlerInputTypes = handler.Method.GetParameters().Select(info => info.ParameterType).ToArray();
Priority = Actions.Priority.Default;
}

private string? Name { get; set; }
private Delegate? Condition { get; set; }
private Type[] ConditionInputTypes { get; set; } = null!;
private Delegate? Handler { get; set; }
private Type[] HandlerInputTypes { get; set; } = null!;
private UpdateCategory Category { get; set; } = null!;
private ushort Priority { get; set; }
private TimeSpan? Cooldown { get; set; }
private ChatAction? ChatAction { get; set; }

/// <summary>
/// Builds the bot action.
Expand All @@ -47,14 +36,74 @@ public BotAction Build()
{
var information = new BotActionInformation
{
ChatAction = ChatAction,
Category = Category,
ConditionInputTypes = _conditionInputTypes,
HandlerInputTypes = _handlerInputTypes,
ConditionInputTypes = ConditionInputTypes,
HandlerInputTypes = HandlerInputTypes,
Name = Name ?? $"{_id}",
Priority = Priority,
Cooldown = Cooldown
};

return new BotAction(_id, information, _condition, _handler);
if (Condition is null || Handler is null)
throw new NavigatorException("Both condition and handler must be set");

if (!(Condition.Method.ReturnType != typeof(Task<bool>) || Condition.Method.ReturnType != typeof(bool)))
throw new NavigatorException("The condition delegate must return Task<bool> or bool");

if (Category is null)
throw new NavigatorException("The category must be set");

return new BotAction(_id, information, Condition, Handler);
}

/// <summary>
/// Sets the name of the <see cref="BotAction" />.
/// </summary>
/// <param name="name">The name to be set.</param>
/// <returns>An instance of <see cref="BotActionBuilder" /> to be able to continue configuring the <see cref="BotAction" />.</returns>
public BotActionBuilder WithName(string name)
{
Name = name;

return this;
}

/// <summary>
/// Sets the condition of the <see cref="BotAction" />.
/// </summary>
/// <param name="condition">The condition to be set.</param>
/// <returns>An instance of <see cref="BotActionBuilder" /> to be able to continue configuring the <see cref="BotAction" />.</returns>
public BotActionBuilder SetCondition(Delegate condition)
{
Condition = condition;
ConditionInputTypes = condition.Method.GetParameters().Select(info => info.ParameterType).ToArray();

return this;
}

/// <summary>
/// Sets the handler of the <see cref="BotAction" />.
/// </summary>
/// <param name="handler">The handler to be set.</param>
/// <returns>An instance of <see cref="BotActionBuilder" /> to be able to continue configuring the <see cref="BotAction" />.</returns>
public BotActionBuilder SetHandler(Delegate handler)
{
Handler = handler;
HandlerInputTypes = handler.Method.GetParameters().Select(info => info.ParameterType).ToArray();

return this;
}

/// <summary>
/// Sets the <see cref="UpdateCategory" /> of the <see cref="BotAction" />.
/// </summary>
/// <param name="category">The <see cref="UpdateCategory" /> to be set.</param>
/// <returns>An instance of <see cref="BotActionBuilder" /> to be able to continue configuring the <see cref="BotAction" />.</returns>
public BotActionBuilder SetCategory(UpdateCategory category)
{
Category = category;
return this;
}

/// <summary>
Expand All @@ -80,13 +129,14 @@ public BotActionBuilder WithCooldown(TimeSpan cooldown)
}

/// <summary>
/// Sets the <see cref="UpdateCategory" /> of the <see cref="BotAction" />.
/// Sets the <see cref="ChatAction" /> of the <see cref="BotAction" />.
/// </summary>
/// <param name="category">The <see cref="UpdateCategory" /> to be set.</param>
/// <param name="chatAction">The <see cref="ChatAction" /> to be set.</param>
/// <returns>An instance of <see cref="BotActionBuilder" /> to be able to continue configuring the <see cref="BotAction" />.</returns>
public BotActionBuilder SetCategory(UpdateCategory category)
public BotActionBuilder WithChatAction(ChatAction chatAction)
{
Category = category;
ChatAction = chatAction;

return this;
}
}
36 changes: 29 additions & 7 deletions src/Navigator/Catalog/Factory/BotActionCatalogFactory.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.Extensions.Logging;
using Navigator.Actions;
using Navigator.Actions.Builder;
using Telegram.Bot.Types.Enums;
Expand All @@ -9,6 +10,17 @@ namespace Navigator.Catalog.Factory;
/// </summary>
public class BotActionCatalogFactory
{
private readonly ILogger<BotActionCatalogFactory> _logger;

/// <summary>
/// Initializes a new instance of the <see cref="BotActionCatalogFactory" /> class.
/// </summary>
/// <param name="logger">An instance of <see cref="ILogger{TCategoryName}" />.</param>
public BotActionCatalogFactory(ILogger<BotActionCatalogFactory> logger)
{
_logger = logger;
}

private List<BotActionBuilder> Actions { get; } = [];
private BotActionCatalog? Catalog { get; set; }

Expand All @@ -22,12 +34,14 @@ public class BotActionCatalogFactory
/// <param name="handler">
/// A delegate representing the action to take when the condition is met.
/// </param>
public BotActionBuilder OnUpdate(Delegate condition, Delegate handler)
public BotActionBuilder OnUpdate(Delegate condition, Delegate? handler = default)
{
var id = Guid.NewGuid();
var actionBuilder = new BotActionBuilder(condition, handler);
var actionBuilder = new BotActionBuilder();

actionBuilder.SetCategory(new UpdateCategory(nameof(UpdateType), nameof(UpdateType.Unknown)));
actionBuilder
.SetCondition(condition)
.SetHandler(handler ?? (Action)(() => { }))
.SetCategory(new UpdateCategory(nameof(UpdateType), nameof(UpdateType.Unknown)));

Actions.Add(actionBuilder);

Expand All @@ -50,9 +64,17 @@ public BotActionCatalog Retrieve()
/// </summary>
private void Build()
{
var actions = Actions
.Select(actionBuilder => actionBuilder.Build())
.ToList();
_logger.LogInformation("Building BotActionCatalog with {ActionsCount} actions", Actions.Count);

var actions = new List<BotAction>();

foreach (var builtAction in Actions.Select(actionBuilder => actionBuilder.Build()))
{
_logger.LogDebug("Built action {ActionName} with priority {Priority} for category {Category}",
builtAction.Information.Name, builtAction.Information.Priority, builtAction.Information.Category);

actions.Add(builtAction);
}

Catalog = new BotActionCatalog(actions);
}
Expand Down
Loading
Loading