Browse Source

add inline docs

pull/2169/head
Cenk Ergen 3 years ago
parent
commit
e9b1fa6ec7
14 changed files with 181 additions and 78 deletions
  1. +3
    -0
      src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs
  2. +1
    -0
      src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs
  3. +1
    -2
      src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
  4. +28
    -2
      src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs
  5. +6
    -3
      src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs
  6. +3
    -0
      src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs
  7. +14
    -4
      src/Discord.Net.Interactions/Info/ModalInfo.cs
  8. +10
    -2
      src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs
  9. +63
    -35
      src/Discord.Net.Interactions/InteractionService.cs
  10. +22
    -1
      src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs
  11. +0
    -2
      src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs
  12. +2
    -0
      src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs
  13. +0
    -27
      src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs
  14. +28
    -0
      src/Discord.Net.Interactions/TypeReaders/TypeReader.cs

+ 3
- 0
src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs View File

@@ -38,6 +38,9 @@ namespace Discord.Interactions.Builders
/// </summary>
Type Type { get; }

/// <summary>
/// Get the <see cref="ComponentTypeConverter"/> assigned to this input.
/// </summary>
ComponentTypeConverter TypeConverter { get; }

/// <summary>


+ 1
- 0
src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs View File

@@ -33,6 +33,7 @@ namespace Discord.Interactions.Builders
/// <inheritdoc/>
public Type Type { get; private set; }

/// <inheritdoc/>
public ComponentTypeConverter TypeConverter { get; private set; }

/// <inheritdoc/>


+ 1
- 2
src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs View File

@@ -446,8 +446,7 @@ namespace Discord.Interactions.Builders

private static void BuildComponentParameter(ComponentCommandParameterBuilder builder, ParameterInfo paramInfo, bool isComponentParam)
{
builder.SetAsRouteSegment(!isComponentParam);

builder.SetIsRouteSegment(!isComponentParam);
BuildParameter(builder, paramInfo);
}



+ 28
- 2
src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs View File

