Command execution code rework and TypeConverters auto-scope fix (#2306)

* command execution rework and sync service scopes for typeconverters

* replace ValueTask with Task

* fix implementation bugs

Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>
This commit is contained in:
Cenk Ergen
2022-11-22 14:05:26 +03:00
committed by GitHub
parent ea039b848c
commit 6869817184
11 changed files with 159 additions and 172 deletions

View File

@@ -23,7 +23,7 @@ namespace Discord.Interactions
public string CommandName { get; } public string CommandName { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; } public override IReadOnlyList<CommandParameterInfo> Parameters { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override bool SupportsWildCards => false; public override bool SupportsWildCards => false;
@@ -41,9 +41,12 @@ namespace Discord.Interactions
if (context.Interaction is not IAutocompleteInteraction) if (context.Interaction is not IAutocompleteInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction");
return await RunAsync(context, Array.Empty<object>(), services).ConfigureAwait(false); return await base.ExecuteAsync(context, services).ConfigureAwait(false);
} }
protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
=> Task.FromResult(ParseResult.FromSuccess(Array.Empty<object>()) as IResult);
/// <inheritdoc/> /// <inheritdoc/>
protected override Task InvokeModuleEvent(IInteractionContext context, IResult result) => protected override Task InvokeModuleEvent(IInteractionContext context, IResult result) =>
CommandService._autocompleteCommandExecutedEvent.InvokeAsync(this, context, result); CommandService._autocompleteCommandExecutedEvent.InvokeAsync(this, context, result);

View File

@@ -64,7 +64,7 @@ namespace Discord.Interactions
public IReadOnlyCollection<PreconditionAttribute> Preconditions { get; } public IReadOnlyCollection<PreconditionAttribute> Preconditions { get; }
/// <inheritdoc cref="ICommandInfo.Parameters"/> /// <inheritdoc cref="ICommandInfo.Parameters"/>
public abstract IReadOnlyCollection<TParameter> Parameters { get; } public abstract IReadOnlyList<TParameter> Parameters { get; }
internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService) internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService)
{ {
@@ -85,71 +85,16 @@ namespace Discord.Interactions
} }
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services); public virtual async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
protected abstract Task InvokeModuleEvent(IInteractionContext context, IResult result);
protected abstract string GetLogString(IInteractionContext context);
/// <inheritdoc/>
public async Task<PreconditionResult> CheckPreconditionsAsync(IInteractionContext context, IServiceProvider services)
{
async Task<PreconditionResult> CheckGroups(ILookup<string, PreconditionAttribute> preconditions, string type)
{
foreach (IGrouping<string, PreconditionAttribute> preconditionGroup in preconditions)
{
if (preconditionGroup.Key == null)
{
foreach (PreconditionAttribute precondition in preconditionGroup)
{
var result = await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}
}
else
{
var results = new List<PreconditionResult>();
foreach (PreconditionAttribute precondition in preconditionGroup)
results.Add(await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false));
if (!results.Any(p => p.IsSuccess))
return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results);
}
}
return PreconditionGroupResult.FromSuccess();
}
var moduleResult = await CheckGroups(Module.GroupedPreconditions, "Module").ConfigureAwait(false);
if (!moduleResult.IsSuccess)
return moduleResult;
var commandResult = await CheckGroups(_groupedPreconditions, "Command").ConfigureAwait(false);
return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess();
}
protected async Task<IResult> RunAsync(IInteractionContext context, object[] args, IServiceProvider services)
{ {
switch (RunMode) switch (RunMode)
{ {
case RunMode.Sync: case RunMode.Sync:
{ return await ExecuteInternalAsync(context, services).ConfigureAwait(false);
if (CommandService._autoServiceScopes)
{
using var scope = services?.CreateScope();
return await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false);
}
return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
}
case RunMode.Async: case RunMode.Async:
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
if (CommandService._autoServiceScopes) await ExecuteInternalAsync(context, services).ConfigureAwait(false);
{
using var scope = services?.CreateScope();
await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false);
}
else
await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
}); });
break; break;
default: default:
@@ -159,16 +104,33 @@ namespace Discord.Interactions
return ExecuteResult.FromSuccess(); return ExecuteResult.FromSuccess();
} }
private async Task<IResult> ExecuteInternalAsync(IInteractionContext context, object[] args, IServiceProvider services) protected abstract Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services);
private async Task<IResult> ExecuteInternalAsync(IInteractionContext context, IServiceProvider services)
{ {
await CommandService._cmdLogger.DebugAsync($"Executing {GetLogString(context)}").ConfigureAwait(false); await CommandService._cmdLogger.DebugAsync($"Executing {GetLogString(context)}").ConfigureAwait(false);
using var scope = services?.CreateScope();
if (CommandService._autoServiceScopes)
services = scope?.ServiceProvider ?? EmptyServiceProvider.Instance;
try try
{ {
var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false); var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false);
if (!preconditionResult.IsSuccess) if (!preconditionResult.IsSuccess)
return await InvokeEventAndReturn(context, preconditionResult).ConfigureAwait(false); return await InvokeEventAndReturn(context, preconditionResult).ConfigureAwait(false);
var argsResult = await ParseArgumentsAsync(context, services).ConfigureAwait(false);
if (!argsResult.IsSuccess)
return await InvokeEventAndReturn(context, argsResult).ConfigureAwait(false);
if(argsResult is not ParseResult parseResult)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason.");
var args = parseResult.Args;
var index = 0; var index = 0;
foreach (var parameter in Parameters) foreach (var parameter in Parameters)
{ {
@@ -221,7 +183,47 @@ namespace Discord.Interactions
} }
} }
protected async ValueTask<IResult> InvokeEventAndReturn(IInteractionContext context, IResult result) protected abstract Task InvokeModuleEvent(IInteractionContext context, IResult result);
protected abstract string GetLogString(IInteractionContext context);
/// <inheritdoc/>
public async Task<PreconditionResult> CheckPreconditionsAsync(IInteractionContext context, IServiceProvider services)
{
async Task<PreconditionResult> CheckGroups(ILookup<string, PreconditionAttribute> preconditions, string type)
{
foreach (IGrouping<string, PreconditionAttribute> preconditionGroup in preconditions)
{
if (preconditionGroup.Key == null)
{
foreach (PreconditionAttribute precondition in preconditionGroup)
{
var result = await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}
}
else
{
var results = new List<PreconditionResult>();
foreach (PreconditionAttribute precondition in preconditionGroup)
results.Add(await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false));
if (!results.Any(p => p.IsSuccess))
return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results);
}
}
return PreconditionGroupResult.FromSuccess();
}
var moduleResult = await CheckGroups(Module.GroupedPreconditions, "Module").ConfigureAwait(false);
if (!moduleResult.IsSuccess)
return moduleResult;
var commandResult = await CheckGroups(_groupedPreconditions, "Command").ConfigureAwait(false);
return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess();
}
protected async Task<T> InvokeEventAndReturn<T>(IInteractionContext context, T result) where T : IResult
{ {
await InvokeModuleEvent(context, result).ConfigureAwait(false); await InvokeModuleEvent(context, result).ConfigureAwait(false);
return result; return result;

View File

@@ -13,7 +13,7 @@ namespace Discord.Interactions
public class ComponentCommandInfo : CommandInfo<ComponentCommandParameterInfo> public class ComponentCommandInfo : CommandInfo<ComponentCommandParameterInfo>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<ComponentCommandParameterInfo> Parameters { get; } public override IReadOnlyList<ComponentCommandParameterInfo> Parameters { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override bool SupportsWildCards => true; public override bool SupportsWildCards => true;
@@ -25,48 +25,32 @@ namespace Discord.Interactions
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
=> await ExecuteAsync(context, services, null).ConfigureAwait(false);
/// <summary>
/// Execute this command using dependency injection.
/// </summary>
/// <param name="context">Context that will be injected to the <see cref="InteractionModuleBase{T}"/>.</param>
/// <param name="services">Services that will be used while initializing the <see cref="InteractionModuleBase{T}"/>.</param>
/// <param name="additionalArgs">Provide additional string parameters to the method along with the auto generated parameters.</param>
/// <returns>
/// A task representing the asynchronous command execution process.
/// </returns>
public async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services, params string[] additionalArgs)
{ {
if (context.Interaction is not IComponentInteraction componentInteraction) if (context.Interaction is not IComponentInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction");
return await ExecuteAsync(context, Parameters, additionalArgs, componentInteraction.Data, services); return await base.ExecuteAsync(context, services).ConfigureAwait(false);
} }
/// <inheritdoc/> protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<string> wildcardCaptures, IComponentInteractionData data,
IServiceProvider services)
{ {
var paramCount = paramList.Count(); var captures = (context as IRouteMatchContainer)?.SegmentMatches?.ToList();
var captureCount = wildcardCaptures?.Count() ?? 0; var captureCount = captures?.Count() ?? 0;
if (context.Interaction is not IComponentInteraction messageComponent)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Component Command Interaction");
try try
{ {
var args = new object[paramCount]; var data = (context.Interaction as IComponentInteraction).Data;
var args = new object[Parameters.Count];
for (var i = 0; i < paramCount; i++) for(var i = 0; i < Parameters.Count; i++)
{ {
var parameter = Parameters.ElementAt(i); var parameter = Parameters[i];
var isCapture = i < captureCount; var isCapture = i < captureCount;
if (isCapture ^ parameter.IsRouteSegmentParameter) if (isCapture ^ parameter.IsRouteSegmentParameter)
return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Argument type and parameter type didn't match (Wild Card capture/Component value)")).ConfigureAwait(false); return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Argument type and parameter type didn't match (Wild Card capture/Component value)")).ConfigureAwait(false);
var readResult = isCapture ? await parameter.TypeReader.ReadAsync(context, wildcardCaptures.ElementAt(i), services).ConfigureAwait(false) : var readResult = isCapture ? await parameter.TypeReader.ReadAsync(context, captures[i].Value, services).ConfigureAwait(false) :
await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false); await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false);
if (!readResult.IsSuccess) if (!readResult.IsSuccess)
@@ -75,7 +59,7 @@ namespace Discord.Interactions
args[i] = readResult.Value; args[i] = readResult.Value;
} }
return await RunAsync(context, args, services).ConfigureAwait(false); return ParseResult.FromSuccess(args);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -24,7 +24,7 @@ namespace Discord.Interactions
public GuildPermission? DefaultMemberPermissions { get; } public GuildPermission? DefaultMemberPermissions { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; } public override IReadOnlyList<CommandParameterInfo> Parameters { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override bool SupportsWildCards => false; public override bool SupportsWildCards => false;

View File

@@ -14,18 +14,23 @@ namespace Discord.Interactions
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
{ {
if (context.Interaction is not IMessageCommandInteraction messageCommand) if (context.Interaction is not IMessageCommandInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation");
return await base.ExecuteAsync(context, services).ConfigureAwait(false);
}
protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{
try try
{ {
object[] args = new object[1] { messageCommand.Data.Message }; object[] args = new object[1] { (context.Interaction as IMessageCommandInteraction).Data.Message };
return await RunAsync(context, args, services).ConfigureAwait(false); return Task.FromResult(ParseResult.FromSuccess(args) as IResult);
} }
catch (Exception ex) catch (Exception ex)
{ {
return ExecuteResult.FromError(ex); return Task.FromResult(ParseResult.FromError(ex) as IResult);
} }
} }

View File

@@ -17,15 +17,20 @@ namespace Discord.Interactions
if (context.Interaction is not IUserCommandInteraction userCommand) if (context.Interaction is not IUserCommandInteraction userCommand)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation");
return await base.ExecuteAsync(context, services).ConfigureAwait(false);
}
protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{
try try
{ {
object[] args = new object[1] { userCommand.Data.User }; object[] args = new object[1] { (context.Interaction as IUserCommandInteraction).Data.User };
return await RunAsync(context, args, services).ConfigureAwait(false); return Task.FromResult(ParseResult.FromSuccess(args) as IResult);
} }
catch (Exception ex) catch (Exception ex)
{ {
return ExecuteResult.FromError(ex); return Task.FromResult(ParseResult.FromError(ex) as IResult);
} }
} }

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.Tracing;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Discord.Interactions namespace Discord.Interactions
@@ -20,7 +19,7 @@ namespace Discord.Interactions
public override bool SupportsWildCards => true; public override bool SupportsWildCards => true;
/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<ModalCommandParameterInfo> Parameters { get; } public override IReadOnlyList<ModalCommandParameterInfo> Parameters { get; }
internal ModalCommandInfo(Builders.ModalCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) internal ModalCommandInfo(Builders.ModalCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService)
{ {
@@ -30,34 +29,29 @@ namespace Discord.Interactions
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
=> await ExecuteAsync(context, services, null).ConfigureAwait(false);
/// <summary>
/// Execute this command using dependency injection.
/// </summary>
/// <param name="context">Context that will be injected to the <see cref="InteractionModuleBase{T}"/>.</param>
/// <param name="services">Services that will be used while initializing the <see cref="InteractionModuleBase{T}"/>.</param>
/// <param name="additionalArgs">Provide additional string parameters to the method along with the auto generated parameters.</param>
/// <returns>
/// A task representing the asynchronous command execution process.
/// </returns>
public async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services, params string[] additionalArgs)
{ {
if (context.Interaction is not IModalInteraction modalInteraction) if (context.Interaction is not IModalInteraction modalInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Modal Interaction."); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Modal Interaction.");
return await base.ExecuteAsync(context, services).ConfigureAwait(false);
}
protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{
var captures = (context as IRouteMatchContainer)?.SegmentMatches?.ToList();
var captureCount = captures?.Count() ?? 0;
try try
{ {
var args = new object[Parameters.Count]; var args = new object[Parameters.Count];
var captureCount = additionalArgs?.Length ?? 0;
for(var i = 0; i < Parameters.Count; i++) for (var i = 0; i < Parameters.Count; i++)
{ {
var parameter = Parameters.ElementAt(i); var parameter = Parameters.ElementAt(i);
if(i < captureCount) if (i < captureCount)
{ {
var readResult = await parameter.TypeReader.ReadAsync(context, additionalArgs[i], services).ConfigureAwait(false); var readResult = await parameter.TypeReader.ReadAsync(context, captures[i].Value, services).ConfigureAwait(false);
if (!readResult.IsSuccess) if (!readResult.IsSuccess)
return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false); return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false);
@@ -69,13 +63,14 @@ namespace Discord.Interactions
if (!modalResult.IsSuccess) if (!modalResult.IsSuccess)
return await InvokeEventAndReturn(context, modalResult).ConfigureAwait(false); return await InvokeEventAndReturn(context, modalResult).ConfigureAwait(false);
if (modalResult is not ParseResult parseResult) if (modalResult is not TypeConverterResult converterResult)
return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.")); return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."));
args[i] = parseResult.Value; args[i] = converterResult.Value;
} }
} }
return await RunAsync(context, args, services);
return ParseResult.FromSuccess(args);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -33,7 +33,7 @@ namespace Discord.Interactions
public GuildPermission? DefaultMemberPermissions { get; } public GuildPermission? DefaultMemberPermissions { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override IReadOnlyCollection<SlashCommandParameterInfo> Parameters { get; } public override IReadOnlyList<SlashCommandParameterInfo> Parameters { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override bool SupportsWildCards => false; public override bool SupportsWildCards => false;
@@ -41,9 +41,9 @@ namespace Discord.Interactions
/// <summary> /// <summary>
/// Gets the flattened collection of command parameters and complex parameter fields. /// Gets the flattened collection of command parameters and complex parameter fields.
/// </summary> /// </summary>
public IReadOnlyCollection<SlashCommandParameterInfo> FlattenedParameters { get; } public IReadOnlyList<SlashCommandParameterInfo> FlattenedParameters { get; }
internal SlashCommandInfo (Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) internal SlashCommandInfo(Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService)
{ {
Description = builder.Description; Description = builder.Description;
DefaultPermission = builder.DefaultPermission; DefaultPermission = builder.DefaultPermission;
@@ -60,49 +60,45 @@ namespace Discord.Interactions
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<IResult> ExecuteAsync (IInteractionContext context, IServiceProvider services) public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services)
{ {
if(context.Interaction is not ISlashCommandInteraction slashCommand) if (context.Interaction is not ISlashCommandInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Slash Command Interaction"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Slash Command Interaction");
var options = slashCommand.Data.Options; return await base.ExecuteAsync(context, services);
}
protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services)
{
List<IApplicationCommandInteractionDataOption> GetOptions()
{
var options = (context.Interaction as ISlashCommandInteraction).Data.Options;
while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup)) while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup))
options = options.ElementAt(0)?.Options; options = options.ElementAt(0)?.Options;
return await ExecuteAsync(context, Parameters, options?.ToList(), services); return options.ToList();
} }
private async Task<IResult> ExecuteAsync (IInteractionContext context, IEnumerable<SlashCommandParameterInfo> paramList, var options = GetOptions();
List<IApplicationCommandInteractionDataOption> argList, IServiceProvider services) var args = new object[Parameters.Count];
for(var i = 0; i < Parameters.Count; i++)
{ {
try var parameter = Parameters[i];
{ var result = await ParseArgumentAsync(parameter, context, options, services).ConfigureAwait(false);
var slashCommandParameterInfos = paramList.ToList();
var args = new object[slashCommandParameterInfos.Count];
for (var i = 0; i < slashCommandParameterInfos.Count; i++)
{
var parameter = slashCommandParameterInfos[i];
var result = await ParseArgument(parameter, context, argList, services).ConfigureAwait(false);
if (!result.IsSuccess) if (!result.IsSuccess)
return await InvokeEventAndReturn(context, result).ConfigureAwait(false); return await InvokeEventAndReturn(context, ParseResult.FromError(result)).ConfigureAwait(false);
if (result is not ParseResult parseResult) if (result is not TypeConverterResult converterResult)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."); return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason.");
args[i] = parseResult.Value; args[i] = converterResult.Value;
}
return await RunAsync(context, args, services).ConfigureAwait(false);
}
catch(Exception ex)
{
return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false);
} }
return ParseResult.FromSuccess(args);
} }
private async Task<IResult> ParseArgument(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> argList, private async ValueTask<IResult> ParseArgumentAsync(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> argList,
IServiceProvider services) IServiceProvider services)
{ {
if (parameterInfo.IsComplexParameter) if (parameterInfo.IsComplexParameter)
@@ -111,32 +107,29 @@ namespace Discord.Interactions
for (var i = 0; i < ctorArgs.Length; i++) for (var i = 0; i < ctorArgs.Length; i++)
{ {
var result = await ParseArgument(parameterInfo.ComplexParameterFields.ElementAt(i), context, argList, services).ConfigureAwait(false); var result = await ParseArgumentAsync(parameterInfo.ComplexParameterFields.ElementAt(i), context, argList, services).ConfigureAwait(false);
if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;
if (result is not ParseResult parseResult) if (result is not TypeConverterResult converterResult)
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason.");
ctorArgs[i] = parseResult.Value; ctorArgs[i] = converterResult.Value;
} }
return ParseResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs)); return TypeConverterResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs));
} }
var arg = argList?.Find(x => string.Equals(x.Name, parameterInfo.Name, StringComparison.OrdinalIgnoreCase)); var arg = argList?.Find(x => string.Equals(x.Name, parameterInfo.Name, StringComparison.OrdinalIgnoreCase));
if (arg == default) if (arg == default)
return parameterInfo.IsRequired ? ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters") : return parameterInfo.IsRequired ? ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters") :
ParseResult.FromSuccess(parameterInfo.DefaultValue); TypeConverterResult.FromSuccess(parameterInfo.DefaultValue);
var typeConverter = parameterInfo.TypeConverter; var typeConverter = parameterInfo.TypeConverter;
var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false);
if (!readResult.IsSuccess)
return readResult; return readResult;
return ParseResult.FromSuccess(readResult.Value);
} }
protected override Task InvokeModuleEvent (IInteractionContext context, IResult result) protected override Task InvokeModuleEvent (IInteractionContext context, IResult result)

