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:
Joe4evr
2017-06-23 16:28:22 +02:00
committed by RogueException
parent d44d5e7198
commit 4a9c8168a9
5 changed files with 70 additions and 16 deletions

View File

@@ -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 &amp;&amp; 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);
} }
} }

View File

@@ -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);

View File

@@ -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);

View 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}";
}
}

View File

@@ -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;