* 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> <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Version>1.0.11</Version> <Version>1.0.12</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+1 -1
View File
@@ -16,7 +16,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Version>1.0.11</Version> <Version>1.0.12</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+2 -1
View File
@@ -100,12 +100,13 @@ namespace Telegrator.Hosting
/// Adds a Microsoft.Extensions.Logging adapter to Alligator using a logger factory. /// Adds a Microsoft.Extensions.Logging adapter to Alligator using a logger factory.
/// </summary> /// </summary>
/// <param name="host"></param> /// <param name="host"></param>
public static void AddLoggingAdapter(this ITelegramBotHost host) public static ITelegramBotHost AddLoggingAdapter(this ITelegramBotHost host)
{ {
ILoggerFactory loggerFactory = host.Services.GetRequiredService<ILoggerFactory>(); ILoggerFactory loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
ILogger logger = loggerFactory.CreateLogger("Telegrator"); ILogger logger = loggerFactory.CreateLogger("Telegrator");
MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger); MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger);
Alligator.AddAdapter(adapter); Alligator.AddAdapter(adapter);
return host;
} }
} }
+1 -1
View File
@@ -121,7 +121,7 @@ namespace Telegrator.Tests.Filters
var filter2 = Filter<Update>.If(_ => true); var filter2 = Filter<Update>.If(_ => true);
var filter3 = Filter<Update>.If(_ => false); 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()); var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
// Act // Act
@@ -20,7 +20,7 @@ namespace Telegrator.Attributes.Components
/// <summary> /// <summary>
/// Gets the <see cref="IFilter{T}"/> that <see cref="UpdateHandlerBase"/> processing /// Gets the <see cref="IFilter{T}"/> that <see cref="UpdateHandlerBase"/> processing
/// </summary> /// </summary>
public abstract Filter<Update> AnonymousFilter { get; protected set; } public abstract IFilter<Update> AnonymousFilter { get; protected set; }
/// <summary> /// <summary>
/// Gets or sets the filter modifiers that affect how this filter is combined with others. /// Gets or sets the filter modifiers that affect how this filter is combined with others.
@@ -35,6 +35,8 @@ namespace Telegrator.Attributes.Components
/// </summary> /// </summary>
public int Priority { get; set; } public int Priority { get; set; }
public bool FormReport { get; set; }
/// <summary> /// <summary>
/// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/> /// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/>
/// </summary> /// </summary>
+10 -8
View File
@@ -15,12 +15,12 @@ namespace Telegrator.Attributes
/// <summary> /// <summary>
/// Gets the compiled anonymous filter for this attribute. /// Gets the compiled anonymous filter for this attribute.
/// </summary> /// </summary>
public override Filter<Update> AnonymousFilter { get; protected set; } public override IFilter<Update> AnonymousFilter { get; protected set; }
/// <summary> /// <summary>
/// Gets the compiled filter logic for the update target. /// Gets the compiled filter logic for the update target.
/// </summary> /// </summary>
public Filter<T> UpdateFilter { get; protected set; } public IFilter<T> UpdateFilter { get; protected set; }
/// <summary> /// <summary>
/// Empty constructor for internal using /// Empty constructor for internal using
@@ -38,18 +38,20 @@ namespace Telegrator.Attributes
/// <param name="filters">The filters to compose</param> /// <param name="filters">The filters to compose</param>
protected UpdateFilterAttribute(params IFilter<T>[] filters) protected UpdateFilterAttribute(params IFilter<T>[] filters)
{ {
UpdateFilter = CompiledFilter<T>.Compile(filters); string name = GetType().Name;
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget); UpdateFilter = new CompiledFilter<T>(name, filters);
AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget);
} }
/// <summary> /// <summary>
/// Initializes the attribute with a precompiled filter for the update target. /// Initializes the attribute with a precompiled filter for the update target.
/// </summary> /// </summary>
/// <param name="updateFilter">The compiled filter</param> /// <param name="updateFilter">The compiled filter</param>
protected UpdateFilterAttribute(Filter<T> updateFilter) protected UpdateFilterAttribute(IFilter<T> updateFilter)
{ {
string name = GetType().Name;
UpdateFilter = updateFilter; UpdateFilter = updateFilter;
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget); AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget);
} }
/// <summary> /// <summary>
@@ -60,13 +62,13 @@ namespace Telegrator.Attributes
public override sealed bool ProcessModifiers(UpdateFilterAttributeBase? previous) public override sealed bool ProcessModifiers(UpdateFilterAttributeBase? previous)
{ {
if (Modifiers.HasFlag(FilterModifier.Not)) if (Modifiers.HasFlag(FilterModifier.Not))
AnonymousFilter = AnonymousFilter.Not(); AnonymousFilter = Filter<T>.Not(AnonymousFilter);
if (previous is not null) if (previous is not null)
{ {
if (previous.Modifiers.HasFlag(FilterModifier.OrNext)) 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 Sender
} }
/*
/// <summary> /// <summary>
/// Messages from where this filter was originated /// Messages from where this filter was originated
/// </summary> /// </summary>
@@ -95,6 +96,7 @@
/// </summary> /// </summary>
Regualr Regualr
} }
*/
/* /*
/// <summary> /// <summary>
@@ -1,5 +1,4 @@
using Telegram.Bot; using Telegram.Bot.Types;
using Telegram.Bot.Types;
using Telegrator.Logging; using Telegrator.Logging;
namespace Telegrator.Filters.Components namespace Telegrator.Filters.Components
@@ -7,15 +6,26 @@ namespace Telegrator.Filters.Components
/// <summary> /// <summary>
/// Represents a compiled filter that applies a set of filters to an anonymous target type. /// Represents a compiled filter that applies a set of filters to an anonymous target type.
/// </summary> /// </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> /// <summary>
/// Initializes a new instance of the <see cref="AnonymousCompiledFilter"/> class. /// Initializes a new instance of the <see cref="AnonymousCompiledFilter"/> class.
/// </summary> /// </summary>
/// <param name="name"></param>
/// <param name="filterAction">The filter action delegate.</param> /// <param name="filterAction">The filter action delegate.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</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) private AnonymousCompiledFilter(string name, Func<Update, object?> getFilterringTarget, Func<FilterExecutionContext<Update>, object, bool> filterAction)
: base(filterAction, getFilterringTarget) { } {
FilterAction = filterAction;
GetFilterringTarget = getFilterringTarget;
_name = name;
}
/// <summary> /// <summary>
/// Compiles a set of filters into an <see cref="AnonymousCompiledFilter"/> for a specific target type. /// 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="filters">The list of filters to compile.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param> /// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns> /// <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( return new AnonymousCompiledFilter(
(context, filterringTarget) => CanPassInternal(filters, context, filterringTarget), string.Join("+", filters.Select(fltr => fltr.GetType().Name)),
getFilterringTarget); 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> /// <summary>
@@ -39,16 +66,15 @@ namespace Telegrator.Filters.Components
/// <param name="updateContext">The filter execution context.</param> /// <param name="updateContext">The filter execution context.</param>
/// <param name="filterringTarget">The filtering target.</param> /// <param name="filterringTarget">The filtering target.</param>
/// <returns>True if all filters pass; otherwise, false.</returns> /// <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); FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
foreach (IFilter<T> filter in filters) foreach (IFilter<T> filter in filters)
{ {
if (!filter.CanPass(context)) if (!filter.CanPass(context))
{ {
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) 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; return false;
} }
@@ -58,5 +84,22 @@ namespace Telegrator.Filters.Components
return true; 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> /// <summary>
/// Represents a filter that applies a filter action to an anonymous target type extracted from an update. /// Represents a filter that applies a filter action to an anonymous target type extracted from an update.
/// </summary> /// </summary>
public class AnonymousTypeFilter : Filter<Update> public class AnonymousTypeFilter : Filter<Update>, INamedFilter
{ {
private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction; private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction;
private readonly Func<Update, object?> GetFilterringTarget; private readonly Func<Update, object?> GetFilterringTarget;
private readonly string _name;
public virtual string Name => _name;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AnonymousTypeFilter"/> class. /// Initializes a new instance of the <see cref="AnonymousTypeFilter"/> class.
/// </summary> /// </summary>
/// <param name="name"></param>
/// <param name="filterAction">The filter action delegate.</param> /// <param name="filterAction">The filter action delegate.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</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; FilterAction = filterAction;
GetFilterringTarget = getFilterringTarget; GetFilterringTarget = getFilterringTarget;
_name = name;
} }
/// <summary> /// <summary>
@@ -32,8 +37,25 @@ namespace Telegrator.Filters.Components
public static AnonymousTypeFilter Compile<T>(IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class public static AnonymousTypeFilter Compile<T>(IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class
{ {
return new AnonymousTypeFilter( return new AnonymousTypeFilter(
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget), filter.GetType().Name,
getFilterringTarget); 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> /// <summary>
@@ -6,27 +6,32 @@ namespace Telegrator.Filters.Components
/// Represents a filter that composes multiple filters and passes only if all of them pass. /// Represents a filter that composes multiple filters and passes only if all of them pass.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the input for the filter.</typeparam> /// <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 IFilter<T>[] Filters;
private readonly string _name;
public virtual string Name => _name;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class. /// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class.
/// </summary> /// </summary>
/// <param name="filters">The filters to compose.</param> /// <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; Filters = filters;
} }
/// <summary> /// <summary>
/// Compiles multiple filters into a <see cref="CompiledFilter{T}"/>. /// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class.
/// </summary> /// </summary>
/// <param name="name"></param>
/// <param name="filters">The filters to compose.</param> /// <param name="filters">The filters to compose.</param>
/// <returns>A new <see cref="CompiledFilter{T}"/> instance.</returns> public CompiledFilter(string name, params IFilter<T>[] filters)
public static CompiledFilter<T> Compile(params IFilter<T>[] filters)
{ {
return new CompiledFilter<T>(filters); _name = name;
Filters = filters;
} }
/// <summary> /// <summary>
@@ -41,7 +46,7 @@ namespace Telegrator.Filters.Components
if (!filter.CanPass(context)) if (!filter.CanPass(context))
{ {
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) 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; return false;
} }
+5
View File
@@ -1,5 +1,10 @@
namespace Telegrator.Filters.Components namespace Telegrator.Filters.Components
{ {
public interface INamedFilter
{
public string Name { get; }
}
/// <summary> /// <summary>
/// Interface for filters that can be collected into a completed filters list. /// Interface for filters that can be collected into a completed filters list.
/// Provides information about whether a filter should be tracked during execution. /// 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() public Filter<T> Not()
=> new ReverseFilter<T>(this); => 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> /// <summary>
/// Creates a filter that passes only if both this and the specified filter pass. /// Creates a filter that passes only if both this and the specified filter pass.
/// </summary> /// </summary>
@@ -46,6 +54,16 @@ namespace Telegrator.Filters
public OrFilter<T> Or(IFilter<T> filter) public OrFilter<T> Or(IFilter<T> filter)
=> new OrFilter<T>(this, 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> /// <summary>
/// Gets a value indicating whether this filter is collectible. /// Gets a value indicating whether this filter is collectible.
/// </summary> /// </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> /// <summary>
/// Represents a token that tracks the lifetime of a handler instance. /// 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;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegrator.Filters.Components; using Telegrator.Filters.Components;
@@ -41,7 +42,10 @@ namespace Telegrator.Handlers.Components
// Executing pre processor // Executing pre processor
if (aspects != null) 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) if (!preResult.Positive)
return preResult; return preResult;
} }
@@ -54,7 +58,10 @@ namespace Telegrator.Handlers.Components
// Executing post processor // Executing post processor
if (aspects != null) 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) if (!postResult.Positive)
return postResult; 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) if (this is IHandlerContainerFactory handlerDefainedContainerFactory)
return handlerDefainedContainerFactory.CreateContainer(handlerInfo); return handlerDefainedContainerFactory.CreateContainer(handlerInfo);
@@ -114,14 +121,14 @@ namespace Telegrator.Handlers.Components
/// <summary> /// <summary>
/// Handles failed filters during handler describing. /// Handles failed filters during handler describing.
/// Use <see cref="Result"/> to control how router should treat this fail. /// Use <see cref="Result"/> to control how router should treat this fail.
/// <see cref="Result.Ok"/> to silently continue decribing. /// <see cref="Result.Next"/> to silently continue decribing.
/// <see cref="Result.Fault"/> to stop\break decribing sequence. /// <see cref="Result.Fault"/> to stop\break desribing sequence.
/// </summary> /// </summary>
/// <param name="context"></param> /// <param name="report"></param>
/// <param name="failedFilter"></param> /// <param name="client"></param>
/// <param name="origin"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <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()); return Task.FromResult(Result.Ok());
} }
@@ -1,5 +1,7 @@
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Filters;
using Telegrator.Filters.Components; using Telegrator.Filters.Components;
using Telegrator.Handlers.Components;
using Telegrator.Logging; using Telegrator.Logging;
namespace Telegrator.MadiatorCore.Descriptors namespace Telegrator.MadiatorCore.Descriptors
@@ -41,61 +43,108 @@ namespace Telegrator.MadiatorCore.Descriptors
/// Validates the filter context using all filters in the set. /// Validates the filter context using all filters in the set.
/// </summary> /// </summary>
/// <param name="filterContext">The filter execution context.</param> /// <param name="filterContext">The filter execution context.</param>
/// <param name="failedFilter"></param> /// <param name="formReport"></param>
/// <param name="origin"></param> /// <param name="report"></param>
/// <returns>True if all filters pass; otherwise, false.</returns> /// <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 != 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); report.UpdateValidator = new FilterFallbackInfo("Validator", UpdateValidator, !result, exc);
failedFilter = UpdateValidator;
origin = FilterOrigin.Validator;
return false;
} }
//LeveledDebug.FilterWriteLine("UpdateValidator of {0} for Update ({2}) passed", filterContext.Data["handler_name"]); if (!result)
filterContext.CompletedFilters.Add(UpdateValidator); {
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 != 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); report.StateKeeperValidator = new FilterFallbackInfo("State", StateKeeperValidator, !result, exc);
failedFilter = StateKeeperValidator;
origin = FilterOrigin.StateKeeper;
return false;
} }
//LeveledDebug.FilterWriteLine("StateKeeperValidator of {0} for Update ({2}) passed", filterContext.Data["handler_name"]); if (!result)
filterContext.CompletedFilters.Add(StateKeeperValidator); {
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) if (UpdateFilters != null)
{ {
foreach (IFilter<Update> filter in UpdateFilters) foreach (IFilter<Update> filter in UpdateFilters)
{ {
if (!filter.CanPass(filterContext)) bool result = ExecuteFilter(filter, filterContext, out Exception? exc);
{ string filterName = filter is INamedFilter named ? named.Name : filter.GetType().Name;
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);
failedFilter = filter; if (formReport)
origin = FilterOrigin.Regualr; {
return false; 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)
filterContext.CompletedFilters.Add(filter); {
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!; if (!anyErrors)
origin = FilterOrigin.None; return Result.Ok();
return true;
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; set;
} }
public bool FormReport
{
get;
set;
}
/// <summary> /// <summary>
/// The set of filters associated with this handler. /// The set of filters associated with this handler.
/// </summary> /// </summary>
@@ -163,6 +169,7 @@ namespace Telegrator.MadiatorCore.Descriptors
UpdateType = handlerAttribute.Type; UpdateType = handlerAttribute.Type;
Indexer = handlerAttribute.GetIndexer(); Indexer = handlerAttribute.GetIndexer();
FormReport = handlerAttribute.FormReport;
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters); Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
Aspects = HandlerInspector.GetAspects(handlerType); Aspects = HandlerInspector.GetAspects(handlerType);
DisplayString = HandlerInspector.GetDisplayName(handlerType); DisplayString = HandlerInspector.GetDisplayName(handlerType);
+30 -22
View File
@@ -178,23 +178,21 @@ namespace Telegrator.Polling
/// <returns>A collection of described handler information</returns> /// <returns>A collection of described handler information</returns>
protected virtual IEnumerable<DescribedHandlerInfo> DescribeDescriptors(IHandlersProvider provider, HandlerDescriptorList descriptors, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) 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())
{ {
Alligator.LogDebug("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id); cancellationToken.ThrowIfCancellationRequested();
foreach (HandlerDescriptor descriptor in descriptors.Reverse()) DescribedHandlerInfo? describedHandler = DescribeHandler(provider, descriptor, client, update, out bool breakRouting, cancellationToken);
{ if (breakRouting)
cancellationToken.ThrowIfCancellationRequested(); yield break;
DescribedHandlerInfo? describedHandler = DescribeHandler(provider, descriptor, client, update, cancellationToken);
if (describedHandler == null)
continue;
yield return describedHandler; if (describedHandler == null)
} continue;
}
finally yield return describedHandler;
{
Alligator.LogDebug("Describing for Update ({0}) finished", update.Id);
} }
Alligator.LogDebug("Describing for Update ({0}) finished", update.Id);
} }
/// <summary> /// <summary>
@@ -205,10 +203,12 @@ namespace Telegrator.Polling
/// <param name="descriptor">The handler descriptor to process</param> /// <param name="descriptor">The handler descriptor to process</param>
/// <param name="client">The Telegram bot client instance</param> /// <param name="client">The Telegram bot client instance</param>
/// <param name="update">The incoming Telegram update to process</param> /// <param name="update">The incoming Telegram update to process</param>
/// <param name="breakRouting"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns>The described handler info if validation passes; otherwise, null</returns> /// <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(); cancellationToken.ThrowIfCancellationRequested();
Dictionary<string, object> data = new Dictionary<string, object>() Dictionary<string, object> data = new Dictionary<string, object>()
{ {
@@ -216,15 +216,23 @@ namespace Telegrator.Polling
}; };
UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken); UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken);
FilterExecutionContext<Update> filterContext = new FilterExecutionContext<Update>(_botInfo, update, update, data, []); 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();
return null; 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); return new DescribedHandlerInfo(descriptor, this, AwaitingProvider, client, handlerInstance, filterContext, descriptor.DisplayString);
+2 -2
View File
@@ -37,7 +37,6 @@ namespace Telegrator
/// Represents 'success' /// Represents 'success'
/// <list type="bullet"> /// <list type="bullet">
/// <item>Inside <see cref="IPreProcessor"/> - let handler's main block be executed</item> /// <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> /// <item>Inside <see cref="UpdateHandlerBase.ExecuteInternal(IHandlerContainer, CancellationToken)"/> - tells <see cref="IUpdateRouter"/> that he can stop describing, as needed handler was found</item>
/// </list> /// </list>
/// </summary> /// </summary>
@@ -49,7 +48,7 @@ namespace Telegrator
/// Represents 'fault' or 'error'. Use cases: /// Represents 'fault' or 'error'. Use cases:
/// <list type="bullet"> /// <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="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> /// </list>
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
@@ -59,6 +58,7 @@ namespace Telegrator
/// <summary> /// <summary>
/// Represents 'continue'. Use cases: /// Represents 'continue'. Use cases:
/// <list type="bullet"> /// <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> /// <item>Inside <see cref="UpdateHandlerBase.ExecuteInternal(IHandlerContainer, CancellationToken)"/> - Tells <see cref="IUpdateRouter"/> to continue describing handlers</item>
/// </list> /// </list>
/// </summary> /// </summary>
+1 -1
View File
@@ -17,7 +17,7 @@
<EnableNETAnalyzers>True</EnableNETAnalyzers> <EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.0.11</Version> <Version>1.0.12</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>