@@ -4,13 +4,32 @@ namespace Discord.Interactions.Builders
{
public class ComponentCommandParameterBuilder : ParameterBuilder<ComponentCommandParameterInfo, ComponentCommandParameterBuilder>
{
/// <summary>
/// Get the <see cref="ComponentTypeConverter"/> assigned to this parameter, if <see cref="IsRouteSegmentParameter"/> is <see langword="false"/>.
/// </summary>
public ComponentTypeConverter TypeConverter { get; private set; }

/// <summary>
/// Get the <see cref="Discord.Interactions.TypeReader"/> assigned to this parameter, if <see cref="IsRouteSegmentParameter"/> is <see langword="true"/>.
/// </summary>
public TypeReader TypeReader { get; private set; }

/// <summary>
/// Gets whether this parameter is a CustomId segment or a Component value parameter.
/// </summary>
public bool IsRouteSegmentParameter { get; private set; }

/// <inheritdoc/>
protected override ComponentCommandParameterBuilder Instance => this;

public ComponentCommandParameterBuilder(ICommandBuilder command) : base(command) { }
internal ComponentCommandParameterBuilder(ICommandBuilder command) : base(command) { }

/// <summary>
/// Initializes a new <see cref="ComponentCommandParameterBuilder"/>.
/// </summary>
/// <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 ComponentCommandParameterBuilder(ICommandBuilder command, string name, Type type) : base(command, name, type) { }

/// <inheritdoc/>
@@ -36,7 +55,14 @@ namespace Discord.Interactions.Builders
return this;
}

public ComponentCommandParameterBuilder SetAsRouteSegment(bool isRouteSegment)
/// <summary>
/// Sets <see cref="IsRouteSegmentParameter"/>.
/// </summary>
/// <param name="isRouteSegment">New value of the <see cref="IsRouteSegmentParameter"/>.</param>
/// <returns>
/// The builder instance.
/// </returns>
public ComponentCommandParameterBuilder SetIsRouteSegment(bool isRouteSegment)
{
IsRouteSegmentParameter = isRouteSegment;
return this;


+ 6
- 3
src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs View File

@@ -52,15 +52,18 @@ namespace Discord.Interactions
if (additionalArgs is not null)
args.AddRange(additionalArgs);

var modalResult = await Modal.ParseModalAsync(context, services, Module.CommandService._exitOnMissingModalField).ConfigureAwait(false);
var modalResult = await Modal.CreateModalAsync(context, services, Module.CommandService._exitOnMissingModalField).ConfigureAwait(false);

if(!modalResult.IsSuccess || modalResult is not ParseResult parseResult)
if(!modalResult.IsSuccess)
{
await InvokeModuleEvent(context, modalResult).ConfigureAwait(false);
return modalResult;
}

args.Add(parseResult.Value);
if(modalResult is ParseResult parseResult)
args.Add(parseResult.Value);
else
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.");

return await RunAsync(context, args.ToArray(), services);
}


+ 3
- 0
src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs View File

@@ -39,6 +39,9 @@ namespace Discord.Interactions
/// </summary>
public Type Type { get; }

/// <summary>
/// Gets the <see cref="ComponentTypeConverter"/> assigned to this component.
/// </summary>
public ComponentTypeConverter TypeConverter { get; }

/// <summary>


+ 14
- 4
src/Discord.Net.Interactions/Info/ModalInfo.cs View File

@@ -62,10 +62,11 @@ namespace Discord.Interactions
/// <summary>
/// Creates an <see cref="IModal"/> and fills it with provided message components.
/// </summary>
/// <param name="components"><see cref="IModalInteraction"/> that will be injected into the modal.</param>
/// <param name="modalInteraction"><see cref="IModalInteraction"/> that will be injected into the modal.</param>
/// <returns>
/// A <see cref="IModal"/> filled with the provided components.
/// </returns>
[Obsolete("This method is no longer supported with the introduction of Component TypeConverters, please use the CreateModalAsync method.")]
public IModal CreateModal(IModalInteraction modalInteraction, bool throwOnMissingField = false)
{
var args = new object[Components.Count];
@@ -90,10 +91,19 @@ namespace Discord.Interactions
return _initializer(args);
}

internal async Task<IResult> ParseModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false)
/// <summary>
/// Creates an <see cref="IModal"/> and fills it with provided message components.
/// </summary>
/// <param name="context">Context of the <see cref="IModalInteraction"/> that will be injected into the modal.</param>
/// <param name="services">Services to be passed onto the <see cref="ComponentTypeConverter"/>s of the modal fiels.</param>
/// <param name="throwOnMissingField">Wheter or not this method should exit on encountering a missing modal field.</param>
/// <returns>
/// A <see cref="TypeConverterResult"/> if a type conversion has failed, else a <see cref="ParseResult"/>.
/// </returns>
public async Task<IResult> CreateModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false)
{
if (context.Interaction is not IModalInteraction modalInteraction)
throw new InvalidOperationException("Provided context doesn't belong to a Modal Interaction.");
return ParseResult.FromError(InteractionCommandError.Unsuccessful, "Provided context doesn't belong to a Modal Interaction.");

services ??= EmptyServiceProvider.Instance;

@@ -110,7 +120,7 @@ namespace Discord.Interactions
if (!throwOnMissingField)
args[i] = input.DefaultValue;
else
throw new InvalidOperationException($"Modal interaction is missing the required field: {input.CustomId}");
return ParseResult.FromError(InteractionCommandError.BadArgs, $"Modal interaction is missing the required field: {input.CustomId}");
}
else
{


+ 10
- 2
src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs View File

@@ -1,5 +1,4 @@
using Discord.Interactions.Builders;
using System;

namespace Discord.Interactions
{
@@ -10,10 +9,19 @@ namespace Discord.Interactions
{
/// <summary>
/// Gets the <see cref="ComponentTypeConverter"/> that will be used to convert a message component value into
/// <see cref="CommandParameterInfo.ParameterType"/>.
/// <see cref="CommandParameterInfo.ParameterType"/>, if <see cref="IsRouteSegmentParameter"/> is false.
/// </summary>
public ComponentTypeConverter TypeConverter { get; }

/// <summary>
/// Gets the <see cref="TypeReader"/> that will be used to convert a CustomId segment value into
/// <see cref="CommandParameterInfo.ParameterType"/>, if <see cref="IsRouteSegmentParameter"/> is <see langword="true"/>.
/// </summary>
public TypeReader TypeReader { get; }

/// <summary>
/// Gets whether this parameter is a CustomId segment or a component value parameter.
/// </summary>
public bool IsRouteSegmentParameter { get; }

internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command)


+ 63
- 35
src/Discord.Net.Interactions/InteractionService.cs View File

@@ -181,30 +181,30 @@ namespace Discord.Interactions
_autoServiceScopes = config.AutoServiceScopes;
_restResponseCallback = config.RestResponseCallback;

_typeConverterMap = new TypeMap<TypeConverter, IApplicationCommandInteractionDataOption>(this, new Dictionary<Type, TypeConverter>
{
[typeof(TimeSpan)] = new TimeSpanConverter()
}, new Dictionary<Type, Type>
{
[typeof(IChannel)] = typeof(DefaultChannelConverter<>),
[typeof(IRole)] = typeof(DefaultRoleConverter<>),
[typeof(IAttachment)] = typeof(DefaultAttachmentConverter<>),
[typeof(IUser)] = typeof(DefaultUserConverter<>),
[typeof(IMentionable)] = typeof(DefaultMentionableConverter<>),
[typeof(IConvertible)] = typeof(DefaultValueConverter<>),
[typeof(Enum)] = typeof(EnumConverter<>),
[typeof(Nullable<>)] = typeof(NullableConverter<>),
});
_compTypeConverterMap = new TypeMap<ComponentTypeConverter, IComponentInteractionData>(this, new Dictionary<Type, ComponentTypeConverter>(),
new Dictionary<Type, Type>
_typeConverterMap = new TypeMap<TypeConverter, IApplicationCommandInteractionDataOption>(this, new ConcurrentDictionary<Type, TypeConverter>
{
[typeof(TimeSpan)] = new TimeSpanConverter()
}, new ConcurrentDictionary<Type, Type>
{
[typeof(IChannel)] = typeof(DefaultChannelConverter<>),
[typeof(IRole)] = typeof(DefaultRoleConverter<>),
[typeof(IAttachment)] = typeof(DefaultAttachmentConverter<>),
[typeof(IUser)] = typeof(DefaultUserConverter<>),
[typeof(IMentionable)] = typeof(DefaultMentionableConverter<>),
[typeof(IConvertible)] = typeof(DefaultValueConverter<>),
[typeof(Enum)] = typeof(EnumConverter<>),
[typeof(Nullable<>)] = typeof(NullableConverter<>),
});
_compTypeConverterMap = new TypeMap<ComponentTypeConverter, IComponentInteractionData>(this, new ConcurrentDictionary<Type, ComponentTypeConverter>(),
new ConcurrentDictionary<Type, Type>
{
[typeof(Array)] = typeof(DefaultArrayComponentConverter<>),
[typeof(IConvertible)] = typeof(DefaultValueComponentConverter<>)
});

_typeReaderMap = new TypeMap<TypeReader, string>(this, new Dictionary<Type, TypeReader>(),
new Dictionary<Type, Type>
_typeReaderMap = new TypeMap<TypeReader, string>(this, new ConcurrentDictionary<Type, TypeReader>(),
new ConcurrentDictionary<Type, Type>
{
[typeof(IChannel)] = typeof(DefaultChannelReader<>),
[typeof(IRole)] = typeof(DefaultRoleReader<>),
@@ -827,56 +827,84 @@ namespace Discord.Interactions
_compTypeConverterMap.Get(type, services);

/// <summary>
/// Add a concrete type <see cref="TypeReader"/>.
/// Add a concrete type <see cref="ComponentTypeConverter"/>.
/// </summary>
/// <typeparam name="T">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</typeparam>
/// <param name="converter">The <see cref="TypeReader"/> instance.</param>
/// <typeparam name="T">Primary target <see cref="Type"/> of the <see cref="ComponentTypeConverter"/>.</typeparam>
/// <param name="converter">The <see cref="ComponentTypeConverter"/> instance.</param>
public void AddComponentTypeConverter<T>(ComponentTypeConverter converter) =>
AddComponentTypeConverter(typeof(T), converter);

/// <summary>
/// Add a concrete type <see cref="TypeReader"/>.
/// Add a concrete type <see cref="ComponentTypeConverter"/>.
/// </summary>
/// <param name="type">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</param>
/// <param name="converter">The <see cref="TypeReader"/> instance.</param>
/// <param name="type">Primary target <see cref="Type"/> of the <see cref="ComponentTypeConverter"/>.</param>
/// <param name="converter">The <see cref="ComponentTypeConverter"/> instance.</param>
public void AddComponentTypeConverter(Type type, ComponentTypeConverter converter) =>
_compTypeConverterMap.AddConcrete(type, converter);

/// <summary>
/// Add a generic type <see cref="CompTypeConverter{T}"/>.
/// Add a generic type <see cref="ComponentTypeConverter{T}"/>.
/// </summary>
/// <typeparam name="T">Generic Type constraint of the <see cref="Type"/> of the <see cref="CompTypeConverter{T}"/>.</typeparam>
/// <param name="converterType">Type of the <see cref="CompTypeConverter{T}"/>.</param>

/// <typeparam name="T">Generic Type constraint of the <see cref="Type"/> of the <see cref="ComponentTypeConverter{T}"/>.</typeparam>
/// <param name="converterType">Type of the <see cref="ComponentTypeConverter{T}"/>.</param>
public void AddGenericComponentTypeConverter<T>(Type converterType) =>
AddGenericComponentTypeConverter(typeof(T), converterType);

/// <summary>
/// Add a generic type <see cref="CompTypeConverter{T}"/>.
/// Add a generic type <see cref="ComponentTypeConverter{T}"/>.
/// </summary>
/// <param name="targetType">Generic Type constraint of the <see cref="Type"/> of the <see cref="CompTypeConverter{T}"/>.</param>
/// <param name="converterType">Type of the <see cref="CompTypeConverter{T}"/>.</param>
/// <param name="targetType">Generic Type constraint of the <see cref="Type"/> of the <see cref="ComponentTypeConverter{T}"/>.</param>
/// <param name="converterType">Type of the <see cref="ComponentTypeConverter{T}"/>.</param>
public void AddGenericComponentTypeConverter(Type targetType, Type converterType) =>
_compTypeConverterMap.AddGeneric(targetType, converterType);

public Task<string> SerializeValue<T>(T obj, IServiceProvider services = null) =>
_compTypeConverterMap.Get(typeof(T), services).SerializeAsync(obj);

internal TypeReader GetTypeReader(Type type, IServiceProvider services = null) =>
_typeReaderMap.Get(type, services);

/// <summary>
/// Add a concrete type <see cref="TypeReader"/>.
/// </summary>
/// <typeparam name="T">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</typeparam>
/// <param name="converter">The <see cref="TypeReader"/> instance.</param>
public void AddTypeReader<T>(TypeReader reader) =>
AddTypeReader(typeof(T), reader);

/// <summary>
/// Add a concrete type <see cref="TypeReader"/>.
/// </summary>
/// <param name="type">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</param>
/// <param name="converter">The <see cref="TypeReader"/> instance.</param>
public void AddTypeReader(Type type, TypeReader reader) =>
_typeReaderMap.AddConcrete(type, reader);

/// <summary>
/// Add a generic type <see cref="TypeReader{T}"/>.
/// </summary>
/// <typeparam name="T">Generic Type constraint of the <see cref="Type"/> of the <see cref="TypeReader{T}"/>.</typeparam>
/// <param name="readerType">Type of the <see cref="TypeReader{T}"/>.</param>
public void AddGenericTypeReader<T>(Type readerType) =>
AddGenericTypeReader(typeof(T), readerType);

/// <summary>
/// Add a generic type <see cref="TypeReader{T}"/>.
/// </summary>
/// <param name="targetType">Generic Type constraint of the <see cref="Type"/> of the <see cref="TypeReader{T}"/>.</param>
/// <param name="readerType">Type of the <see cref="TypeReader{T}"/>.</param>
public void AddGenericTypeReader(Type targetType, Type readerType) =>
_typeReaderMap.AddGeneric(targetType, readerType);

/// <summary>
/// Serialize an object using a <see cref="TypeReader"/> into a <see cref="string"/> to be placed in a Component CustomId.
/// </summary>
/// <typeparam name="T">Type of the object to be serialized.</typeparam>
/// <param name="obj">Object to be serialized.</param>
/// <param name="services">Services that will be passed on to the TypeReader.</param>
/// <returns>
/// A task representing the conversion process. The task result contains the result of the conversion.
/// </returns>
public Task<string> SerializeValue<T>(T obj, IServiceProvider services = null) =>
_typeReaderMap.Get(typeof(T), services).SerializeAsync(obj);

/// <summary>
/// Loads and caches an <see cref="ModalInfo"/> for the provided <see cref="IModal"/>.
/// </summary>


+ 22
- 1
src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs View File

@@ -3,15 +3,36 @@ using System.Threading.Tasks;

namespace Discord.Interactions
{
/// <summary>
/// Base class for creating Component TypeConverters. <see cref="InteractionService"/> uses TypeConverters to interface with Slash Command parameters.
/// </summary>
public abstract class ComponentTypeConverter : ITypeConverter<IComponentInteractionData>
{
/// <summary>
/// Will be used to search for alternative TypeConverters whenever the Command Service encounters an unknown parameter type.
/// </summary>
/// <param name="type">An object type.</param>
/// <returns>
/// The boolean result.
/// </returns>
public abstract bool CanConvertTo(Type type);

/// <summary>
/// Will be used to read the incoming payload before executing the method body.
/// </summary>
/// <param name="context">Command exexution context.</param>
/// <param name="option">Recieved option payload.</param>
/// <param name="services">Service provider that will be used to initialize the command module.</param>
/// <returns>
/// The result of the read process.
/// </returns>
public abstract Task<TypeConverterResult> ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services);
public virtual Task<string> SerializeAsync(object obj) => Task.FromResult(obj.ToString());
}

/// <inheritdoc/>
public abstract class ComponentTypeConverter<T> : ComponentTypeConverter
{
/// <inheritdoc/>
public sealed override bool CanConvertTo(Type type) =>
typeof(T).IsAssignableFrom(type);
}


+ 0
- 2
src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs View File

@@ -1,7 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Discord.Interactions


+ 2
- 0
src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs View File

@@ -20,6 +20,8 @@ namespace Discord.Interactions
else
return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} must be a valid {typeof(T).Name} snowflake to be parsed.");
}

public override Task<string> SerializeAsync(object obj) => Task.FromResult((obj as ISnowflakeEntity)?.Id.ToString());
}

internal sealed class DefaultUserReader<T> : DefaultSnowflakeReader<T>


+ 0
- 27
src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs View File

@@ -1,27 +0,0 @@
using System;
using System.Threading.Tasks;

namespace Discord.Interactions
{
internal sealed class EnumTypeReader<T> : TypeReader<T>
where T : struct, Enum
{
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services)
{
if (Enum.TryParse<T>(option, out var result))
return Task.FromResult(TypeConverterResult.FromSuccess(result));
else
return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {option} cannot be converted to {nameof(T)}"));
}

public override Task<string> SerializeAsync(object obj)
{
var name = Enum.GetName(typeof(T), obj);

if (name is null)
throw new ArgumentException($"Enum name cannot be parsed from {obj}");

return Task.FromResult(name);
}
}
}

+ 28
- 0
src/Discord.Net.Interactions/TypeReaders/TypeReader.cs View File

@@ -3,15 +3,43 @@ using System.Threading.Tasks;

namespace Discord.Interactions
{
/// <summary>
/// Base class for creating TypeConverters. <see cref="InteractionService"/> uses TypeConverters to interface with Slash Command parameters.
/// </summary>
public abstract class TypeReader : ITypeConverter<string>
{
/// <summary>
/// Will be used to search for alternative TypeReaders whenever the Command Service encounters an unknown parameter type.
/// </summary>
/// <param name="type">An object type.</param>
/// <returns>
/// The boolean result.
/// </returns>
public abstract bool CanConvertTo(Type type);

/// <summary>
/// Will be used to read the incoming payload before executing the method body.
/// </summary>
/// <param name="context">Command exexution context.</param>
/// <param name="option">Recieved option payload.</param>
/// <param name="services">Service provider that will be used to initialize the command module.</param>
/// <returns>The result of the read process.</returns>
public abstract Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services);

/// <summary>
/// Will be used to serialize objects into strings.
/// </summary>
/// <param name="obj">Object to be serialized.</param>
/// <returns>
/// A task represting the conversion process. The result of the task contains the conversion result.
/// </returns>
public virtual Task<string> SerializeAsync(object obj) => Task.FromResult(obj.ToString());
}

/// <inheritdoc/>
public abstract class TypeReader<T> : TypeReader
{
/// <inheritdoc/>
public sealed override bool CanConvertTo(Type type) =>
typeof(T).IsAssignableFrom(type);
}


Loading…
Cancel
Save