Skip to content

Commit 84fad56

Browse files
authored
Improve simultaneous choices and autocomplete handling (#342)
* Improve simultaneous choices and autocomplete handling * Improve code readability * Add a missing type check and improve test * Simplify test * Add choices and autocomplete tests * Use InvalidDefinitionException instead of InvalidOperationException, make autocomplete not being supported throw instead of being ignored * Remove a ' file * Improve InvalidDefinitionException * Improve validation * Add sub slash command validation tests * Simplify test
1 parent 2bff0f5 commit 84fad56

14 files changed

Lines changed: 616 additions & 129 deletions

NetCord.Services/ApplicationCommands/ApplicationCommandService.cs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ public ValueTask<IExecutionResult> ExecuteAutocompleteAsync(TAutocompleteContext
3636
return new(NotFoundResult.Command);
3737
}
3838

39-
private protected override void OnAutocompleteAdd(IAutocompleteInfo autocompleteInfo)
39+
internal override Delegate CreateAutocompleteDelegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type autocompleteProviderType,
40+
IServiceResolverProvider serviceResolverProvider,
41+
MethodInfo method)
4042
{
41-
autocompleteInfo.InitializeAutocomplete<TAutocompleteContext>(_configuration.ServiceResolverProvider);
43+
return SlashCommandParameter<TContext>.CreateInvokeAutocompleteDelegate<TAutocompleteContext>(autocompleteProviderType, serviceResolverProvider, method);
4244
}
4345
}
4446