View File

@@ -103,7 +103,7 @@ namespace Discord.Interactions
public async Task<IResult> CreateModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false) public async Task<IResult> CreateModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false)
{ {
if (context.Interaction is not IModalInteraction modalInteraction) if (context.Interaction is not IModalInteraction modalInteraction)
return ParseResult.FromError(InteractionCommandError.Unsuccessful, "Provided context doesn't belong to a Modal Interaction."); return TypeConverterResult.FromError(InteractionCommandError.Unsuccessful, "Provided context doesn't belong to a Modal Interaction.");
services ??= EmptyServiceProvider.Instance; services ??= EmptyServiceProvider.Instance;
@@ -120,7 +120,7 @@ namespace Discord.Interactions
if (!throwOnMissingField) if (!throwOnMissingField)
args[i] = input.DefaultValue; args[i] = input.DefaultValue;
else else
return ParseResult.FromError(InteractionCommandError.BadArgs, $"Modal interaction is missing the required field: {input.CustomId}"); return TypeConverterResult.FromError(InteractionCommandError.BadArgs, $"Modal interaction is missing the required field: {input.CustomId}");
} }
else else
{ {
@@ -133,7 +133,7 @@ namespace Discord.Interactions
} }
} }
return ParseResult.FromSuccess(_initializer(args)); return TypeConverterResult.FromSuccess(_initializer(args));
} }
} }
} }

