Interaction Service Complex Parameters (#2155)
* Interaction Service Complex Parameters * add complex parameters * add complex parameters * fix build errors * add argument parsing * add nested complex parameter checks * add inline docs * add preferred constructor declaration * fix autocompletehandlers for complex parameters * make GetConstructor private * use flattened params in ToProps method * make DiscordType of SlashParameter nullable * add docs to Flattened parameters collection and move the GetComplexParameterCtor method * add inline docs to SlashCommandParameterBuilder.ComplexParameterFields * add check for validating required/optinal parameter order * implement change requests * return internal ParseResult as ExecuteResult Co-Authored-By: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> * fix merge errors Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com>
This commit is contained in:
@@ -397,7 +397,6 @@ namespace Discord.Interactions.Builders
|
||||
builder.Description = paramInfo.Name;
|
||||
builder.IsRequired = !paramInfo.IsOptional;
|
||||
builder.DefaultValue = paramInfo.DefaultValue;
|
||||
builder.SetParameterType(paramType, services);
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
@@ -435,12 +434,32 @@ namespace Discord.Interactions.Builders
|
||||
case MinValueAttribute minValue:
|
||||
builder.MinValue = minValue.Value;
|
||||
break;
|
||||
case ComplexParameterAttribute complexParameter:
|
||||
{
|
||||
builder.IsComplexParameter = true;
|
||||
ConstructorInfo ctor = GetComplexParameterConstructor(paramInfo.ParameterType.GetTypeInfo(), complexParameter);
|
||||
|
||||
foreach (var parameter in ctor.GetParameters())
|
||||
{
|
||||
if (parameter.IsDefined(typeof(ComplexParameterAttribute)))
|
||||
throw new InvalidOperationException("You cannot create nested complex parameters.");
|
||||
|
||||
builder.AddComplexParameterField(fieldBuilder => BuildSlashParameter(fieldBuilder, parameter, services));
|
||||
}
|
||||
|
||||
var initializer = builder.Command.Module.InteractionService._useCompiledLambda ?
|
||||
ReflectionUtils<object>.CreateLambdaConstructorInvoker(paramInfo.ParameterType.GetTypeInfo()) : ctor.Invoke;
|
||||
builder.ComplexParameterInitializer = args => initializer(args);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
builder.AddAttributes(attribute);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
builder.SetParameterType(paramType, services);
|
||||
|
||||
// Replace pascal casings with '-'
|
||||
builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower();
|
||||
}
|
||||
@@ -608,5 +627,41 @@ namespace Discord.Interactions.Builders
|
||||
propertyInfo.SetMethod?.IsStatic == false &&
|
||||
propertyInfo.IsDefined(typeof(ModalInputAttribute));
|
||||
}
|
||||
|
||||
private static ConstructorInfo GetComplexParameterConstructor(TypeInfo typeInfo, ComplexParameterAttribute complexParameter)
|
||||
{
|
||||
var ctors = typeInfo.GetConstructors();
|
||||
|
||||
if (ctors.Length == 0)
|
||||
throw new InvalidOperationException($"No constructor found for \"{typeInfo.FullName}\".");
|
||||
|
||||
if (complexParameter.PrioritizedCtorSignature is not null)
|
||||
{
|
||||
var ctor = typeInfo.GetConstructor(complexParameter.PrioritizedCtorSignature);
|
||||
|
||||
if (ctor is null)
|
||||
throw new InvalidOperationException($"No constructor was found with the signature: {string.Join(",", complexParameter.PrioritizedCtorSignature.Select(x => x.Name))}");
|
||||
|
||||
return ctor;
|
||||
}
|
||||
|
||||
var prioritizedCtors = ctors.Where(x => x.IsDefined(typeof(ComplexParameterCtorAttribute), true));
|
||||
|
||||
switch (prioritizedCtors.Count())
|
||||
{
|
||||
case > 1:
|
||||
throw new InvalidOperationException($"{nameof(ComplexParameterCtorAttribute)} can only be used once in a type.");
|
||||
case 1:
|
||||
return prioritizedCtors.First();
|
||||
}
|
||||
|
||||
switch (ctors.Length)
|
||||
{
|
||||
case > 1:
|
||||
throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\".");
|
||||
default:
|
||||
return ctors.First();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord.Interactions.Builders
|
||||
{
|
||||
@@ -10,6 +11,7 @@ namespace Discord.Interactions.Builders
|
||||
{
|
||||
private readonly List<ParameterChoice> _choices = new();
|
||||
private readonly List<ChannelType> _channelTypes = new();
|
||||
private readonly List<SlashCommandParameterBuilder> _complexParameterFields = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description of this parameter.
|
||||
@@ -36,6 +38,11 @@ namespace Discord.Interactions.Builders
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<ChannelType> ChannelTypes => _channelTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the constructor parameters of this parameter, if <see cref="IsComplexParameter"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SlashCommandParameterBuilder> ComplexParameterFields => _complexParameterFields;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this parameter should be configured for Autocomplete Interactions.
|
||||
/// </summary>
|
||||
@@ -46,6 +53,16 @@ namespace Discord.Interactions.Builders
|
||||
/// </summary>
|
||||
public TypeConverter TypeConverter { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this type should be treated as a complex parameter.
|
||||
/// </summary>
|
||||
public bool IsComplexParameter { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initializer delegate for this parameter, if <see cref="IsComplexParameter"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public ComplexParameterInitializer ComplexParameterInitializer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IAutocompleteHandler"/> of this parameter.
|
||||
/// </summary>
|
||||
@@ -60,7 +77,14 @@ namespace Discord.Interactions.Builders
|
||||
/// <param name="command">Parent command of this parameter.</param>
|
||||
/// <param name="name">Name of this command.</param>
|
||||
/// <param name="type">Type of this parameter.</param>
|
||||
public SlashCommandParameterBuilder(ICommandBuilder command, string name, Type type) : base(command, name, type) { }
|
||||
public SlashCommandParameterBuilder(ICommandBuilder command, string name, Type type, ComplexParameterInitializer complexParameterInitializer = null)
|
||||
: base(command, name, type)
|
||||
{
|
||||
ComplexParameterInitializer = complexParameterInitializer;
|
||||
|
||||
if (complexParameterInitializer is not null)
|
||||
IsComplexParameter = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="Description"/>.
|
||||
@@ -168,7 +192,47 @@ namespace Discord.Interactions.Builders
|
||||
public SlashCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null)
|
||||
{
|
||||
base.SetParameterType(type);
|
||||
TypeConverter = Command.Module.InteractionService.GetTypeConverter(ParameterType, services);
|
||||
|
||||
if(!IsComplexParameter)
|
||||
TypeConverter = Command.Module.InteractionService.GetTypeConverter(ParameterType, services);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a parameter builders to <see cref="ComplexParameterFields"/>.
|
||||
/// </summary>
|
||||
/// <param name="configure"><see cref="SlashCommandParameterBuilder"/> factory.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the added field has a <see cref="ComplexParameterAttribute"/>.</exception>
|
||||
public SlashCommandParameterBuilder AddComplexParameterField(Action<SlashCommandParameterBuilder> configure)
|
||||
{
|
||||
SlashCommandParameterBuilder builder = new(Command);
|
||||
configure(builder);
|
||||
|
||||
if(builder.IsComplexParameter)
|
||||
throw new InvalidOperationException("You cannot create nested complex parameters.");
|
||||
|
||||
_complexParameterFields.Add(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds parameter builders to <see cref="ComplexParameterFields"/>.
|
||||
/// </summary>
|
||||
/// <param name="fields">New parameter builders to be added to <see cref="ComplexParameterFields"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the added field has a <see cref="ComplexParameterAttribute"/>.</exception>
|
||||
public SlashCommandParameterBuilder AddComplexParameterFields(params SlashCommandParameterBuilder[] fields)
|
||||
{
|
||||
if(fields.Any(x => x.IsComplexParameter))
|
||||
throw new InvalidOperationException("You cannot create nested complex parameters.");
|
||||
|
||||
_complexParameterFields.AddRange(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user