Add grouping of preconditions to allow for flexible precondition logic. (#672)
* Add grouping of preconditions to allow for flexible precondition logic. * Fix checking Module Preconditions twice (and none of the command's own) * Fix command preconditions group 0 looping over every other precondition anyway #whoopsies * Use custom message when a non-zero Precondition Group fails. * Fix doc comment rendering. * Refactor loops into local function * Considering a new result type * Switch to IReadOnlyCollection<T> and fix compiler errors * Revert PreconditionResult -> IResult in return types - Change PreconditionResult to a class that PreconditionGroupResult inherits. * Feedback on property name. * Change grouping type int -> string * Explicitly use an ordinal StringComparer * Full stops on error messages * Remove some sillyness. * Remove unneeded using.
This commit is contained in:
@@ -6,6 +6,13 @@ namespace Discord.Commands
|
|||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||||
public abstract class PreconditionAttribute : Attribute
|
public abstract class PreconditionAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specify a group that this precondition belongs to. Preconditions of the same group require only one
|
||||||
|
/// of the preconditions to pass in order to be successful (A || B). Specifying <see cref="Group"/> = <see cref="null"/>
|
||||||
|
/// or not at all will require *all* preconditions to pass, just like normal (A && B).
|
||||||
|
/// </summary>
|
||||||
|
public string Group { get; set; } = null;
|
||||||
|
|
||||||
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services);
|
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
|
public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
|
||||||
=> Command.CheckPreconditionsAsync(context, services);
|
=> Command.CheckPreconditionsAsync(context, services);
|
||||||
public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null)
|
public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null)
|
||||||
=> Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult);
|
=> Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult);
|
||||||
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
|
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
|
||||||
=> Command.ExecuteAsync(context, argList, paramList, services);
|
=> Command.ExecuteAsync(context, argList, paramList, services);
|
||||||
|
|||||||
@@ -68,29 +68,49 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
services = services ?? EmptyServiceProvider.Instance;
|
services = services ?? EmptyServiceProvider.Instance;
|
||||||
|
|
||||||
foreach (PreconditionAttribute precondition in Module.Preconditions)
|
async Task<PreconditionGroupResult> CheckGroups(IEnumerable<PreconditionAttribute> preconditions, string type)
|
||||||
{
|
{
|
||||||
var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false);
|
foreach (IGrouping<string, PreconditionAttribute> preconditionGroup in preconditions.GroupBy(p => p.Group, StringComparer.Ordinal))
|
||||||
if (!result.IsSuccess)
|
{
|
||||||
return result;
|
if (preconditionGroup.Key == null)
|
||||||
|
{
|
||||||
|
foreach (PreconditionAttribute precondition in preconditionGroup)
|
||||||
|
{
|
||||||
|
var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false);
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
return PreconditionGroupResult.FromError($"{type} default precondition group failed.", new[] { result });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var results = new List<PreconditionResult>();
|
||||||
|
foreach (PreconditionAttribute precondition in preconditionGroup)
|
||||||
|
results.Add(await precondition.CheckPermissions(context, this, services).ConfigureAwait(false));
|
||||||
|
|
||||||
|
if (!results.Any(p => p.IsSuccess))
|
||||||
|
return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PreconditionGroupResult.FromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (PreconditionAttribute precondition in Preconditions)
|
var moduleResult = await CheckGroups(Module.Preconditions, "Module");
|
||||||
{
|
if (!moduleResult.IsSuccess)
|
||||||
var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false);
|
return moduleResult;
|
||||||
if (!result.IsSuccess)
|
|
||||||
return result;
|
var commandResult = await CheckGroups(Preconditions, "Command");
|
||||||
}
|
if (!commandResult.IsSuccess)
|
||||||
|
return commandResult;
|
||||||
|
|
||||||
return PreconditionResult.FromSuccess();
|
return PreconditionResult.FromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult? preconditionResult = null)
|
public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null)
|
||||||
{
|
{
|
||||||
if (!searchResult.IsSuccess)
|
if (!searchResult.IsSuccess)
|
||||||
return ParseResult.FromError(searchResult);
|
return ParseResult.FromError(searchResult);
|
||||||
if (preconditionResult != null && !preconditionResult.Value.IsSuccess)
|
if (preconditionResult != null && !preconditionResult.IsSuccess)
|
||||||
return ParseResult.FromError(preconditionResult.Value);
|
return ParseResult.FromError(preconditionResult);
|
||||||
|
|
||||||
string input = searchResult.Text.Substring(startIndex);
|
string input = searchResult.Text.Substring(startIndex);
|
||||||
return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false);
|
return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false);
|
||||||
|
|||||||
27
src/Discord.Net.Commands/Results/PreconditionGroupResult.cs
Normal file
27
src/Discord.Net.Commands/Results/PreconditionGroupResult.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
|
public class PreconditionGroupResult : PreconditionResult
|
||||||
|
{
|
||||||
|
public IReadOnlyCollection<PreconditionResult> PreconditionResults { get; }
|
||||||
|
|
||||||
|
protected PreconditionGroupResult(CommandError? error, string errorReason, ICollection<PreconditionResult> preconditions)
|
||||||
|
: base(error, errorReason)
|
||||||
|
{
|
||||||
|
PreconditionResults = (preconditions ?? new List<PreconditionResult>(0)).ToReadOnlyCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static new PreconditionGroupResult FromSuccess()
|
||||||
|
=> new PreconditionGroupResult(null, null, null);
|
||||||
|
public static PreconditionGroupResult FromError(string reason, ICollection<PreconditionResult> preconditions)
|
||||||
|
=> new PreconditionGroupResult(CommandError.UnmetPrecondition, reason, preconditions);
|
||||||
|
public static new PreconditionGroupResult FromError(IResult result) //needed?
|
||||||
|
=> new PreconditionGroupResult(result.Error, result.ErrorReason, null);
|
||||||
|
|
||||||
|
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||||
|
private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,14 @@
|
|||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
public struct PreconditionResult : IResult
|
public class PreconditionResult : IResult
|
||||||
{
|
{
|
||||||
public CommandError? Error { get; }
|
public CommandError? Error { get; }
|
||||||
public string ErrorReason { get; }
|
public string ErrorReason { get; }
|
||||||
|
|
||||||
public bool IsSuccess => !Error.HasValue;
|
public bool IsSuccess => !Error.HasValue;
|
||||||
|
|
||||||
private PreconditionResult(CommandError? error, string errorReason)
|
protected PreconditionResult(CommandError? error, string errorReason)
|
||||||
{
|
{
|
||||||
Error = error;
|
Error = error;
|
||||||
ErrorReason = errorReason;
|
ErrorReason = errorReason;
|
||||||
|
|||||||
Reference in New Issue
Block a user