@@ -81,6 +83,8 @@ public ApplicationCommandService(ApplicationCommandServiceConfiguration<TContext
8183
/// <inheritdoc cref="IApplicationCommandService.GetCommands" />
8284
public IReadOnlyList<ApplicationCommandInfo<TContext>> GetCommands() => [.. _commands];
8385

86+
private AutocompleteDelegateProvider<TContext> GetAutocompleteDelegateProvider() => new(this);
87+
8488
[RequiresUnreferencedCode("Types might be removed")]
8589
public void AddModules(Assembly assembly)
8690
{
@@ -109,8 +113,7 @@ private void AddModuleCore([DynamicallyAccessedMembers(DynamicallyAccessedMember
109113

110114
foreach (var slashCommandAttribute in type.GetCustomAttributes<SlashCommandAttribute>())
111115
{
112-
SlashCommandGroupInfo<TContext> slashCommandGroupInfo = new(type, slashCommandAttribute, configuration);
113-
OnAutocompleteAdd(slashCommandGroupInfo);
116+
SlashCommandGroupInfo<TContext> slashCommandGroupInfo = new(type, slashCommandAttribute, configuration, GetAutocompleteDelegateProvider());
114117
AddCommandInfo(slashCommandGroupInfo);
115118

116119
slashCommandGroup = true;
@@ -138,8 +141,7 @@ private void AddModuleCore([DynamicallyAccessedMembers(DynamicallyAccessedMember
138141
{
139142
if (applicationCommandAttribute is SlashCommandAttribute slashCommandAttribute)
140143
{
141-
SlashCommandInfo<TContext> slashCommandInfo = new(method, type, slashCommandAttribute, configuration);
142-
OnAutocompleteAdd(slashCommandInfo);
144+
SlashCommandInfo<TContext> slashCommandInfo = new(method, type, slashCommandAttribute, configuration, GetAutocompleteDelegateProvider());
143145
AddCommandInfo(slashCommandInfo);
144146
}
145147

@@ -157,16 +159,12 @@ private void AddModuleCore([DynamicallyAccessedMembers(DynamicallyAccessedMember
157159

158160
public void AddSlashCommand(SlashCommandBuilder builder)
159161
{
160-
SlashCommandInfo<TContext> info = new(builder, _configuration);
161-
OnAutocompleteAdd(info);
162-
AddCommandInfo(info);
162+
AddCommandInfo(new SlashCommandInfo<TContext>(builder, _configuration, GetAutocompleteDelegateProvider()));
163163
}
164164

165165
public void AddSlashCommandGroup(SlashCommandGroupBuilder builder)
166166
{
167-
SlashCommandGroupInfo<TContext> info = new(builder, _configuration);
168-
OnAutocompleteAdd(info);
169-
AddCommandInfo(info);
167+
AddCommandInfo(new SlashCommandGroupInfo<TContext>(builder, _configuration, GetAutocompleteDelegateProvider()));
170168
}
171169

172170
public void AddUserCommand(UserCommandBuilder builder)
@@ -221,7 +219,10 @@ public async ValueTask<IExecutionResult> ExecuteAsync(TContext context, IService
221219
return NotFoundResult.Command;
222220
}
223221

224-
private protected virtual void OnAutocompleteAdd(IAutocompleteInfo autocompleteInfo)
222+
internal virtual Delegate CreateAutocompleteDelegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type autocompleteProviderType, IServiceResolverProvider serviceResolverProvider, MethodInfo method)
225223
{
224+
throw new InvalidDefinitionException(
225+
$"Autocomplete is not supported by '{typeof(ApplicationCommandService<>)}'. Use '{typeof(ApplicationCommandService<,>)}' instead to enable autocomplete support.",
226+
method);
226227
}
227228
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.Reflection;
3+
4+
namespace NetCord.Services.ApplicationCommands;
5+
6+
internal readonly struct AutocompleteDelegateProvider<TContext>(ApplicationCommandService<TContext> service) where TContext : IApplicationCommandContext
7+
{
8+
public Delegate Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type autocompleteProviderType,
9+
IServiceResolverProvider serviceResolverProvider,
10+
MethodInfo method)
11+
{
12+
return service.CreateAutocompleteDelegate(autocompleteProviderType, serviceResolverProvider, method);
13+
}
14+
}

NetCord.Services/ApplicationCommands/IAutocompleteInfo.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,4 @@ namespace NetCord.Services.ApplicationCommands;
33
public interface IAutocompleteInfo
44
{
55
public ValueTask<IExecutionResult> InvokeAutocompleteAsync<TAutocompleteContext>(TAutocompleteContext context, IReadOnlyList<ApplicationCommandInteractionDataOption> options, IServiceProvider? serviceProvider) where TAutocompleteContext : IAutocompleteInteractionContext;
6-
7-
internal void InitializeAutocomplete<TAutocompleteContext>(IServiceResolverProvider serviceResolverProvider) where TAutocompleteContext : IAutocompleteInteractionContext;
86
}

NetCord.Services/ApplicationCommands/SlashCommandGroupInfo.cs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ public class SlashCommandGroupInfo<TContext> : ApplicationCommandInfo<TContext>,
1313
[UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", Justification = "'DynamicallyAccessedMembersAttribute' is inherited for nested types")]
1414
internal SlashCommandGroupInfo([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicNestedTypes)] Type type,
1515
SlashCommandAttribute attribute,
16-
ApplicationCommandServiceConfiguration<TContext> configuration) : base(attribute,
17-
configuration,
18-
type,
19-
out var typeAttributes)
16+
ApplicationCommandServiceConfiguration<TContext> configuration,
17+
AutocompleteDelegateProvider<TContext> autocompleteDelegateProvider) : base(attribute,
18+
configuration,
19+
type,
20+
out var typeAttributes)
2021
{
2122
Description = attribute.Description;
2223

@@ -27,7 +28,7 @@ internal SlashCommandGroupInfo([DynamicallyAccessedMembers(DynamicallyAccessedMe
2728
foreach (var method in type.GetMethods())
2829
{
2930
foreach (var subSlashCommandAttribute in method.GetCustomAttributes<SubSlashCommandAttribute>())
30-
subCommands.Add(new(subSlashCommandAttribute.Name!, new SubSlashCommandInfo<TContext>(method, type, subSlashCommandAttribute, configuration, LocalizationPath)));
31+
subCommands.Add(new(subSlashCommandAttribute.Name, new SubSlashCommandInfo<TContext>(method, type, subSlashCommandAttribute, configuration, LocalizationPath, autocompleteDelegateProvider)));
3132
}
3233

3334
var baseType = typeof(BaseApplicationCommandModule<TContext>);
@@ -37,20 +38,21 @@ internal SlashCommandGroupInfo([DynamicallyAccessedMembers(DynamicallyAccessedMe
3738
continue;
3839

3940
foreach (var subSlashCommandAttribute in nested.GetCustomAttributes<SubSlashCommandAttribute>())
40-
subCommands.Add(new(subSlashCommandAttribute.Name!, new SubSlashCommandGroupInfo<TContext>(nested, subSlashCommandAttribute, configuration, LocalizationPath)));
41+
subCommands.Add(new(subSlashCommandAttribute.Name, new SubSlashCommandGroupInfo<TContext>(nested, subSlashCommandAttribute, configuration, LocalizationPath, autocompleteDelegateProvider)));
4142
}
4243

4344
if (subCommands.Count == 0)
44-
throw new InvalidOperationException($"No sub commands found in '{type.FullName}'.");
45+
throw new InvalidDefinitionException($"No sub commands found.", type);
4546

4647
SubCommands = subCommands.ToFrozenDictionary();
4748
}
4849

4950
internal SlashCommandGroupInfo(SlashCommandGroupBuilder builder,
50-
ApplicationCommandServiceConfiguration<TContext> configuration) : base(builder,
51-
configuration,
52-
null,
53-
out _)
51+
ApplicationCommandServiceConfiguration<TContext> configuration,
52+
AutocompleteDelegateProvider<TContext> autocompleteDelegateProvider) : base(builder,
53+
configuration,
54+
null,
55+
out _)
5456
{
5557
Description = builder.Description;
5658

@@ -64,7 +66,7 @@ internal SlashCommandGroupInfo(SlashCommandGroupBuilder builder,
6466
for (int i = 0; i < subCommandCount; i++)
6567
{
6668
var subCommandBuilder = subCommandBuilders[i];
67-
SubSlashCommandInfo<TContext> subCommand = new(subCommandBuilder, configuration, LocalizationPath);
69+
SubSlashCommandInfo<TContext> subCommand = new(subCommandBuilder, configuration, LocalizationPath, autocompleteDelegateProvider);
6870
subCommands.Add(new(subCommandBuilder.Name, subCommand));
6971
}
7072

@@ -74,10 +76,13 @@ internal SlashCommandGroupInfo(SlashCommandGroupBuilder builder,
7476
for (int i = 0; i < subCommandGroupCount; i++)
7577
{
7678
var subCommandGroupBuilder = subCommandGroupBuilders[i];
77-
SubSlashCommandGroupInfo<TContext> subCommandGroup = new(subCommandGroupBuilder, configuration, LocalizationPath);
79+
SubSlashCommandGroupInfo<TContext> subCommandGroup = new(subCommandGroupBuilder, configuration, LocalizationPath, autocompleteDelegateProvider);
7880
subCommands.Add(new(subCommandGroupBuilder.Name, subCommandGroup));
7981
}
8082

83+
if (subCommands.Count == 0)
84+
throw new InvalidDefinitionException($"No sub commands found.", builder.Name);
85+
8186
SubCommands = subCommands.ToFrozenDictionary();
8287
}
8388

@@ -129,10 +134,4 @@ public ValueTask<IExecutionResult> InvokeAutocompleteAsync<TAutocompleteContext>
129134

130135
return new(NotFoundResult.Command);
131136
}
132-
133-
void IAutocompleteInfo.InitializeAutocomplete<TAutocompleteContext>(IServiceResolverProvider serviceResolverProvider)
134-
{
135-
foreach (var subCommand in SubCommands.Values)
136-
subCommand.InitializeAutocomplete<TAutocompleteContext>(serviceResolverProvider);
137-
}
138137
}

NetCord.Services/ApplicationCommands/SlashCommandInfo.cs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ public class SlashCommandInfo<TContext> : ApplicationCommandInfo<TContext>, IAut
1212
internal SlashCommandInfo(MethodInfo method,
1313
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type declaringType,
1414
SlashCommandAttribute attribute,
15-
ApplicationCommandServiceConfiguration<TContext> configuration) : base(attribute, configuration, method, out var methodAttributes)
15+
ApplicationCommandServiceConfiguration<TContext> configuration,
16+
AutocompleteDelegateProvider<TContext> autocompleteDelegateProvider) : base(attribute, configuration, method, out var methodAttributes)
1617
{
1718
Description = attribute.Description;
1819

19-
var parameters = SlashCommandParametersHelper.GetParameters(method.GetParameters(), method, configuration, LocalizationPath);
20+
var parameters = SlashCommandParametersHelper.GetParameters(method.GetParameters(), method, configuration, LocalizationPath, autocompleteDelegateProvider);
2021
Parameters = parameters;
2122
ParametersDictionary = parameters.ToFrozenDictionary(p => p.Name);
2223

@@ -26,10 +27,11 @@ internal SlashCommandInfo(MethodInfo method,
2627
}
2728

2829
internal SlashCommandInfo(SlashCommandBuilder builder,
29-
ApplicationCommandServiceConfiguration<TContext> configuration) : base(builder,
30-
configuration,
31-
builder.Handler.Method,
32-
out var methodAttributes)
30+
ApplicationCommandServiceConfiguration<TContext> configuration,
31+
AutocompleteDelegateProvider<TContext> autocompleteDelegateProvider) : base(builder,
32+
configuration,
33+
builder.Handler.Method,
34+
out var methodAttributes)
3335
{
3436
Description = builder.Description;
3537

@@ -39,7 +41,7 @@ internal SlashCommandInfo(SlashCommandBuilder builder,
3941

4042
var split = ParametersHelper.SplitHandlerParameters<TContext>(method);
4143

42-
var parameters = SlashCommandParametersHelper.GetParameters(split.Parameters, method, configuration, LocalizationPath);
44+
var parameters = SlashCommandParametersHelper.GetParameters(split.Parameters, method, configuration, LocalizationPath, autocompleteDelegateProvider);
4345
Parameters = parameters;
4446
ParametersDictionary = parameters.ToFrozenDictionary(p => p.Name);
4547

@@ -120,12 +122,4 @@ public async ValueTask<IExecutionResult> InvokeAutocompleteAsync<TAutocompleteCo
120122

121123
return NotFoundResult.Command;
122124
}
123-
124-
void IAutocompleteInfo.InitializeAutocomplete<TAutocompleteContext>(IServiceResolverProvider serviceResolverProvider)
125-
{
126-
var parameters = Parameters;
127-
var count = parameters.Count;
128-
for (int i = 0; i < count; i++)
129-
parameters[i].InitializeAutocomplete<TAutocompleteContext>(serviceResolverProvider);
130-
}
131125
}

0 commit comments

Comments
 (0)