View File

@@ -822,7 +822,7 @@ namespace Discord.Interactions
SetMatchesIfApplicable(context, result); SetMatchesIfApplicable(context, result);
return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); return await result.Command.ExecuteAsync(context, services).ConfigureAwait(false);
} }
private async Task<IResult> ExecuteAutocompleteAsync (IInteractionContext context, IAutocompleteInteraction interaction, IServiceProvider services ) private async Task<IResult> ExecuteAutocompleteAsync (IInteractionContext context, IAutocompleteInteraction interaction, IServiceProvider services )
@@ -869,7 +869,7 @@ namespace Discord.Interactions
SetMatchesIfApplicable(context, result); SetMatchesIfApplicable(context, result);
return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); return await result.Command.ExecuteAsync(context, services).ConfigureAwait(false);
} }
private static void SetMatchesIfApplicable<T>(IInteractionContext context, SearchResult<T> searchResult) private static void SetMatchesIfApplicable<T>(IInteractionContext context, SearchResult<T> searchResult)

View File

@@ -2,9 +2,9 @@ using System;
namespace Discord.Interactions namespace Discord.Interactions
{ {
internal struct ParseResult : IResult public struct ParseResult : IResult
{ {
public object Value { get; } public object[] Args { get; }
public InteractionCommandError? Error { get; } public InteractionCommandError? Error { get; }
@@ -12,15 +12,15 @@ namespace Discord.Interactions
public bool IsSuccess => !Error.HasValue; public bool IsSuccess => !Error.HasValue;
private ParseResult(object value, InteractionCommandError? error, string reason) private ParseResult(object[] args, InteractionCommandError? error, string reason)
{ {
Value = value; Args = args;
Error = error; Error = error;
ErrorReason = reason; ErrorReason = reason;
} }
public static ParseResult FromSuccess(object value) => public static ParseResult FromSuccess(object[] args) =>
new ParseResult(value, null, null); new ParseResult(args, null, null);
public static ParseResult FromError(Exception exception) => public static ParseResult FromError(Exception exception) =>
new ParseResult(null, InteractionCommandError.Exception, exception.Message); new ParseResult(null, InteractionCommandError.Exception, exception.Message);