* 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>tags/3.9.0
| @@ -23,7 +23,7 @@ namespace Discord.Interactions | |||
| public string CommandName { get; } | |||
| /// <inheritdoc/> | |||
| public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; } | |||
| public override IReadOnlyList<CommandParameterInfo> Parameters { get; } | |||
| /// <inheritdoc/> | |||
| public override bool SupportsWildCards => false; | |||
| @@ -41,9 +41,12 @@ namespace Discord.Interactions | |||
| if (context.Interaction is not IAutocompleteInteraction) | |||
| 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/> | |||
| protected override Task InvokeModuleEvent(IInteractionContext context, IResult result) => | |||
| CommandService._autocompleteCommandExecutedEvent.InvokeAsync(this, context, result); | |||
| @@ -64,7 +64,7 @@ namespace Discord.Interactions | |||
| public IReadOnlyCollection<PreconditionAttribute> Preconditions { get; } | |||
| /// <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) | |||
| { | |||
| @@ -85,71 +85,16 @@ namespace Discord.Interactions | |||
| } | |||
| /// <inheritdoc/> | |||
| public abstract 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) | |||
| public virtual async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | |||
| { | |||
| switch (RunMode) | |||
| { | |||
| case RunMode.Sync: | |||
| { | |||
| 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); | |||
| } | |||
| return await ExecuteInternalAsync(context, services).ConfigureAwait(false); | |||
| case RunMode.Async: | |||
| _ = Task.Run(async () => | |||
| { | |||
| if (CommandService._autoServiceScopes) | |||
| { | |||
| using var scope = services?.CreateScope(); | |||
| await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false); | |||
| } | |||
| else | |||
| await ExecuteInternalAsync(context, args, services).ConfigureAwait(false); | |||
| await ExecuteInternalAsync(context, services).ConfigureAwait(false); | |||
| }); | |||
| break; | |||
| default: | |||
| @@ -159,16 +104,33 @@ namespace Discord.Interactions | |||
| 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); | |||
| using var scope = services?.CreateScope(); | |||
| if (CommandService._autoServiceScopes) | |||
| services = scope?.ServiceProvider ?? EmptyServiceProvider.Instance; | |||
| try | |||
| { | |||
| var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false); | |||
| if (!preconditionResult.IsSuccess) | |||
| 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; | |||
| 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); | |||
| return result; | |||
| @@ -13,7 +13,7 @@ namespace Discord.Interactions | |||
| public class ComponentCommandInfo : CommandInfo<ComponentCommandParameterInfo> | |||
| { | |||
| /// <inheritdoc/> | |||
| public override IReadOnlyCollection<ComponentCommandParameterInfo> Parameters { get; } | |||
| public override IReadOnlyList<ComponentCommandParameterInfo> Parameters { get; } | |||
| /// <inheritdoc/> | |||
| public override bool SupportsWildCards => true; | |||
| @@ -25,48 +25,32 @@ namespace Discord.Interactions | |||
| /// <inheritdoc/> | |||
| 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 await ExecuteAsync(context, Parameters, additionalArgs, componentInteraction.Data, services); | |||
| return await base.ExecuteAsync(context, services).ConfigureAwait(false); | |||
| } | |||
| /// <inheritdoc/> | |||
| public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<string> wildcardCaptures, IComponentInteractionData data, | |||
| IServiceProvider services) | |||
| protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||
| { | |||
| var paramCount = paramList.Count(); | |||
| var captureCount = wildcardCaptures?.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"); | |||
| var captures = (context as IRouteMatchContainer)?.SegmentMatches?.ToList(); | |||
| var captureCount = captures?.Count() ?? 0; | |||
| 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; | |||
| 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); | |||
| 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); | |||
| if (!readResult.IsSuccess) | |||
| @@ -75,7 +59,7 @@ namespace Discord.Interactions | |||
| args[i] = readResult.Value; | |||
| } | |||
| return await RunAsync(context, args, services).ConfigureAwait(false); | |||
| return ParseResult.FromSuccess(args); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| @@ -24,7 +24,7 @@ namespace Discord.Interactions | |||
| public GuildPermission? DefaultMemberPermissions { get; } | |||
| /// <inheritdoc/> | |||
| public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; } | |||
| public override IReadOnlyList<CommandParameterInfo> Parameters { get; } | |||
| /// <inheritdoc/> | |||
| public override bool SupportsWildCards => false; | |||
| @@ -14,18 +14,23 @@ namespace Discord.Interactions | |||
| /// <inheritdoc/> | |||
| 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 await base.ExecuteAsync(context, services).ConfigureAwait(false); | |||
| } | |||
| protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||
| { | |||
| 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) | |||
| { | |||
| return ExecuteResult.FromError(ex); | |||
| return Task.FromResult(ParseResult.FromError(ex) as IResult); | |||
| } | |||
| } | |||
| @@ -17,15 +17,20 @@ namespace Discord.Interactions | |||
| if (context.Interaction is not IUserCommandInteraction userCommand) | |||
| 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 | |||
| { | |||
| 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) | |||
| { | |||
| return ExecuteResult.FromError(ex); | |||
| return Task.FromResult(ParseResult.FromError(ex) as IResult); | |||
| } | |||
| } | |||
| @@ -1,7 +1,6 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Collections.Immutable; | |||
| using System.Diagnostics.Tracing; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.Interactions | |||
| @@ -20,7 +19,7 @@ namespace Discord.Interactions | |||
| public override bool SupportsWildCards => true; | |||
| /// <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) | |||
| { | |||
| @@ -30,34 +29,29 @@ namespace Discord.Interactions | |||
| /// <inheritdoc/> | |||
| 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) | |||
| 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 | |||
| { | |||
| 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); | |||
| 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) | |||
| return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false); | |||
| @@ -69,13 +63,14 @@ namespace Discord.Interactions | |||
| if (!modalResult.IsSuccess) | |||
| 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.")); | |||
| args[i] = parseResult.Value; | |||
| args[i] = converterResult.Value; | |||
| } | |||
| } | |||
| return await RunAsync(context, args, services); | |||
| return ParseResult.FromSuccess(args); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| @@ -33,7 +33,7 @@ namespace Discord.Interactions | |||
| public GuildPermission? DefaultMemberPermissions { get; } | |||
| /// <inheritdoc/> | |||
| public override IReadOnlyCollection<SlashCommandParameterInfo> Parameters { get; } | |||
| public override IReadOnlyList<SlashCommandParameterInfo> Parameters { get; } | |||
| /// <inheritdoc/> | |||
| public override bool SupportsWildCards => false; | |||
| @@ -41,9 +41,9 @@ namespace Discord.Interactions | |||
| /// <summary> | |||
| /// Gets the flattened collection of command parameters and complex parameter fields. | |||
| /// </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; | |||
| DefaultPermission = builder.DefaultPermission; | |||
| @@ -60,49 +60,45 @@ namespace Discord.Interactions | |||
| } | |||
| /// <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"); | |||
| var options = slashCommand.Data.Options; | |||
| while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup)) | |||
| options = options.ElementAt(0)?.Options; | |||
| return await ExecuteAsync(context, Parameters, options?.ToList(), services); | |||
| return await base.ExecuteAsync(context, services); | |||
| } | |||
| private async Task<IResult> ExecuteAsync (IInteractionContext context, IEnumerable<SlashCommandParameterInfo> paramList, | |||
| List<IApplicationCommandInteractionDataOption> argList, IServiceProvider services) | |||
| protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||
| { | |||
| try | |||
| List<IApplicationCommandInteractionDataOption> GetOptions() | |||
| { | |||
| 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) | |||
| return await InvokeEventAndReturn(context, result).ConfigureAwait(false); | |||
| var options = (context.Interaction as ISlashCommandInteraction).Data.Options; | |||
| if (result is not ParseResult parseResult) | |||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."); | |||
| while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup)) | |||
| options = options.ElementAt(0)?.Options; | |||
| args[i] = parseResult.Value; | |||
| } | |||
| return await RunAsync(context, args, services).ConfigureAwait(false); | |||
| return options.ToList(); | |||
| } | |||
| catch(Exception ex) | |||
| var options = GetOptions(); | |||
| var args = new object[Parameters.Count]; | |||
| for(var i = 0; i < Parameters.Count; i++) | |||
| { | |||
| return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false); | |||
| var parameter = Parameters[i]; | |||
| var result = await ParseArgumentAsync(parameter, context, options, services).ConfigureAwait(false); | |||
| if (!result.IsSuccess) | |||
| return await InvokeEventAndReturn(context, ParseResult.FromError(result)).ConfigureAwait(false); | |||
| if (result is not TypeConverterResult converterResult) | |||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); | |||
| args[i] = converterResult.Value; | |||
| } | |||
| 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) | |||
| { | |||
| if (parameterInfo.IsComplexParameter) | |||
| @@ -111,32 +107,29 @@ namespace Discord.Interactions | |||
| 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) | |||
| 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."); | |||
| 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)); | |||
| if (arg == default) | |||
| 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 readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); | |||
| if (!readResult.IsSuccess) | |||
| return readResult; | |||
| return ParseResult.FromSuccess(readResult.Value); | |||
| return readResult; | |||
| } | |||
| protected override Task InvokeModuleEvent (IInteractionContext context, IResult result) | |||
| @@ -103,7 +103,7 @@ namespace Discord.Interactions | |||
| public async Task<IResult> CreateModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false) | |||
| { | |||
| 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; | |||
| @@ -120,7 +120,7 @@ namespace Discord.Interactions | |||
| if (!throwOnMissingField) | |||
| args[i] = input.DefaultValue; | |||
| 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 | |||
| { | |||
| @@ -133,7 +133,7 @@ namespace Discord.Interactions | |||
| } | |||
| } | |||
| return ParseResult.FromSuccess(_initializer(args)); | |||
| return TypeConverterResult.FromSuccess(_initializer(args)); | |||
| } | |||
| } | |||
| } | |||
| @@ -822,7 +822,7 @@ namespace Discord.Interactions | |||
| 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 ) | |||
| @@ -869,7 +869,7 @@ namespace Discord.Interactions | |||
| 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) | |||
| @@ -2,9 +2,9 @@ using System; | |||
| namespace Discord.Interactions | |||
| { | |||
| internal struct ParseResult : IResult | |||
| public struct ParseResult : IResult | |||
| { | |||
| public object Value { get; } | |||
| public object[] Args { get; } | |||
| public InteractionCommandError? Error { get; } | |||
| @@ -12,15 +12,15 @@ namespace Discord.Interactions | |||
| 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; | |||
| ErrorReason = reason; | |||
| } | |||
| public static ParseResult FromSuccess(object value) => | |||
| new ParseResult(value, null, null); | |||
| public static ParseResult FromSuccess(object[] args) => | |||
| new ParseResult(args, null, null); | |||
| public static ParseResult FromError(Exception exception) => | |||
| new ParseResult(null, InteractionCommandError.Exception, exception.Message); | |||