* Added "INamedFilter" to tagging filters. Required for fallbacking system

* AnonymousCompiledFilter now has undependent realization
* All internal filters now implement INamedFilter interface
* Added new static methods to "Filter" class
* Added Filters fallbacking system
* Added "FiltersFallback" method in "UpdatehandlerBase"
* Changed filters validating system to be able to report failed filters
* Added "FiltersFallbackReport" class
* Added "FormReport" Property to "UpdateHandlerAttributeBase"
* Version incremented
This commit is contained in:
2025-08-08 17:04:56 +04:00
parent e790f31495
commit bf66431089
21 changed files with 338 additions and 101 deletions
@@ -15,7 +15,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Version>1.0.11</Version>
<Version>1.0.12</Version>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -16,7 +16,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Version>1.0.11</Version>
<Version>1.0.12</Version>
</PropertyGroup>
<ItemGroup>
+2 -1
View File
@@ -100,12 +100,13 @@ namespace Telegrator.Hosting
/// Adds a Microsoft.Extensions.Logging adapter to Alligator using a logger factory.
/// </summary>
/// <param name="host"></param>
public static void AddLoggingAdapter(this ITelegramBotHost host)
public static ITelegramBotHost AddLoggingAdapter(this ITelegramBotHost host)
{
ILoggerFactory loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
ILogger logger = loggerFactory.CreateLogger("Telegrator");
MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger);
Alligator.AddAdapter(adapter);
return host;
}
}
+1 -1
View File
@@ -121,7 +121,7 @@ namespace Telegrator.Tests.Filters
var filter2 = Filter<Update>.If(_ => true);
var filter3 = Filter<Update>.If(_ => false);
var compiledFilter = CompiledFilter<Update>.Compile(filter1, filter2, filter3);
var compiledFilter = new CompiledFilter<Update>(filter1, filter2, filter3);
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
// Act
@@ -20,7 +20,7 @@ namespace Telegrator.Attributes.Components
/// <summary>
/// Gets the <see cref="IFilter{T}"/> that <see cref="UpdateHandlerBase"/> processing
/// </summary>
public abstract Filter<Update> AnonymousFilter { get; protected set; }
public abstract IFilter<Update> AnonymousFilter { get; protected set; }
/// <summary>
/// Gets or sets the filter modifiers that affect how this filter is combined with others.
@@ -35,6 +35,8 @@ namespace Telegrator.Attributes.Components
/// </summary>
public int Priority { get; set; }
public bool FormReport { get; set; }
/// <summary>
/// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/>
/// </summary>
+10 -8
View File
@@ -15,12 +15,12 @@ namespace Telegrator.Attributes
/// <summary>
/// Gets the compiled anonymous filter for this attribute.
/// </summary>
public override Filter<Update> AnonymousFilter { get; protected set; }
public override IFilter<Update> AnonymousFilter { get; protected set; }
/// <summary>
/// Gets the compiled filter logic for the update target.
/// </summary>
public Filter<T> UpdateFilter { get; protected set; }
public IFilter<T> UpdateFilter { get; protected set; }
/// <summary>
/// Empty constructor for internal using
@@ -38,18 +38,20 @@ namespace Telegrator.Attributes
/// <param name="filters">The filters to compose</param>
protected UpdateFilterAttribute(params IFilter<T>[] filters)
{
UpdateFilter = CompiledFilter<T>.Compile(filters);
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget);
string name = GetType().Name;
UpdateFilter = new CompiledFilter<T>(name, filters);
AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget);
}
/// <summary>
/// Initializes the attribute with a precompiled filter for the update target.
/// </summary>
/// <param name="updateFilter">The compiled filter</param>
protected UpdateFilterAttribute(Filter<T> updateFilter)
protected UpdateFilterAttribute(IFilter<T> updateFilter)
{
string name = GetType().Name;
UpdateFilter = updateFilter;
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget);
AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget);
}
/// <summary>
@@ -60,13 +62,13 @@ namespace Telegrator.Attributes
public override sealed bool ProcessModifiers(UpdateFilterAttributeBase? previous)
{
if (Modifiers.HasFlag(FilterModifier.Not))
AnonymousFilter = AnonymousFilter.Not();
AnonymousFilter = Filter<T>.Not(AnonymousFilter);
if (previous is not null)
{
if (previous.Modifiers.HasFlag(FilterModifier.OrNext))
{
AnonymousFilter = previous.AnonymousFilter.Or(AnonymousFilter);
AnonymousFilter = Filter<Update>.Or(previous.AnonymousFilter, AnonymousFilter);
}
}
+2
View File
@@ -70,6 +70,7 @@
Sender
}
/*
/// <summary>
/// Messages from where this filter was originated
/// </summary>
@@ -95,6 +96,7 @@
/// </summary>
Regualr
}
*/
/*
/// <summary>
@@ -1,5 +1,4 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types;
using Telegrator.Logging;
namespace Telegrator.Filters.Components
@@ -7,15 +6,26 @@ namespace Telegrator.Filters.Components
/// <summary>
/// Represents a compiled filter that applies a set of filters to an anonymous target type.
/// </summary>
public sealed class AnonymousCompiledFilter : AnonymousTypeFilter
public class AnonymousCompiledFilter : Filter<Update>, INamedFilter
{
private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction;
private readonly Func<Update, object?> GetFilterringTarget;
private readonly string _name;
public virtual string Name => _name;
/// <summary>
/// Initializes a new instance of the <see cref="AnonymousCompiledFilter"/> class.
/// </summary>
/// <param name="name"></param>
/// <param name="filterAction">The filter action delegate.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
private AnonymousCompiledFilter(Func<FilterExecutionContext<Update>, object, bool> filterAction, Func<Update, object?> getFilterringTarget)
: base(filterAction, getFilterringTarget) { }
private AnonymousCompiledFilter(string name, Func<Update, object?> getFilterringTarget, Func<FilterExecutionContext<Update>, object, bool> filterAction)
{
FilterAction = filterAction;
GetFilterringTarget = getFilterringTarget;
_name = name;
}
/// <summary>
/// Compiles a set of filters into an <see cref="AnonymousCompiledFilter"/> for a specific target type.
@@ -24,11 +34,28 @@ namespace Telegrator.Filters.Components
/// <param name="filters">The list of filters to compile.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousCompiledFilter Compile<T>(IList<IFilter<T>> filters, Func<Update, object?> getFilterringTarget) where T : class
public static AnonymousCompiledFilter Compile<T>(IEnumerable<IFilter<T>> filters, Func<Update, object?> getFilterringTarget) where T : class
{
return new AnonymousCompiledFilter(
(context, filterringTarget) => CanPassInternal(filters, context, filterringTarget),
getFilterringTarget);
string.Join("+", filters.Select(fltr => fltr.GetType().Name)),
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filters, filterringTarget));
}
/// <summary>
/// Compiles a set of filters into an <see cref="AnonymousCompiledFilter"/> for a specific target type.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="name"></param>
/// <param name="filters">The list of filters to compile.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousCompiledFilter Compile<T>(string name, IEnumerable<IFilter<T>> filters, Func<Update, object?> getFilterringTarget) where T : class
{
return new AnonymousCompiledFilter(
name,
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filters, filterringTarget));
}
/// <summary>
@@ -39,16 +66,15 @@ namespace Telegrator.Filters.Components
/// <param name="updateContext">The filter execution context.</param>
/// <param name="filterringTarget">The filtering target.</param>
/// <returns>True if all filters pass; otherwise, false.</returns>
private static bool CanPassInternal<T>(IList<IFilter<T>> filters, FilterExecutionContext<Update> updateContext, object filterringTarget) where T : class
private static bool CanPassInternal<T>(FilterExecutionContext<Update> updateContext, IEnumerable<IFilter<T>> filters, object filterringTarget) where T : class
{
FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
foreach (IFilter<T> filter in filters)
{
if (!filter.CanPass(context))
{
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter)
Alligator.LogDebug("{0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]);
Alligator.LogDebug("{0} filter of {1} didnt pass! (Compiled anonymous)", filter.GetType().Name, context.Data["handler_name"]);
return false;
}
@@ -58,5 +84,22 @@ namespace Telegrator.Filters.Components
return true;
}
/// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<Update> context)
{
try
{
object? filterringTarget = GetFilterringTarget.Invoke(context.Input);
if (filterringTarget == null)
return false;
return FilterAction.Invoke(context, filterringTarget);
}
catch
{
return false;
}
}
}
}
@@ -6,20 +6,25 @@ namespace Telegrator.Filters.Components
/// <summary>
/// Represents a filter that applies a filter action to an anonymous target type extracted from an update.
/// </summary>
public class AnonymousTypeFilter : Filter<Update>
public class AnonymousTypeFilter : Filter<Update>, INamedFilter
{
private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction;
private readonly Func<Update, object?> GetFilterringTarget;
private readonly string _name;
public virtual string Name => _name;
/// <summary>
/// Initializes a new instance of the <see cref="AnonymousTypeFilter"/> class.
/// </summary>
/// <param name="name"></param>
/// <param name="filterAction">The filter action delegate.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
protected AnonymousTypeFilter(Func<FilterExecutionContext<Update>, object, bool> filterAction, Func<Update, object?> getFilterringTarget)
public AnonymousTypeFilter(string name, Func<Update, object?> getFilterringTarget, Func<FilterExecutionContext<Update>, object, bool> filterAction)
{
FilterAction = filterAction;
GetFilterringTarget = getFilterringTarget;
_name = name;
}
/// <summary>
@@ -32,8 +37,25 @@ namespace Telegrator.Filters.Components
public static AnonymousTypeFilter Compile<T>(IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class
{
return new AnonymousTypeFilter(
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget),
getFilterringTarget);
filter.GetType().Name,
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget));
}
/// <summary>
/// Compiles a filter for a specific target type.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="name"></param>
/// <param name="filter">The filter to apply.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousTypeFilter Compile<T>(string name, IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class
{
return new AnonymousTypeFilter(
name,
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget));
}
/// <summary>
@@ -6,27 +6,32 @@ namespace Telegrator.Filters.Components
/// Represents a filter that composes multiple filters and passes only if all of them pass.
/// </summary>
/// <typeparam name="T">The type of the input for the filter.</typeparam>
public class CompiledFilter<T> : Filter<T> where T : class
public class CompiledFilter<T> : Filter<T>, INamedFilter where T : class
{
private readonly IFilter<T>[] Filters;
private readonly string _name;
public virtual string Name => _name;
/// <summary>
/// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class.
/// </summary>
/// <param name="filters">The filters to compose.</param>
private CompiledFilter(IFilter<T>[] filters)
public CompiledFilter(params IFilter<T>[] filters)
{
_name = string.Join("+", filters.Select(fltr => fltr.GetType().Name));
Filters = filters;
}
/// <summary>
/// Compiles multiple filters into a <see cref="CompiledFilter{T}"/>.
/// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class.
/// </summary>
/// <param name="name"></param>
/// <param name="filters">The filters to compose.</param>
/// <returns>A new <see cref="CompiledFilter{T}"/> instance.</returns>
public static CompiledFilter<T> Compile(params IFilter<T>[] filters)
public CompiledFilter(string name, params IFilter<T>[] filters)
{
return new CompiledFilter<T>(filters);
_name = name;
Filters = filters;
}
/// <summary>
@@ -41,7 +46,7 @@ namespace Telegrator.Filters.Components
if (!filter.CanPass(context))
{
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter)
Alligator.LogDebug("{0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]);
Alligator.LogDebug("{0} filter of {1} didnt pass! (Compiled)", filter.GetType().Name, context.Data["handler_name"]);
return false;
}
+5
View File
@@ -1,5 +1,10 @@
namespace Telegrator.Filters.Components
{
public interface INamedFilter
{
public string Name { get; }
}
/// <summary>
/// Interface for filters that can be collected into a completed filters list.
/// Provides information about whether a filter should be tracked during execution.
+18
View File
@@ -30,6 +30,14 @@ namespace Telegrator.Filters
public Filter<T> Not()
=> new ReverseFilter<T>(this);
/// <summary>
/// Creates a filter that inverts the result of this filter.
/// </summary>
/// <typeparam name="Q"></typeparam>
/// <returns>A <see cref="ReverseFilter{T}"/> instance.</returns>
public static Filter<Q> Not<Q>(IFilter<Q> filter) where Q : class
=> new ReverseFilter<Q>(filter);
/// <summary>
/// Creates a filter that passes only if both this and the specified filter pass.
/// </summary>
@@ -46,6 +54,16 @@ namespace Telegrator.Filters
public OrFilter<T> Or(IFilter<T> filter)
=> new OrFilter<T>(this, filter);
/// <summary>
/// Creates a filter that passes if either this or the specified filter pass.
/// </summary>
/// <typeparam name="Q"></typeparam>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns>An <see cref="OrFilter{Q}"/> instance.</returns>
public static OrFilter<Q> Or<Q>(IFilter<Q> left, IFilter<Q> right) where Q : class
=> new OrFilter<Q>(left, right);
/// <summary>
/// Gets a value indicating whether this filter is collectible.
/// </summary>
@@ -0,0 +1,66 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegrator.Attributes.Components;
using Telegrator.Filters.Components;
using Telegrator.MadiatorCore.Descriptors;
namespace Telegrator.Handlers.Components
{
public class FiltersFallbackReport(HandlerDescriptor descriptor, FilterExecutionContext<Update> context)
{
public HandlerDescriptor Descriptor { get; } = descriptor;
public FilterExecutionContext<Update> Context { get; } = context;
public FilterFallbackInfo? UpdateValidator { get; set; }
public FilterFallbackInfo? StateKeeperValidator { get; set; }
public List<FilterFallbackInfo> UpdateFilters { get; } = [];
/*
public FilterFallbackInfo OfType<T>(int index = 0) where T : IFilter<Update>
{
return UpdateFilters.Where(info => info.Failed is T).ElementAt(index);
}
public FilterFallbackInfo OfAttribute<T>(int index = 0) where T : UpdateFilterAttributeBase
{
return UpdateFilters.Where(info => info.Name == typeof(T).Name).ElementAt(index);
}
public FilterFallbackInfo OfName(string name, int index = 0)
{
return UpdateFilters.Where(info => info.Name == name).ElementAt(index);
}
*/
public bool ExceptAttribute<T>(int index = 0) where T : UpdateFilterAttributeBase
{
string name = typeof(T).Name;
IEnumerable<FilterFallbackInfo> failed = UpdateFilters.Where(info => info.Failed);
if (failed.Count() > 1)
return false;
return failed.SingleOrDefault()?.Name == name;
}
}
public class FilterFallbackInfo(string name, IFilter<Update> filter, bool failed, Exception? exception)
{
public string Name { get; } = name;
public IFilter<Update> Filter { get; } = filter;
public bool Failed { get; } = failed;
public Exception? Exception { get; } = exception;
}
public enum FallbackReason
{
NullTarget,
FailedFilter
}
}
@@ -1,4 +1,4 @@
namespace Telegrator.MadiatorCore.Descriptors
namespace Telegrator.Handlers.Components
{
/// <summary>
/// Represents a token that tracks the lifetime of a handler instance.
@@ -1,4 +1,5 @@
using Telegram.Bot.Polling;
using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Filters.Components;
@@ -41,7 +42,10 @@ namespace Telegrator.Handlers.Components
// Executing pre processor
if (aspects != null)
{
Result? preResult = await aspects.ExecutePre(this, container, cancellationToken).ConfigureAwait(false);
Result? preResult = await aspects
.ExecutePre(this, container, cancellationToken)
.ConfigureAwait(false);
if (!preResult.Positive)
return preResult;
}
@@ -54,7 +58,10 @@ namespace Telegrator.Handlers.Components
// Executing post processor
if (aspects != null)
{
Result postResult = await aspects.ExecutePost(this, container, cancellationToken).ConfigureAwait(false);
Result postResult = await aspects
.ExecutePost(this, container, cancellationToken)
.ConfigureAwait(false);
if (!postResult.Positive)
return postResult;
}
@@ -82,7 +89,7 @@ namespace Telegrator.Handlers.Components
}
}
private IHandlerContainer GetContainer(DescribedHandlerInfo handlerInfo)
internal IHandlerContainer GetContainer(DescribedHandlerInfo handlerInfo)
{
if (this is IHandlerContainerFactory handlerDefainedContainerFactory)
return handlerDefainedContainerFactory.CreateContainer(handlerInfo);
@@ -114,14 +121,14 @@ namespace Telegrator.Handlers.Components
/// <summary>
/// Handles failed filters during handler describing.
/// Use <see cref="Result"/> to control how router should treat this fail.
/// <see cref="Result.Ok"/> to silently continue decribing.
/// <see cref="Result.Fault"/> to stop\break decribing sequence.
/// <see cref="Result.Next"/> to silently continue decribing.
/// <see cref="Result.Fault"/> to stop\break desribing sequence.
/// </summary>
/// <param name="context"></param>
/// <param name="failedFilter"></param>
/// <param name="origin"></param>
/// <param name="report"></param>
/// <param name="client"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task<Result> FiltersFallback(FilterExecutionContext<Update> context, IFilter<Update> failedFilter, FilterOrigin origin)
public virtual Task<Result> FiltersFallback(FiltersFallbackReport report, ITelegramBotClient client, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result.Ok());
}
@@ -1,5 +1,7 @@
using Telegram.Bot.Types;
using Telegrator.Filters;
using Telegrator.Filters.Components;
using Telegrator.Handlers.Components;
using Telegrator.Logging;
namespace Telegrator.MadiatorCore.Descriptors
@@ -41,61 +43,108 @@ namespace Telegrator.MadiatorCore.Descriptors
/// Validates the filter context using all filters in the set.
/// </summary>
/// <param name="filterContext">The filter execution context.</param>
/// <param name="failedFilter"></param>
/// <param name="origin"></param>
/// <param name="formReport"></param>
/// <param name="report"></param>
/// <returns>True if all filters pass; otherwise, false.</returns>
public bool Validate(FilterExecutionContext<Update> filterContext, out IFilter<Update> failedFilter, out FilterOrigin origin)
public Result Validate(FilterExecutionContext<Update> filterContext, bool formReport, ref FiltersFallbackReport report)
{
bool anyErrors = false;
bool anyMatches = false;
if (UpdateValidator != null)
{
if (!UpdateValidator.CanPass(filterContext))
bool result = ExecuteFilter(UpdateValidator, filterContext, out Exception? exc);
if (formReport)
{
Alligator.LogDebug("(E) UpdateValidator filter of {0} for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id);
failedFilter = UpdateValidator;
origin = FilterOrigin.Validator;
return false;
report.UpdateValidator = new FilterFallbackInfo("Validator", UpdateValidator, !result, exc);
}
//LeveledDebug.FilterWriteLine("UpdateValidator of {0} for Update ({2}) passed", filterContext.Data["handler_name"]);
if (!result)
{
anyErrors = true;
Alligator.LogDebug("(E) UpdateValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id);
if (!formReport)
return Result.Fault();
}
else
{
//anyMatches = true; // DO NOT COUNT
filterContext.CompletedFilters.Add(UpdateValidator);
}
}
if (StateKeeperValidator != null)
{
if (!StateKeeperValidator.CanPass(filterContext))
bool result = ExecuteFilter(StateKeeperValidator, filterContext, out Exception? exc);
if (formReport)
{
Alligator.LogDebug("(E) StateKeeperValidator filter of {0} for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id);
failedFilter = StateKeeperValidator;
origin = FilterOrigin.StateKeeper;
return false;
report.StateKeeperValidator = new FilterFallbackInfo("State", StateKeeperValidator, !result, exc);
}
//LeveledDebug.FilterWriteLine("StateKeeperValidator of {0} for Update ({2}) passed", filterContext.Data["handler_name"]);
if (!result)
{
anyErrors = true;
Alligator.LogDebug("(E) StateKeeperValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id);
if (!formReport)
return Result.Fault();
}
else
{
anyMatches = true;
filterContext.CompletedFilters.Add(StateKeeperValidator);
}
}
if (UpdateFilters != null)
{
foreach (IFilter<Update> filter in UpdateFilters)
{
if (!filter.CanPass(filterContext))
{
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter)
Alligator.LogDebug("(E) {0} filter of {1} for Update ({2}) didnt pass!", filter.GetType().Name, filterContext.Data["handler_name"], filterContext.Update.Id);
bool result = ExecuteFilter(filter, filterContext, out Exception? exc);
string filterName = filter is INamedFilter named ? named.Name : filter.GetType().Name;
failedFilter = filter;
origin = FilterOrigin.Regualr;
return false;
if (formReport)
{
report.UpdateFilters.Add(new FilterFallbackInfo(filterName, filter, !result, exc));
}
//LeveledDebug.FilterWriteLine("{0} filter of {1} for Update ({2}) passed", filter.GetType().Name, filterContext.Data["handler_name"]);
if (!result)
{
anyErrors = true;
Alligator.LogDebug("(E) '{0}' filter of '{1}' for Update ({2}) didnt pass!", filterName, filterContext.Data["handler_name"], filterContext.Update.Id);
if (!formReport)
return Result.Fault();
}
else
{
anyMatches = true;
filterContext.CompletedFilters.Add(filter);
}
}
}
failedFilter = null!;
origin = FilterOrigin.None;
return true;
if (!anyErrors)
return Result.Ok();
return formReport ? Result.Next() : Result.Fault();
}
private static bool ExecuteFilter<T>(IFilter<T> filter, FilterExecutionContext<T> context, out Exception? exception) where T : class
{
try
{
exception = null;
return filter.CanPass(context);
}
catch (Exception ex)
{
exception = ex;
return false;
}
}
}
}
@@ -73,6 +73,12 @@ namespace Telegrator.MadiatorCore.Descriptors
set;
}
public bool FormReport
{
get;
set;
}
/// <summary>
/// The set of filters associated with this handler.
/// </summary>
@@ -163,6 +169,7 @@ namespace Telegrator.MadiatorCore.Descriptors
UpdateType = handlerAttribute.Type;
Indexer = handlerAttribute.GetIndexer();
FormReport = handlerAttribute.FormReport;
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
Aspects = HandlerInspector.GetAspects(handlerType);
DisplayString = HandlerInspector.GetDisplayName(handlerType);
+22 -14
View File
@@ -177,25 +177,23 @@ namespace Telegrator.Polling
/// <param name="cancellationToken"></param>
/// <returns>A collection of described handler information</returns>
protected virtual IEnumerable<DescribedHandlerInfo> DescribeDescriptors(IHandlersProvider provider, HandlerDescriptorList descriptors, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
try
{
Alligator.LogDebug("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id);
foreach (HandlerDescriptor descriptor in descriptors.Reverse())
{
cancellationToken.ThrowIfCancellationRequested();
DescribedHandlerInfo? describedHandler = DescribeHandler(provider, descriptor, client, update, cancellationToken);
DescribedHandlerInfo? describedHandler = DescribeHandler(provider, descriptor, client, update, out bool breakRouting, cancellationToken);
if (breakRouting)
yield break;
if (describedHandler == null)
continue;
yield return describedHandler;
}
}
finally
{
Alligator.LogDebug("Describing for Update ({0}) finished", update.Id);
}
}
/// <summary>
/// Describes a single handler descriptor for a given update context.
@@ -205,10 +203,12 @@ namespace Telegrator.Polling
/// <param name="descriptor">The handler descriptor to process</param>
/// <param name="client">The Telegram bot client instance</param>
/// <param name="update">The incoming Telegram update to process</param>
/// <param name="breakRouting"></param>
/// <param name="cancellationToken"></param>
/// <returns>The described handler info if validation passes; otherwise, null</returns>
public virtual DescribedHandlerInfo? DescribeHandler(IHandlersProvider provider, HandlerDescriptor descriptor, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
public virtual DescribedHandlerInfo? DescribeHandler(IHandlersProvider provider, HandlerDescriptor descriptor, ITelegramBotClient client, Update update, out bool breakRouting, CancellationToken cancellationToken = default)
{
breakRouting = false;
cancellationToken.ThrowIfCancellationRequested();
Dictionary<string, object> data = new Dictionary<string, object>()
{
@@ -216,16 +216,24 @@ namespace Telegrator.Polling
};
UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken);
FilterExecutionContext<Update> filterContext = new FilterExecutionContext<Update>(_botInfo, update, update, data, []);
if (descriptor.Filters != null && !descriptor.Filters.Validate(filterContext, out IFilter<Update> failedFilter, out FilterOrigin origin))
{
Result fallbackResult = handlerInstance.FiltersFallback(filterContext, failedFilter, origin).Result;
if (!fallbackResult.Positive)
throw new BreakDescribingException();
if (descriptor.Filters != null)
{
FiltersFallbackReport report = new FiltersFallbackReport(descriptor, filterContext);
Result filtersResult = descriptor.Filters.Validate(filterContext, descriptor.FormReport, ref report);
if (filtersResult.RouteNext)
{
Result fallbackResult = handlerInstance.FiltersFallback(report, client, cancellationToken).Result;
breakRouting = !fallbackResult.RouteNext;
return null;
}
else if (!filtersResult.Positive)
{
return null;
}
}
return new DescribedHandlerInfo(descriptor, this, AwaitingProvider, client, handlerInstance, filterContext, descriptor.DisplayString);
}
+2 -2
View File
@@ -37,7 +37,6 @@ namespace Telegrator
/// Represents 'success'
/// <list type="bullet">
/// <item>Inside <see cref="IPreProcessor"/> - let handler's main block be executed</item>
/// <item>Inside <see cref="UpdateHandlerBase.FiltersFallback(FilterExecutionContext{Update}, IFilter{Update}, FilterOrigin)"/> - let router continue describing</item>
/// <item>Inside <see cref="UpdateHandlerBase.ExecuteInternal(IHandlerContainer, CancellationToken)"/> - tells <see cref="IUpdateRouter"/> that he can stop describing, as needed handler was found</item>
/// </list>
/// </summary>
@@ -49,7 +48,7 @@ namespace Telegrator
/// Represents 'fault' or 'error'. Use cases:
/// <list type="bullet">
/// <item>Inside <see cref="IPreProcessor"/> - interupts execution of handler, main block and <see cref="IPostProcessor"/> wont be executed</item>
/// <item>Inside <see cref="UpdateHandlerBase.FiltersFallback(FilterExecutionContext{Update}, IFilter{Update}, FilterOrigin)"/> - interupts router's describing sequence</item>
/// <item>Inside <see cref="UpdateHandlerBase.FiltersFallback(FiltersFallbackReport, Telegram.Bot.ITelegramBotClient, CancellationToken)"/> - interupts <see cref="IUpdateRouter"/>'s describing sequence</item>
/// </list>
/// </summary>
/// <returns></returns>
@@ -59,6 +58,7 @@ namespace Telegrator
/// <summary>
/// Represents 'continue'. Use cases:
/// <list type="bullet">
/// <item>Inside <see cref="UpdateHandlerBase.FiltersFallback(FiltersFallbackReport, Telegram.Bot.ITelegramBotClient, CancellationToken)"/> - let router continue describing</item>
/// <item>Inside <see cref="UpdateHandlerBase.ExecuteInternal(IHandlerContainer, CancellationToken)"/> - Tells <see cref="IUpdateRouter"/> to continue describing handlers</item>
/// </list>
/// </summary>
+1 -1
View File
@@ -17,7 +17,7 @@
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.0.11</Version>
<Version>1.0.12</Version>
</PropertyGroup>
<ItemGroup>