From 1c5dd8037ebe6e7305c0a09d3e00c72432dbc968 Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Tue, 28 Dec 2021 16:12:09 +0100 Subject: [PATCH 1/9] Validate command and options names --- http/src/client/interaction.rs | 16 +- .../create_global_command/chat_input.rs | 6 +- .../command/create_global_command/message.rs | 11 +- .../command/create_global_command/mod.rs | 37 ++- .../command/create_global_command/user.rs | 11 +- .../create_guild_command/chat_input.rs | 4 +- .../command/create_guild_command/message.rs | 11 +- .../command/create_guild_command/mod.rs | 35 ++- .../command/create_guild_command/user.rs | 11 +- validate/src/command.rs | 219 +++++++++++++++--- 10 files changed, 277 insertions(+), 84 deletions(-) diff --git a/http/src/client/interaction.rs b/http/src/client/interaction.rs index cacc867123b..35dcb9d0b1e 100644 --- a/http/src/client/interaction.rs +++ b/http/src/client/interaction.rs @@ -167,16 +167,13 @@ impl<'a> InteractionClient<'a> { /// the same guild will overwrite the old command. See [the discord docs] /// for more information. /// - /// Returns an error of type [`NameInvalid`] error type if the command name - /// is not between 1 and 32 characters. - /// /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command - pub fn create_guild_command( + pub const fn create_guild_command( &'a self, guild_id: Id, name: &'a str, - ) -> Result, CommandValidationError> { + ) -> CreateGuildCommand<'a> { CreateGuildCommand::new(self.client, self.application_id, guild_id, name) } @@ -241,15 +238,8 @@ impl<'a> InteractionClient<'a> { /// command with the same name as an already-existing global command will /// overwrite the old command. See [the discord docs] for more information. /// - /// Returns an error of type [`NameInvalid`] if the command name is not - /// between 1 and 32 characters. - /// - /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - pub fn create_global_command( - &'a self, - name: &'a str, - ) -> Result, CommandValidationError> { + pub const fn create_global_command(&'a self, name: &'a str) -> CreateGlobalCommand<'a> { CreateGlobalCommand::new(self.client, self.application_id, name) } diff --git a/http/src/request/application/command/create_global_command/chat_input.rs b/http/src/request/application/command/create_global_command/chat_input.rs index 16f72a3d05b..9fa6b94a47e 100644 --- a/http/src/request/application/command/create_global_command/chat_input.rs +++ b/http/src/request/application/command/create_global_command/chat_input.rs @@ -11,7 +11,8 @@ use twilight_model::{ id::{marker::ApplicationMarker, Id}, }; use twilight_validate::command::{ - description as validate_description, options as validate_options, CommandValidationError, + chat_input_name as validate_name, description as validate_description, + options as validate_options, CommandValidationError, }; /// Create a new chat input global command. @@ -38,6 +39,7 @@ impl<'a> CreateGlobalChatInputCommand<'a> { name: &'a str, description: &'a str, ) -> Result { + validate_name(name).map_err(CommandValidationError::name_invalid)?; validate_description(&description)?; Ok(Self { @@ -54,7 +56,7 @@ impl<'a> CreateGlobalChatInputCommand<'a> { /// /// Required command options must be added before optional options. /// - /// Errors + /// # Errors /// /// Returns an error of type [`OptionsRequiredFirst`] if a required option /// was added after an optional option. The problem option's index is diff --git a/http/src/request/application/command/create_global_command/message.rs b/http/src/request/application/command/create_global_command/message.rs index 062f574e709..e62dbecce81 100644 --- a/http/src/request/application/command/create_global_command/message.rs +++ b/http/src/request/application/command/create_global_command/message.rs @@ -10,6 +10,7 @@ use twilight_model::{ application::command::{Command, CommandType}, id::{marker::ApplicationMarker, Id}, }; +use twilight_validate::command::{name as validate_name, CommandValidationError}; /// Create a new message global command. /// @@ -26,17 +27,19 @@ pub struct CreateGlobalMessageCommand<'a> { } impl<'a> CreateGlobalMessageCommand<'a> { - pub(crate) const fn new( + pub(crate) fn new( http: &'a Client, application_id: Id, name: &'a str, - ) -> Self { - Self { + ) -> Result { + validate_name(name).map_err(CommandValidationError::name_invalid)?; + + Ok(Self { application_id, default_permission: None, http, name, - } + }) } /// Whether the command is enabled by default when the app is added to a guild. diff --git a/http/src/request/application/command/create_global_command/mod.rs b/http/src/request/application/command/create_global_command/mod.rs index 12b5e110ebc..97dd8a56b7d 100644 --- a/http/src/request/application/command/create_global_command/mod.rs +++ b/http/src/request/application/command/create_global_command/mod.rs @@ -9,7 +9,7 @@ pub use self::{ use crate::Client; use twilight_model::id::{marker::ApplicationMarker, Id}; -use twilight_validate::command::{name as validate_name, CommandValidationError}; +use twilight_validate::command::CommandValidationError; /// Create a new global command. /// @@ -26,31 +26,36 @@ pub struct CreateGlobalCommand<'a> { } impl<'a> CreateGlobalCommand<'a> { - pub(crate) fn new( + pub(crate) const fn new( http: &'a Client, application_id: Id, name: &'a str, - ) -> Result { - validate_name(name)?; - - Ok(Self { + ) -> Self { + Self { application_id, http, name, - }) + } } /// Create a new chat input global command. /// + /// The command name must only contain alphanumeric characters and lowercase + /// variants must be used where possible. Special characters `-` and `_` are + /// allowed. + /// /// The description must be between 1 and 100 characters in length. Creating /// a command with the same name as an already-existing global command will /// overwrite the old command. See [the discord docs] for more information. /// /// # Errors /// - /// Returns an error of type [`DescriptionInvalid`] error type if the + /// Returns an error of type [`NameInvalid`] if the command name is invalid. + /// + /// Returns an error of type [`DescriptionInvalid`] if the /// command description is not between 1 and 100 characters. /// + /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid /// [`DescriptionInvalid`]: twilight_validate::command::CommandValidationErrorType::DescriptionInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command pub fn chat_input( @@ -66,8 +71,14 @@ impl<'a> CreateGlobalCommand<'a> { /// command will overwrite the old command. See [the discord docs] for more /// information. /// + /// # Errors + /// + /// Returns an error of type [`NameInvalid`] if the command name is + /// not between 1 and 32 characters. + /// + /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - pub const fn message(self) -> CreateGlobalMessageCommand<'a> { + pub fn message(self) -> Result, CommandValidationError> { CreateGlobalMessageCommand::new(self.http, self.application_id, self.name) } @@ -77,8 +88,14 @@ impl<'a> CreateGlobalCommand<'a> { /// command will overwrite the old command. See [the discord docs] for more /// information. /// + /// # Errors + /// + /// Returns an error of type [`NameInvalid`] if the command name is + /// not between 1 and 32 characters. + /// + /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - pub const fn user(self) -> CreateGlobalUserCommand<'a> { + pub fn user(self) -> Result, CommandValidationError> { CreateGlobalUserCommand::new(self.http, self.application_id, self.name) } } diff --git a/http/src/request/application/command/create_global_command/user.rs b/http/src/request/application/command/create_global_command/user.rs index c564baa86d4..16cd7adf744 100644 --- a/http/src/request/application/command/create_global_command/user.rs +++ b/http/src/request/application/command/create_global_command/user.rs @@ -10,6 +10,7 @@ use twilight_model::{ application::command::{Command, CommandType}, id::{marker::ApplicationMarker, Id}, }; +use twilight_validate::command::{name as validate_name, CommandValidationError}; /// Create a new user global command. /// @@ -26,17 +27,19 @@ pub struct CreateGlobalUserCommand<'a> { } impl<'a> CreateGlobalUserCommand<'a> { - pub(crate) const fn new( + pub(crate) fn new( http: &'a Client, application_id: Id, name: &'a str, - ) -> Self { - Self { + ) -> Result { + validate_name(name).map_err(CommandValidationError::name_invalid)?; + + Ok(Self { application_id, default_permission: None, http, name, - } + }) } /// Whether the command is enabled by default when the app is added to a guild. diff --git a/http/src/request/application/command/create_guild_command/chat_input.rs b/http/src/request/application/command/create_guild_command/chat_input.rs index 29c9a848349..055f5ac1ecb 100644 --- a/http/src/request/application/command/create_guild_command/chat_input.rs +++ b/http/src/request/application/command/create_guild_command/chat_input.rs @@ -14,7 +14,8 @@ use twilight_model::{ }, }; use twilight_validate::command::{ - description as validate_description, options as validate_options, CommandValidationError, + chat_input_name as validate_name, description as validate_description, + options as validate_options, CommandValidationError, }; /// Create a chat input command in a guild. @@ -44,6 +45,7 @@ impl<'a> CreateGuildChatInputCommand<'a> { name: &'a str, description: &'a str, ) -> Result { + validate_name(name).map_err(CommandValidationError::name_invalid)?; validate_description(&description)?; Ok(Self { diff --git a/http/src/request/application/command/create_guild_command/message.rs b/http/src/request/application/command/create_guild_command/message.rs index 528247db1fb..31157525506 100644 --- a/http/src/request/application/command/create_guild_command/message.rs +++ b/http/src/request/application/command/create_guild_command/message.rs @@ -13,6 +13,7 @@ use twilight_model::{ Id, }, }; +use twilight_validate::command::{name as validate_name, CommandValidationError}; /// Create a message command in a guild. /// @@ -31,19 +32,21 @@ pub struct CreateGuildMessageCommand<'a> { } impl<'a> CreateGuildMessageCommand<'a> { - pub(crate) const fn new( + pub(crate) fn new( http: &'a Client, application_id: Id, guild_id: Id, name: &'a str, - ) -> Self { - Self { + ) -> Result { + validate_name(name).map_err(CommandValidationError::name_invalid)?; + + Ok(Self { application_id, default_permission: None, guild_id, http, name, - } + }) } /// Whether the command is enabled by default when the app is added to a diff --git a/http/src/request/application/command/create_guild_command/mod.rs b/http/src/request/application/command/create_guild_command/mod.rs index 411d6fcabc7..1d590558345 100644 --- a/http/src/request/application/command/create_guild_command/mod.rs +++ b/http/src/request/application/command/create_guild_command/mod.rs @@ -12,7 +12,7 @@ use twilight_model::id::{ marker::{ApplicationMarker, GuildMarker}, Id, }; -use twilight_validate::command::{name as validate_name, CommandValidationError}; +use twilight_validate::command::CommandValidationError; /// Create a new command in a guild. /// @@ -31,24 +31,26 @@ pub struct CreateGuildCommand<'a> { } impl<'a> CreateGuildCommand<'a> { - pub(crate) fn new( + pub(crate) const fn new( http: &'a Client, application_id: Id, guild_id: Id, name: &'a str, - ) -> Result { - validate_name(name)?; - - Ok(Self { + ) -> Self { + Self { application_id, guild_id, http, name, - }) + } } /// Create a chat input command in a guild. /// + /// The command name must only contain alphanumeric characters and lowercase + /// variants must be used where possible. Special characters `-` and `_` are + /// allowed. + /// /// The description must be between 1 and 100 characters in length. Creating /// a guild command with the same name as an already-existing guild command /// in the same guild will overwrite the old command. See [the discord docs] @@ -56,9 +58,12 @@ impl<'a> CreateGuildCommand<'a> { /// /// # Errors /// + /// Returns an error of type [`NameInvalid`] if the command name is invalid. + /// /// Returns an error of type [`DescriptionInvalid`] error type if the /// command description is not between 1 and 100 characters. /// + /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid /// [`DescriptionInvalid`]: twilight_validate::command::CommandValidationErrorType::DescriptionInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command pub fn chat_input( @@ -80,8 +85,14 @@ impl<'a> CreateGuildCommand<'a> { /// command in the same guild will overwrite the old command. See [the /// discord docs] for more information. /// + /// # Errors + /// + /// Returns an error of type [`NameInvalid`] if the command name is + /// not between 1 and 32 characters. + /// + /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command - pub const fn message(self) -> CreateGuildMessageCommand<'a> { + pub fn message(self) -> Result, CommandValidationError> { CreateGuildMessageCommand::new(self.http, self.application_id, self.guild_id, self.name) } @@ -91,8 +102,14 @@ impl<'a> CreateGuildCommand<'a> { /// command in the same guild will overwrite the old command. See [the /// discord docs] for more information. /// + /// # Errors + /// + /// Returns an error of type [`NameInvalid`] if the command name is + /// not between 1 and 32 characters. + /// + /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command - pub const fn user(self) -> CreateGuildUserCommand<'a> { + pub fn user(self) -> Result, CommandValidationError> { CreateGuildUserCommand::new(self.http, self.application_id, self.guild_id, self.name) } } diff --git a/http/src/request/application/command/create_guild_command/user.rs b/http/src/request/application/command/create_guild_command/user.rs index 72f8869a215..1868ba22fa8 100644 --- a/http/src/request/application/command/create_guild_command/user.rs +++ b/http/src/request/application/command/create_guild_command/user.rs @@ -13,6 +13,7 @@ use twilight_model::{ Id, }, }; +use twilight_validate::command::{name as validate_name, CommandValidationError}; /// Create a user command in a guild. /// @@ -31,19 +32,21 @@ pub struct CreateGuildUserCommand<'a> { } impl<'a> CreateGuildUserCommand<'a> { - pub(crate) const fn new( + pub(crate) fn new( http: &'a Client, application_id: Id, guild_id: Id, name: &'a str, - ) -> Self { - Self { + ) -> Result { + validate_name(name).map_err(CommandValidationError::name_invalid)?; + + Ok(Self { application_id, default_permission: None, guild_id, http, name, - } + }) } /// Whether the command is enabled by default when the app is added to a guild. diff --git a/validate/src/command.rs b/validate/src/command.rs index 63181fa859c..d821defef97 100644 --- a/validate/src/command.rs +++ b/validate/src/command.rs @@ -4,7 +4,7 @@ use std::{ error::Error, fmt::{Display, Formatter, Result as FmtResult}, }; -use twilight_model::application::command::{Command, CommandOption}; +use twilight_model::application::command::{Command, CommandOption, CommandType}; /// Maximum number of choices an option can have. pub const CHOICES_LIMIT: usize = 25; @@ -93,11 +93,31 @@ impl CommandValidationError { kind: CommandValidationErrorType::OptionsRequiredFirst { index }, } } + + /// Create an error of type [`NameInvalid`] with a provided [`NameValidationError`]. + /// + /// [`NameInvalid`]: CommandValidationErrorType::NameInvalid + #[must_use = "creating an error has no effect if left unused"] + pub const fn name_invalid(error: NameValidationError) -> Self { + Self { + kind: CommandValidationErrorType::NameInvalid(error), + } + } + + /// Create an error of type [`OptionNameInvalid`] with a provided [`NameValidationError`]. + /// + /// [`OptionNameInvalid`]: CommandValidationErrorType::OptionNameInvalid + #[must_use = "creating an error has no effect if left unused"] + pub const fn option_name_invalid(error: NameValidationError) -> Self { + Self { + kind: CommandValidationErrorType::OptionNameInvalid(error), + } + } } impl Display for CommandValidationError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - match self.kind { + match &self.kind { CommandValidationErrorType::CountInvalid => { f.write_str("more than ")?; Display::fmt(&GUILD_COMMAND_LIMIT, f)?; @@ -112,14 +132,8 @@ impl Display for CommandValidationError { f.write_str(" characters") } - CommandValidationErrorType::NameInvalid => { - f.write_str("command name must be between ")?; - Display::fmt(&NAME_LENGTH_MIN, f)?; - f.write_str(" and ")?; - Display::fmt(&NAME_LENGTH_MAX, f)?; - - f.write_str(" characters") - } + CommandValidationErrorType::NameInvalid(error) + | CommandValidationErrorType::OptionNameInvalid(error) => Display::fmt(error, f), CommandValidationErrorType::OptionDescriptionInvalid => { f.write_str("command option description must be between ")?; Display::fmt(&OPTION_DESCRIPTION_LENGTH_MIN, f)?; @@ -128,14 +142,6 @@ impl Display for CommandValidationError { f.write_str(" characters") } - CommandValidationErrorType::OptionNameInvalid => { - f.write_str("command option name must be between ")?; - Display::fmt(&OPTION_NAME_LENGTH_MIN, f)?; - f.write_str(" and ")?; - Display::fmt(&OPTION_NAME_LENGTH_MAX, f)?; - - f.write_str(" characters") - } CommandValidationErrorType::OptionsCountInvalid => { f.write_str("more than ")?; Display::fmt(&OPTIONS_LIMIT, f)?; @@ -169,11 +175,11 @@ pub enum CommandValidationErrorType { /// Command description is invalid. DescriptionInvalid, /// Command name is invalid. - NameInvalid, + NameInvalid(NameValidationError), /// Command option description is invalid. OptionDescriptionInvalid, /// Command option name is invalid. - OptionNameInvalid, + OptionNameInvalid(NameValidationError), /// Command options count invalid. OptionsCountInvalid, /// Required command options have to be passed before optional ones. @@ -185,6 +191,80 @@ pub enum CommandValidationErrorType { PermissionsCountInvalid, } +/// Error created when a [`Command`] or [`CommandOption`] name is invalid. +#[derive(Debug)] +pub struct NameValidationError { + /// Type of error that occurred. + kind: NameValidationErrorType, +} + +impl NameValidationError { + /// Immutable reference to the type of error that occurred. + #[must_use = "retrieving the type has no effect if left unused"] + pub const fn kind(&self) -> &NameValidationErrorType { + &self.kind + } + + /// Consume the error, returning the source error if there is any. + #[allow(clippy::unused_self)] + #[must_use = "consuming the error and retrieving the source has no effect if left unused"] + pub fn into_source(self) -> Option> { + None + } + + /// Consume the error, returning the owned error type and the source error. + #[must_use = "consuming the error into its parts has no effect if left unused"] + pub fn into_parts( + self, + ) -> ( + NameValidationErrorType, + Option>, + ) { + (self.kind, None) + } +} + +impl Display for NameValidationError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self.kind { + NameValidationErrorType::LengthInvalid => { + f.write_str("name must be between ")?; + Display::fmt(&NAME_LENGTH_MIN, f)?; + f.write_str(" and ")?; + Display::fmt(&NAME_LENGTH_MAX, f)?; + + f.write_str(" characters") + } + NameValidationErrorType::CharacterNotAlphanumeric { .. } => { + f.write_str("name must only contain alphanumeric characters") + } + NameValidationErrorType::CharacterUppercase { .. } => { + f.write_str("name must not contain uppercase characters") + } + } + } +} + +impl Error for NameValidationError {} + +/// Type of [`NameValidationError`] that occurred. +#[derive(Debug)] +#[non_exhaustive] +pub enum NameValidationErrorType { + /// Name length is invalid. + LengthInvalid, + /// Name contains a non-alphanumeric character. + CharacterNotAlphanumeric { + /// The invalid character. + character: char, + }, + /// Name contains an uppercase character. + CharacterUppercase { + /// The invalid character. + character: char, + }, +} + /// Validate a [`Command`]. /// /// # Errors @@ -198,12 +278,22 @@ pub enum CommandValidationErrorType { /// [`NameInvalid`]: CommandValidationErrorType::NameInvalid pub fn command(value: &Command) -> Result<(), CommandValidationError> { let Command { - description, name, .. + description, + name, + kind, + .. } = value; self::description(description)?; - self::name(name)?; + match kind { + CommandType::ChatInput => { + self::chat_input_name(name).map_err(CommandValidationError::name_invalid)?; + } + CommandType::User | CommandType::Message => { + self::name(name).map_err(CommandValidationError::name_invalid)?; + } + } Ok(()) } @@ -232,29 +322,83 @@ pub fn description(value: impl AsRef) -> Result<(), CommandValidationError> } } -/// Validate the name of a [`Command`]. +/// Validate the name of a [`User`] or [`Message`] command. /// /// The length of the name must be more than [`NAME_LENGTH_MIN`] and less than /// or equal to [`NAME_LENGTH_MAX`]. /// +/// Use [`chat_input_name`] to validate name of a [`ChatInput`] command. +/// /// # Errors /// /// Returns an error of type [`NameInvalid`] if the name is invalid. /// +/// [`User`]: CommandType::User +/// [`Message`]: CommandType::Message +/// [`ChatInput`]: CommandType::ChatInput /// [`NameInvalid`]: CommandValidationErrorType::NameInvalid -pub fn name(value: impl AsRef) -> Result<(), CommandValidationError> { +pub fn name(value: impl AsRef) -> Result<(), NameValidationError> { let len = value.as_ref().chars().count(); // https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure if (NAME_LENGTH_MIN..=NAME_LENGTH_MAX).contains(&len) { Ok(()) } else { - Err(CommandValidationError { - kind: CommandValidationErrorType::NameInvalid, + Err(NameValidationError { + kind: NameValidationErrorType::LengthInvalid, }) } } +/// Validate the name of a [`ChatInput`] command or a [`CommandOption`] name. +/// +/// The length of the name must be more than [`NAME_LENGTH_MIN`] and less than +/// or equal to [`NAME_LENGTH_MAX`]. It can only contain alphanumeric characters +/// and lowercase variants must be used where possible. Special characters `-` +/// and `_` are allowed. +/// +/// # Errors +/// +/// Returns an error of type [`LengthInvalid`] if the length is invalid. +/// +/// Returns an error of type [`CharacterNotAlphanumeric`] if the name contains +/// a non-alphanumeric character. +/// +/// Returns an error of type [`CharacterUppercase`] if the name contains an +/// uppercase character for which a lowercase variant exists. +/// +/// [`ChatInput`]: CommandType::ChatInput +/// [`LengthInvalid`]: NameValidationErrorType::LengthInvalid +/// [`CharacterNotAlphanumeric`]: NameValidationErrorType::CharacterNotAlphanumeric +/// [`CharacterUppercase`]: NameValidationErrorType::CharacterUppercase +pub fn chat_input_name(value: impl AsRef) -> Result<(), NameValidationError> { + let len = value.as_ref().chars().count(); + + if !(NAME_LENGTH_MIN..=NAME_LENGTH_MAX).contains(&len) { + return Err(NameValidationError { + kind: NameValidationErrorType::LengthInvalid, + }); + } + + let chars = value.as_ref().chars(); + + for char in chars { + if !char.is_alphanumeric() && char != '_' && char != '-' { + return Err(NameValidationError { + kind: NameValidationErrorType::CharacterNotAlphanumeric { character: char }, + }); + } + + if char.to_lowercase().next() != Some(char) { + return Err(NameValidationError { + kind: NameValidationErrorType::CharacterUppercase { character: char }, + }); + } + } + + Ok(()) +} + /// Validate a single [`CommandOption`]. /// /// # Errors @@ -280,7 +424,7 @@ pub fn option(option: &CommandOption) -> Result<(), CommandValidationError> { | CommandOption::Mentionable(data) => (&data.description, &data.name), }; - let description_len = description.len(); + let description_len = description.chars().count(); if description_len > OPTION_DESCRIPTION_LENGTH_MAX && description_len < OPTION_DESCRIPTION_LENGTH_MIN { @@ -289,12 +433,7 @@ pub fn option(option: &CommandOption) -> Result<(), CommandValidationError> { }); } - let name_len = name.len(); - if name_len > OPTION_DESCRIPTION_LENGTH_MAX && name_len < OPTION_DESCRIPTION_LENGTH_MIN { - return Err(CommandValidationError { - kind: CommandValidationErrorType::OptionNameInvalid, - }); - } + self::chat_input_name(name).map_err(CommandValidationError::option_name_invalid)?; Ok(()) } @@ -396,6 +535,20 @@ mod tests { assert!(command(&invalid_command).is_err()); } + #[test] + fn test_chat_input_name() { + assert!(chat_input_name("hello-command").is_ok()); // Latin language + assert!(chat_input_name("Hello").is_err()); // Latin language with uppercase + assert!(chat_input_name("hello!").is_err()); // Latin language with non-alphanumeric + + assert!(chat_input_name("здрасти").is_ok()); // Russian + assert!(chat_input_name("Здрасти").is_err()); // Russian with uppercase + assert!(chat_input_name("здрасти!").is_err()); // Russian with non-alphanumeric + + assert!(chat_input_name("你好").is_ok()); // Chinese (no upper and lowercase variants) + assert!(chat_input_name("你好。").is_err()); // Chinese with non-alphanumeric + } + #[test] fn test_guild_permissions() { assert!(guild_permissions(0).is_ok()); From 51595c7d094df5c13b77e40df603ca0463cf4ce0 Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Tue, 28 Dec 2021 16:27:15 +0100 Subject: [PATCH 2/9] Replace non-ASCII characters with Unicode escapes --- validate/src/command.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/validate/src/command.rs b/validate/src/command.rs index d821defef97..0e031cfd89d 100644 --- a/validate/src/command.rs +++ b/validate/src/command.rs @@ -541,12 +541,12 @@ mod tests { assert!(chat_input_name("Hello").is_err()); // Latin language with uppercase assert!(chat_input_name("hello!").is_err()); // Latin language with non-alphanumeric - assert!(chat_input_name("здрасти").is_ok()); // Russian - assert!(chat_input_name("Здрасти").is_err()); // Russian with uppercase - assert!(chat_input_name("здрасти!").is_err()); // Russian with non-alphanumeric + assert!(chat_input_name("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_ok()); // Russian + assert!(chat_input_name("\u{417}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_err()); // Russian with uppercase + assert!(chat_input_name("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}!").is_err()); // Russian with non-alphanumeric - assert!(chat_input_name("你好").is_ok()); // Chinese (no upper and lowercase variants) - assert!(chat_input_name("你好。").is_err()); // Chinese with non-alphanumeric + assert!(chat_input_name("\u{4f60}\u{597d}").is_ok()); // Chinese (no upper and lowercase variants) + assert!(chat_input_name("\u{4f60}\u{597d}\u{3002}").is_err()); // Chinese with non-alphanumeric } #[test] From a9935b6f2d9807bc4e121dcd6149210af527681a Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Mon, 3 Jan 2022 21:01:57 +0100 Subject: [PATCH 3/9] refactor: remove NameValidationError --- .../create_global_command/chat_input.rs | 2 +- .../command/create_global_command/message.rs | 2 +- .../command/create_global_command/user.rs | 2 +- .../create_guild_command/chat_input.rs | 2 +- .../command/create_guild_command/message.rs | 2 +- .../command/create_guild_command/user.rs | 2 +- validate/src/command.rs | 172 ++++++------------ 7 files changed, 57 insertions(+), 127 deletions(-) diff --git a/http/src/request/application/command/create_global_command/chat_input.rs b/http/src/request/application/command/create_global_command/chat_input.rs index 9fa6b94a47e..70d0f6875d3 100644 --- a/http/src/request/application/command/create_global_command/chat_input.rs +++ b/http/src/request/application/command/create_global_command/chat_input.rs @@ -39,7 +39,7 @@ impl<'a> CreateGlobalChatInputCommand<'a> { name: &'a str, description: &'a str, ) -> Result { - validate_name(name).map_err(CommandValidationError::name_invalid)?; + validate_name(name)?; validate_description(&description)?; Ok(Self { diff --git a/http/src/request/application/command/create_global_command/message.rs b/http/src/request/application/command/create_global_command/message.rs index e62dbecce81..1fa0f16bfcd 100644 --- a/http/src/request/application/command/create_global_command/message.rs +++ b/http/src/request/application/command/create_global_command/message.rs @@ -32,7 +32,7 @@ impl<'a> CreateGlobalMessageCommand<'a> { application_id: Id, name: &'a str, ) -> Result { - validate_name(name).map_err(CommandValidationError::name_invalid)?; + validate_name(name)?; Ok(Self { application_id, diff --git a/http/src/request/application/command/create_global_command/user.rs b/http/src/request/application/command/create_global_command/user.rs index 16cd7adf744..e3525747f2d 100644 --- a/http/src/request/application/command/create_global_command/user.rs +++ b/http/src/request/application/command/create_global_command/user.rs @@ -32,7 +32,7 @@ impl<'a> CreateGlobalUserCommand<'a> { application_id: Id, name: &'a str, ) -> Result { - validate_name(name).map_err(CommandValidationError::name_invalid)?; + validate_name(name)?; Ok(Self { application_id, diff --git a/http/src/request/application/command/create_guild_command/chat_input.rs b/http/src/request/application/command/create_guild_command/chat_input.rs index 055f5ac1ecb..48ba6cba87c 100644 --- a/http/src/request/application/command/create_guild_command/chat_input.rs +++ b/http/src/request/application/command/create_guild_command/chat_input.rs @@ -45,7 +45,7 @@ impl<'a> CreateGuildChatInputCommand<'a> { name: &'a str, description: &'a str, ) -> Result { - validate_name(name).map_err(CommandValidationError::name_invalid)?; + validate_name(name)?; validate_description(&description)?; Ok(Self { diff --git a/http/src/request/application/command/create_guild_command/message.rs b/http/src/request/application/command/create_guild_command/message.rs index 31157525506..bc45d704691 100644 --- a/http/src/request/application/command/create_guild_command/message.rs +++ b/http/src/request/application/command/create_guild_command/message.rs @@ -38,7 +38,7 @@ impl<'a> CreateGuildMessageCommand<'a> { guild_id: Id, name: &'a str, ) -> Result { - validate_name(name).map_err(CommandValidationError::name_invalid)?; + validate_name(name)?; Ok(Self { application_id, diff --git a/http/src/request/application/command/create_guild_command/user.rs b/http/src/request/application/command/create_guild_command/user.rs index 1868ba22fa8..eb0867d4074 100644 --- a/http/src/request/application/command/create_guild_command/user.rs +++ b/http/src/request/application/command/create_guild_command/user.rs @@ -38,7 +38,7 @@ impl<'a> CreateGuildUserCommand<'a> { guild_id: Id, name: &'a str, ) -> Result { - validate_name(name).map_err(CommandValidationError::name_invalid)?; + validate_name(name)?; Ok(Self { application_id, diff --git a/validate/src/command.rs b/validate/src/command.rs index 0e031cfd89d..5a2ff559cb3 100644 --- a/validate/src/command.rs +++ b/validate/src/command.rs @@ -93,26 +93,6 @@ impl CommandValidationError { kind: CommandValidationErrorType::OptionsRequiredFirst { index }, } } - - /// Create an error of type [`NameInvalid`] with a provided [`NameValidationError`]. - /// - /// [`NameInvalid`]: CommandValidationErrorType::NameInvalid - #[must_use = "creating an error has no effect if left unused"] - pub const fn name_invalid(error: NameValidationError) -> Self { - Self { - kind: CommandValidationErrorType::NameInvalid(error), - } - } - - /// Create an error of type [`OptionNameInvalid`] with a provided [`NameValidationError`]. - /// - /// [`OptionNameInvalid`]: CommandValidationErrorType::OptionNameInvalid - #[must_use = "creating an error has no effect if left unused"] - pub const fn option_name_invalid(error: NameValidationError) -> Self { - Self { - kind: CommandValidationErrorType::OptionNameInvalid(error), - } - } } impl Display for CommandValidationError { @@ -132,8 +112,19 @@ impl Display for CommandValidationError { f.write_str(" characters") } - CommandValidationErrorType::NameInvalid(error) - | CommandValidationErrorType::OptionNameInvalid(error) => Display::fmt(error, f), + CommandValidationErrorType::NameLengthInvalid => { + f.write_str("command name must be between ")?; + Display::fmt(&NAME_LENGTH_MIN, f)?; + f.write_str(" and ")?; + Display::fmt(&NAME_LENGTH_MAX, f) + } + CommandValidationErrorType::NameCharacterInvalid { character } => { + f.write_str( + "command name must only contain lowercase alphanumeric characters, found `", + )?; + Display::fmt(character, f)?; + f.write_str("`") + } CommandValidationErrorType::OptionDescriptionInvalid => { f.write_str("command option description must be between ")?; Display::fmt(&OPTION_DESCRIPTION_LENGTH_MIN, f)?; @@ -142,6 +133,17 @@ impl Display for CommandValidationError { f.write_str(" characters") } + CommandValidationErrorType::OptionNameLengthInvalid => { + f.write_str("command option name must be between ")?; + Display::fmt(&NAME_LENGTH_MIN, f)?; + f.write_str(" and ")?; + Display::fmt(&NAME_LENGTH_MAX, f) + } + CommandValidationErrorType::OptionNameCharacterInvalid { character } => { + f.write_str("command option name must only contain lowercase alphanumeric characters, found `")?; + Display::fmt(character, f)?; + f.write_str("`") + } CommandValidationErrorType::OptionsCountInvalid => { f.write_str("more than ")?; Display::fmt(&OPTIONS_LIMIT, f)?; @@ -174,12 +176,22 @@ pub enum CommandValidationErrorType { CountInvalid, /// Command description is invalid. DescriptionInvalid, - /// Command name is invalid. - NameInvalid(NameValidationError), + /// Command name length is invalid. + NameLengthInvalid, + /// Command name contain an invalid character. + NameCharacterInvalid { + /// Invalid character. + character: char, + }, /// Command option description is invalid. OptionDescriptionInvalid, - /// Command option name is invalid. - OptionNameInvalid(NameValidationError), + /// Command option name length is invalid. + OptionNameLengthInvalid, + /// Command option name contain an invalid character. + OptionNameCharacterInvalid { + /// Invalid character. + character: char, + }, /// Command options count invalid. OptionsCountInvalid, /// Required command options have to be passed before optional ones. @@ -191,80 +203,6 @@ pub enum CommandValidationErrorType { PermissionsCountInvalid, } -/// Error created when a [`Command`] or [`CommandOption`] name is invalid. -#[derive(Debug)] -pub struct NameValidationError { - /// Type of error that occurred. - kind: NameValidationErrorType, -} - -impl NameValidationError { - /// Immutable reference to the type of error that occurred. - #[must_use = "retrieving the type has no effect if left unused"] - pub const fn kind(&self) -> &NameValidationErrorType { - &self.kind - } - - /// Consume the error, returning the source error if there is any. - #[allow(clippy::unused_self)] - #[must_use = "consuming the error and retrieving the source has no effect if left unused"] - pub fn into_source(self) -> Option> { - None - } - - /// Consume the error, returning the owned error type and the source error. - #[must_use = "consuming the error into its parts has no effect if left unused"] - pub fn into_parts( - self, - ) -> ( - NameValidationErrorType, - Option>, - ) { - (self.kind, None) - } -} - -impl Display for NameValidationError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - match self.kind { - NameValidationErrorType::LengthInvalid => { - f.write_str("name must be between ")?; - Display::fmt(&NAME_LENGTH_MIN, f)?; - f.write_str(" and ")?; - Display::fmt(&NAME_LENGTH_MAX, f)?; - - f.write_str(" characters") - } - NameValidationErrorType::CharacterNotAlphanumeric { .. } => { - f.write_str("name must only contain alphanumeric characters") - } - NameValidationErrorType::CharacterUppercase { .. } => { - f.write_str("name must not contain uppercase characters") - } - } - } -} - -impl Error for NameValidationError {} - -/// Type of [`NameValidationError`] that occurred. -#[derive(Debug)] -#[non_exhaustive] -pub enum NameValidationErrorType { - /// Name length is invalid. - LengthInvalid, - /// Name contains a non-alphanumeric character. - CharacterNotAlphanumeric { - /// The invalid character. - character: char, - }, - /// Name contains an uppercase character. - CharacterUppercase { - /// The invalid character. - character: char, - }, -} - /// Validate a [`Command`]. /// /// # Errors @@ -287,15 +225,9 @@ pub fn command(value: &Command) -> Result<(), CommandValidationError> { self::description(description)?; match kind { - CommandType::ChatInput => { - self::chat_input_name(name).map_err(CommandValidationError::name_invalid)?; - } - CommandType::User | CommandType::Message => { - self::name(name).map_err(CommandValidationError::name_invalid)?; - } + CommandType::ChatInput => self::chat_input_name(name), + CommandType::User | CommandType::Message => self::name(name), } - - Ok(()) } /// Validate the description of a [`Command`]. @@ -337,15 +269,15 @@ pub fn description(value: impl AsRef) -> Result<(), CommandValidationError> /// [`Message`]: CommandType::Message /// [`ChatInput`]: CommandType::ChatInput /// [`NameInvalid`]: CommandValidationErrorType::NameInvalid -pub fn name(value: impl AsRef) -> Result<(), NameValidationError> { +pub fn name(value: impl AsRef) -> Result<(), CommandValidationError> { let len = value.as_ref().chars().count(); // https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure if (NAME_LENGTH_MIN..=NAME_LENGTH_MAX).contains(&len) { Ok(()) } else { - Err(NameValidationError { - kind: NameValidationErrorType::LengthInvalid, + Err(CommandValidationError { + kind: CommandValidationErrorType::NameLengthInvalid, }) } } @@ -371,12 +303,12 @@ pub fn name(value: impl AsRef) -> Result<(), NameValidationError> { /// [`LengthInvalid`]: NameValidationErrorType::LengthInvalid /// [`CharacterNotAlphanumeric`]: NameValidationErrorType::CharacterNotAlphanumeric /// [`CharacterUppercase`]: NameValidationErrorType::CharacterUppercase -pub fn chat_input_name(value: impl AsRef) -> Result<(), NameValidationError> { +pub fn chat_input_name(value: impl AsRef) -> Result<(), CommandValidationError> { let len = value.as_ref().chars().count(); if !(NAME_LENGTH_MIN..=NAME_LENGTH_MAX).contains(&len) { - return Err(NameValidationError { - kind: NameValidationErrorType::LengthInvalid, + return Err(CommandValidationError { + kind: CommandValidationErrorType::NameLengthInvalid, }); } @@ -384,14 +316,14 @@ pub fn chat_input_name(value: impl AsRef) -> Result<(), NameValidationError for char in chars { if !char.is_alphanumeric() && char != '_' && char != '-' { - return Err(NameValidationError { - kind: NameValidationErrorType::CharacterNotAlphanumeric { character: char }, + return Err(CommandValidationError { + kind: CommandValidationErrorType::NameCharacterInvalid { character: char }, }); } if char.to_lowercase().next() != Some(char) { - return Err(NameValidationError { - kind: NameValidationErrorType::CharacterUppercase { character: char }, + return Err(CommandValidationError { + kind: CommandValidationErrorType::NameCharacterInvalid { character: char }, }); } } @@ -433,9 +365,7 @@ pub fn option(option: &CommandOption) -> Result<(), CommandValidationError> { }); } - self::chat_input_name(name).map_err(CommandValidationError::option_name_invalid)?; - - Ok(()) + self::chat_input_name(name) } /// Validate a list of command options for count, order, and internal validity. From 3e794f1ce2825c979902d972f7bdd0a520100dbc Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Mon, 3 Jan 2022 21:13:35 +0100 Subject: [PATCH 4/9] docs: fix documentation links --- .../command/create_global_command/mod.rs | 13 ++++---- .../command/create_guild_command/mod.rs | 13 ++++---- validate/src/command.rs | 31 ++++++++++--------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/http/src/request/application/command/create_global_command/mod.rs b/http/src/request/application/command/create_global_command/mod.rs index 97dd8a56b7d..40e3e025ae1 100644 --- a/http/src/request/application/command/create_global_command/mod.rs +++ b/http/src/request/application/command/create_global_command/mod.rs @@ -50,12 +50,13 @@ impl<'a> CreateGlobalCommand<'a> { /// /// # Errors /// - /// Returns an error of type [`NameInvalid`] if the command name is invalid. + /// Returns an error of type [`NameLengthInvalid`] or [`NameCharacterInvalid`] if the command name is invalid. /// /// Returns an error of type [`DescriptionInvalid`] if the /// command description is not between 1 and 100 characters. /// - /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid + /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid + /// [`NameCharacterInvalid`]: twilight_validate::command::CommandValidationErrorType::NameCharacterInvalid /// [`DescriptionInvalid`]: twilight_validate::command::CommandValidationErrorType::DescriptionInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command pub fn chat_input( @@ -73,10 +74,10 @@ impl<'a> CreateGlobalCommand<'a> { /// /// # Errors /// - /// Returns an error of type [`NameInvalid`] if the command name is + /// Returns an error of type [`NameLengthInvalid`] if the command name is /// not between 1 and 32 characters. /// - /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid + /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command pub fn message(self) -> Result, CommandValidationError> { CreateGlobalMessageCommand::new(self.http, self.application_id, self.name) @@ -90,10 +91,10 @@ impl<'a> CreateGlobalCommand<'a> { /// /// # Errors /// - /// Returns an error of type [`NameInvalid`] if the command name is + /// Returns an error of type [`NameLengthInvalid`] if the command name is /// not between 1 and 32 characters. /// - /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid + /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command pub fn user(self) -> Result, CommandValidationError> { CreateGlobalUserCommand::new(self.http, self.application_id, self.name) diff --git a/http/src/request/application/command/create_guild_command/mod.rs b/http/src/request/application/command/create_guild_command/mod.rs index 1d590558345..faa6c059e10 100644 --- a/http/src/request/application/command/create_guild_command/mod.rs +++ b/http/src/request/application/command/create_guild_command/mod.rs @@ -58,12 +58,13 @@ impl<'a> CreateGuildCommand<'a> { /// /// # Errors /// - /// Returns an error of type [`NameInvalid`] if the command name is invalid. + /// Returns an error of type [`NameLengthInvalid`] or [`NameCharacterInvalid`] if the command name is invalid. /// /// Returns an error of type [`DescriptionInvalid`] error type if the /// command description is not between 1 and 100 characters. /// - /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid + /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid + /// [`NameCharacterInvalid`]: twilight_validate::command::CommandValidationErrorType::NameCharacterInvalid /// [`DescriptionInvalid`]: twilight_validate::command::CommandValidationErrorType::DescriptionInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command pub fn chat_input( @@ -87,10 +88,10 @@ impl<'a> CreateGuildCommand<'a> { /// /// # Errors /// - /// Returns an error of type [`NameInvalid`] if the command name is + /// Returns an error of type [`NameLengthInvalid`] if the command name is /// not between 1 and 32 characters. /// - /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid + /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command pub fn message(self) -> Result, CommandValidationError> { CreateGuildMessageCommand::new(self.http, self.application_id, self.guild_id, self.name) @@ -104,10 +105,10 @@ impl<'a> CreateGuildCommand<'a> { /// /// # Errors /// - /// Returns an error of type [`NameInvalid`] if the command name is + /// Returns an error of type [`NameLengthInvalid`] if the command name is /// not between 1 and 32 characters. /// - /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid + /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command pub fn user(self) -> Result, CommandValidationError> { CreateGuildUserCommand::new(self.http, self.application_id, self.guild_id, self.name) diff --git a/validate/src/command.rs b/validate/src/command.rs index 5a2ff559cb3..1eb7193043a 100644 --- a/validate/src/command.rs +++ b/validate/src/command.rs @@ -210,10 +210,12 @@ pub enum CommandValidationErrorType { /// Returns an error of type [`DescriptionInvalid`] if the description is /// invalid. /// -/// Returns an error of type [`NameInvalid`] if the name is invalid. +/// Returns an error of type [`NameLengthInvalid`] or [`NameCharacterInvalid`] +/// if the name is invalid. /// /// [`DescriptionInvalid`]: CommandValidationErrorType::DescriptionInvalid -/// [`NameInvalid`]: CommandValidationErrorType::NameInvalid +/// [`NameLengthInvalid`]: CommandValidationErrorType::NameLengthInvalid +/// [`NameCharacterInvalid`]: CommandValidationErrorType::NameCharacterInvalid pub fn command(value: &Command) -> Result<(), CommandValidationError> { let Command { description, @@ -263,12 +265,12 @@ pub fn description(value: impl AsRef) -> Result<(), CommandValidationError> /// /// # Errors /// -/// Returns an error of type [`NameInvalid`] if the name is invalid. +/// Returns an error of type [`NameLengthInvalid`] if the name is invalid. /// /// [`User`]: CommandType::User /// [`Message`]: CommandType::Message /// [`ChatInput`]: CommandType::ChatInput -/// [`NameInvalid`]: CommandValidationErrorType::NameInvalid +/// [`NameLengthInvalid`]: CommandValidationErrorType::NameLengthInvalid pub fn name(value: impl AsRef) -> Result<(), CommandValidationError> { let len = value.as_ref().chars().count(); @@ -291,18 +293,15 @@ pub fn name(value: impl AsRef) -> Result<(), CommandValidationError> { /// /// # Errors /// -/// Returns an error of type [`LengthInvalid`] if the length is invalid. +/// Returns an error of type [`NameLengthInvalid`] if the length is invalid. /// -/// Returns an error of type [`CharacterNotAlphanumeric`] if the name contains -/// a non-alphanumeric character. -/// -/// Returns an error of type [`CharacterUppercase`] if the name contains an -/// uppercase character for which a lowercase variant exists. +/// Returns an error of type [`NameCharacterInvalid`] if the name contains +/// a non-alphanumeric character or an uppercase character for which a +/// lowercase variant exists. /// /// [`ChatInput`]: CommandType::ChatInput -/// [`LengthInvalid`]: NameValidationErrorType::LengthInvalid -/// [`CharacterNotAlphanumeric`]: NameValidationErrorType::CharacterNotAlphanumeric -/// [`CharacterUppercase`]: NameValidationErrorType::CharacterUppercase +/// [`NameLengthInvalid`]: CommandValidationErrorType::NameLengthInvalid +/// [`NameCharacterInvalid`]: CommandValidationErrorType::NameCharacterInvalid pub fn chat_input_name(value: impl AsRef) -> Result<(), CommandValidationError> { let len = value.as_ref().chars().count(); @@ -338,10 +337,12 @@ pub fn chat_input_name(value: impl AsRef) -> Result<(), CommandValidationEr /// Returns an error of type [`OptionDescriptionInvalid`] if the description is /// invalid. /// -/// Returns an error of type [`OptionNameInvalid`] if the name is invalid. +/// Returns an error of type [`OptionNameLengthInvalid`] or [`OptionNameCharacterInvalid`] +/// if the name is invalid. /// /// [`OptionDescriptionInvalid`]: CommandValidationErrorType::OptionDescriptionInvalid -/// [`OptionNameInvalid`]: CommandValidationErrorType::OptionNameInvalid +/// [`OptionNameLengthInvalid`]: CommandValidationErrorType::OptionNameLengthInvalid +/// [`OptionNameCharacterInvalid`]: CommandValidationErrorType::OptionNameCharacterInvalid pub fn option(option: &CommandOption) -> Result<(), CommandValidationError> { let (description, name) = match option { CommandOption::SubCommand(_) | CommandOption::SubCommandGroup(_) => return Ok(()), From e7ecebc14f146ecef91a547539b53bac445ebd04 Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Fri, 7 Jan 2022 21:24:44 +0100 Subject: [PATCH 5/9] fix: adress changes --- http/src/client/interaction.rs | 21 ++-------- .../create_global_command/chat_input.rs | 3 +- .../command/create_global_command/mod.rs | 39 ++++++++----------- .../create_guild_command/chat_input.rs | 3 +- .../command/create_guild_command/mod.rs | 36 +++++++---------- validate/src/command.rs | 12 +++--- 6 files changed, 44 insertions(+), 70 deletions(-) diff --git a/http/src/client/interaction.rs b/http/src/client/interaction.rs index 35dcb9d0b1e..2064841b3ef 100644 --- a/http/src/client/interaction.rs +++ b/http/src/client/interaction.rs @@ -161,20 +161,11 @@ impl<'a> InteractionClient<'a> { } /// Create a new command in a guild. - /// - /// The name must be between 1 and 32 characters in length. Creating a - /// guild command with the same name as an already-existing guild command in - /// the same guild will overwrite the old command. See [the discord docs] - /// for more information. - /// - /// [`NameInvalid`]: twilight_validate::command::CommandValidationErrorType::NameInvalid - /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command pub const fn create_guild_command( &'a self, guild_id: Id, - name: &'a str, ) -> CreateGuildCommand<'a> { - CreateGuildCommand::new(self.client, self.application_id, guild_id, name) + CreateGuildCommand::new(self.client, self.application_id, guild_id) } /// Fetch a guild command for your application. @@ -233,14 +224,8 @@ impl<'a> InteractionClient<'a> { } /// Create a new global command. - /// - /// The name must be between 1 and 32 characters in length. Creating a - /// command with the same name as an already-existing global command will - /// overwrite the old command. See [the discord docs] for more information. - /// - /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - pub const fn create_global_command(&'a self, name: &'a str) -> CreateGlobalCommand<'a> { - CreateGlobalCommand::new(self.client, self.application_id, name) + pub const fn create_global_command(&'a self) -> CreateGlobalCommand<'a> { + CreateGlobalCommand::new(self.client, self.application_id) } /// Fetch a global command for your application. diff --git a/http/src/request/application/command/create_global_command/chat_input.rs b/http/src/request/application/command/create_global_command/chat_input.rs index 70d0f6875d3..d03de80a922 100644 --- a/http/src/request/application/command/create_global_command/chat_input.rs +++ b/http/src/request/application/command/create_global_command/chat_input.rs @@ -39,9 +39,10 @@ impl<'a> CreateGlobalChatInputCommand<'a> { name: &'a str, description: &'a str, ) -> Result { - validate_name(name)?; validate_description(&description)?; + validate_name(name)?; + Ok(Self { application_id, default_permission: None, diff --git a/http/src/request/application/command/create_global_command/mod.rs b/http/src/request/application/command/create_global_command/mod.rs index 40e3e025ae1..7fcbe08a179 100644 --- a/http/src/request/application/command/create_global_command/mod.rs +++ b/http/src/request/application/command/create_global_command/mod.rs @@ -12,29 +12,17 @@ use twilight_model::id::{marker::ApplicationMarker, Id}; use twilight_validate::command::CommandValidationError; /// Create a new global command. -/// -/// The name must be between 1 and 32 characters in length. Creating a command -/// with the same name as an already-existing global command will overwrite the -/// old command. See [the discord docs] for more information. -/// -/// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command #[must_use = "the command must have a type"] pub struct CreateGlobalCommand<'a> { application_id: Id, http: &'a Client, - name: &'a str, } impl<'a> CreateGlobalCommand<'a> { - pub(crate) const fn new( - http: &'a Client, - application_id: Id, - name: &'a str, - ) -> Self { + pub(crate) const fn new(http: &'a Client, application_id: Id) -> Self { Self { application_id, http, - name, } } @@ -42,11 +30,11 @@ impl<'a> CreateGlobalCommand<'a> { /// /// The command name must only contain alphanumeric characters and lowercase /// variants must be used where possible. Special characters `-` and `_` are - /// allowed. + /// allowed. The description must be between 1 and 100 characters in length. /// - /// The description must be between 1 and 100 characters in length. Creating - /// a command with the same name as an already-existing global command will - /// overwrite the old command. See [the discord docs] for more information. + /// Creating a command with the same name as an already-existing global + /// command will overwrite the old command. See [the discord docs] for more + /// information. /// /// # Errors /// @@ -61,9 +49,10 @@ impl<'a> CreateGlobalCommand<'a> { /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command pub fn chat_input( self, + name: &'a str, description: &'a str, ) -> Result, CommandValidationError> { - CreateGlobalChatInputCommand::new(self.http, self.application_id, self.name, description) + CreateGlobalChatInputCommand::new(self.http, self.application_id, name, description) } /// Create a new message global command. @@ -79,8 +68,11 @@ impl<'a> CreateGlobalCommand<'a> { /// /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - pub fn message(self) -> Result, CommandValidationError> { - CreateGlobalMessageCommand::new(self.http, self.application_id, self.name) + pub fn message( + self, + name: &'a str, + ) -> Result, CommandValidationError> { + CreateGlobalMessageCommand::new(self.http, self.application_id, name) } /// Create a new user global command. @@ -96,7 +88,10 @@ impl<'a> CreateGlobalCommand<'a> { /// /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - pub fn user(self) -> Result, CommandValidationError> { - CreateGlobalUserCommand::new(self.http, self.application_id, self.name) + pub fn user( + self, + name: &'a str, + ) -> Result, CommandValidationError> { + CreateGlobalUserCommand::new(self.http, self.application_id, name) } } diff --git a/http/src/request/application/command/create_guild_command/chat_input.rs b/http/src/request/application/command/create_guild_command/chat_input.rs index 48ba6cba87c..b24b41055e3 100644 --- a/http/src/request/application/command/create_guild_command/chat_input.rs +++ b/http/src/request/application/command/create_guild_command/chat_input.rs @@ -45,9 +45,10 @@ impl<'a> CreateGuildChatInputCommand<'a> { name: &'a str, description: &'a str, ) -> Result { - validate_name(name)?; validate_description(&description)?; + validate_name(name)?; + Ok(Self { application_id, default_permission: None, diff --git a/http/src/request/application/command/create_guild_command/mod.rs b/http/src/request/application/command/create_guild_command/mod.rs index faa6c059e10..48c2f7db43e 100644 --- a/http/src/request/application/command/create_guild_command/mod.rs +++ b/http/src/request/application/command/create_guild_command/mod.rs @@ -15,19 +15,11 @@ use twilight_model::id::{ use twilight_validate::command::CommandValidationError; /// Create a new command in a guild. -/// -/// The name must be between 1 and 32 characters in length. Creating a guild -/// command with the same name as an already-existing guild command in the same -/// guild will overwrite the old command. See [the discord docs] for more -/// information. -/// -/// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command #[must_use = "the command must have a type"] pub struct CreateGuildCommand<'a> { application_id: Id, guild_id: Id, http: &'a Client, - name: &'a str, } impl<'a> CreateGuildCommand<'a> { @@ -35,13 +27,11 @@ impl<'a> CreateGuildCommand<'a> { http: &'a Client, application_id: Id, guild_id: Id, - name: &'a str, ) -> Self { Self { application_id, guild_id, http, - name, } } @@ -49,16 +39,16 @@ impl<'a> CreateGuildCommand<'a> { /// /// The command name must only contain alphanumeric characters and lowercase /// variants must be used where possible. Special characters `-` and `_` are - /// allowed. + /// allowed. The description must be between 1 and 100 characters in length. /// - /// The description must be between 1 and 100 characters in length. Creating - /// a guild command with the same name as an already-existing guild command - /// in the same guild will overwrite the old command. See [the discord docs] - /// for more information. + /// Creating a guild command with the same name as an already-existing guild + /// command in the same guild will overwrite the old command. See [the + /// discord docs] for more information. /// /// # Errors /// - /// Returns an error of type [`NameLengthInvalid`] or [`NameCharacterInvalid`] if the command name is invalid. + /// Returns an error of type [`NameLengthInvalid`] or [`NameCharacterInvalid`] + /// if the command name is invalid. /// /// Returns an error of type [`DescriptionInvalid`] error type if the /// command description is not between 1 and 100 characters. @@ -69,13 +59,14 @@ impl<'a> CreateGuildCommand<'a> { /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command pub fn chat_input( self, + name: &'a str, description: &'a str, ) -> Result, CommandValidationError> { CreateGuildChatInputCommand::new( self.http, self.application_id, self.guild_id, - self.name, + name, description, ) } @@ -93,8 +84,11 @@ impl<'a> CreateGuildCommand<'a> { /// /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command - pub fn message(self) -> Result, CommandValidationError> { - CreateGuildMessageCommand::new(self.http, self.application_id, self.guild_id, self.name) + pub fn message( + self, + name: &'a str, + ) -> Result, CommandValidationError> { + CreateGuildMessageCommand::new(self.http, self.application_id, self.guild_id, name) } /// Create a user command in a guild. @@ -110,7 +104,7 @@ impl<'a> CreateGuildCommand<'a> { /// /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid /// [the discord docs]: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command - pub fn user(self) -> Result, CommandValidationError> { - CreateGuildUserCommand::new(self.http, self.application_id, self.guild_id, self.name) + pub fn user(self, name: &'a str) -> Result, CommandValidationError> { + CreateGuildUserCommand::new(self.http, self.application_id, self.guild_id, name) } } diff --git a/validate/src/command.rs b/validate/src/command.rs index 1eb7193043a..834b9e3f433 100644 --- a/validate/src/command.rs +++ b/validate/src/command.rs @@ -116,6 +116,7 @@ impl Display for CommandValidationError { f.write_str("command name must be between ")?; Display::fmt(&NAME_LENGTH_MIN, f)?; f.write_str(" and ")?; + Display::fmt(&NAME_LENGTH_MAX, f) } CommandValidationErrorType::NameCharacterInvalid { character } => { @@ -123,6 +124,7 @@ impl Display for CommandValidationError { "command name must only contain lowercase alphanumeric characters, found `", )?; Display::fmt(character, f)?; + f.write_str("`") } CommandValidationErrorType::OptionDescriptionInvalid => { @@ -137,11 +139,13 @@ impl Display for CommandValidationError { f.write_str("command option name must be between ")?; Display::fmt(&NAME_LENGTH_MIN, f)?; f.write_str(" and ")?; + Display::fmt(&NAME_LENGTH_MAX, f) } CommandValidationErrorType::OptionNameCharacterInvalid { character } => { f.write_str("command option name must only contain lowercase alphanumeric characters, found `")?; Display::fmt(character, f)?; + f.write_str("`") } CommandValidationErrorType::OptionsCountInvalid => { @@ -303,13 +307,7 @@ pub fn name(value: impl AsRef) -> Result<(), CommandValidationError> { /// [`NameLengthInvalid`]: CommandValidationErrorType::NameLengthInvalid /// [`NameCharacterInvalid`]: CommandValidationErrorType::NameCharacterInvalid pub fn chat_input_name(value: impl AsRef) -> Result<(), CommandValidationError> { - let len = value.as_ref().chars().count(); - - if !(NAME_LENGTH_MIN..=NAME_LENGTH_MAX).contains(&len) { - return Err(CommandValidationError { - kind: CommandValidationErrorType::NameLengthInvalid, - }); - } + self::name(&value)?; let chars = value.as_ref().chars(); From be085a5b28a58b12302aa9c4aa439394ce9ac479 Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Fri, 7 Jan 2022 21:40:06 +0100 Subject: [PATCH 6/9] refactor: split name validation methods --- .../command/create_global_command/mod.rs | 3 +- validate/src/command.rs | 81 +++++++++++++++---- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/http/src/request/application/command/create_global_command/mod.rs b/http/src/request/application/command/create_global_command/mod.rs index 7fcbe08a179..d55ca7df121 100644 --- a/http/src/request/application/command/create_global_command/mod.rs +++ b/http/src/request/application/command/create_global_command/mod.rs @@ -38,7 +38,8 @@ impl<'a> CreateGlobalCommand<'a> { /// /// # Errors /// - /// Returns an error of type [`NameLengthInvalid`] or [`NameCharacterInvalid`] if the command name is invalid. + /// Returns an error of type [`NameLengthInvalid`] or [`NameCharacterInvalid`] + /// if the command name is invalid. /// /// Returns an error of type [`DescriptionInvalid`] if the /// command description is not between 1 and 100 characters. diff --git a/validate/src/command.rs b/validate/src/command.rs index 834b9e3f433..b093f29f071 100644 --- a/validate/src/command.rs +++ b/validate/src/command.rs @@ -137,10 +137,10 @@ impl Display for CommandValidationError { } CommandValidationErrorType::OptionNameLengthInvalid => { f.write_str("command option name must be between ")?; - Display::fmt(&NAME_LENGTH_MIN, f)?; + Display::fmt(&OPTION_NAME_LENGTH_MIN, f)?; f.write_str(" and ")?; - Display::fmt(&NAME_LENGTH_MAX, f) + Display::fmt(&OPTION_NAME_LENGTH_MAX, f) } CommandValidationErrorType::OptionNameCharacterInvalid { character } => { f.write_str("command option name must only contain lowercase alphanumeric characters, found `")?; @@ -288,7 +288,7 @@ pub fn name(value: impl AsRef) -> Result<(), CommandValidationError> { } } -/// Validate the name of a [`ChatInput`] command or a [`CommandOption`] name. +/// Validate the name of a [`ChatInput`] command. /// /// The length of the name must be more than [`NAME_LENGTH_MIN`] and less than /// or equal to [`NAME_LENGTH_MAX`]. It can only contain alphanumeric characters @@ -299,9 +299,9 @@ pub fn name(value: impl AsRef) -> Result<(), CommandValidationError> { /// /// Returns an error of type [`NameLengthInvalid`] if the length is invalid. /// -/// Returns an error of type [`NameCharacterInvalid`] if the name contains -/// a non-alphanumeric character or an uppercase character for which a -/// lowercase variant exists. +/// Returns an error of type [`NameCharacterInvalid`] if the name contains a +/// non-alphanumeric character or an uppercase character for which a lowercase +/// variant exists. /// /// [`ChatInput`]: CommandType::ChatInput /// [`NameLengthInvalid`]: CommandValidationErrorType::NameLengthInvalid @@ -309,6 +309,57 @@ pub fn name(value: impl AsRef) -> Result<(), CommandValidationError> { pub fn chat_input_name(value: impl AsRef) -> Result<(), CommandValidationError> { self::name(&value)?; + self::name_characters(value)?; + + Ok(()) +} + +/// Validate the name of a [`CommandOption`]. +/// +/// The length of the name must be more than [`NAME_LENGTH_MIN`] and less than +/// or equal to [`NAME_LENGTH_MAX`]. It can only contain alphanumeric characters +/// and lowercase variants must be used where possible. Special characters `-` +/// and `_` are allowed. +/// +/// # Errors +/// +/// Returns an error of type [`NameLengthInvalid`] if the length is invalid. +/// +/// Returns an error of type [`NameCharacterInvalid`] if the name contains a +/// non-alphanumeric character or an uppercase character for which a lowercase +/// variant exists. +/// +/// [`NameLengthInvalid`]: CommandValidationErrorType::NameLengthInvalid +/// [`NameCharacterInvalid`]: CommandValidationErrorType::NameCharacterInvalid +pub fn option_name(value: impl AsRef) -> Result<(), CommandValidationError> { + let len = value.as_ref().chars().count(); + + if !(OPTION_NAME_LENGTH_MIN..=OPTION_NAME_LENGTH_MAX).contains(&len) { + return Err(CommandValidationError { + kind: CommandValidationErrorType::NameLengthInvalid, + }); + } + + self::name_characters(value)?; + + Ok(()) +} + +/// Validate the characters of a [`ChatInput`] command name or a +/// [`CommandOption`] name. +/// +/// The name can only contain alphanumeric characters and lowercase variants +/// must be used where possible. Special characters `-` and `_` are allowed. +/// +/// # Errors +/// +/// Returns an error of type [`NameCharacterInvalid`] if the name contains a +/// non-alphanumeric character or an uppercase character for which a lowercase +/// variant exists. +/// +/// [`ChatInput`]: CommandType::ChatInput +/// [`NameCharacterInvalid`]: CommandValidationErrorType::NameCharacterInvalid +fn name_characters(value: impl AsRef) -> Result<(), CommandValidationError> { let chars = value.as_ref().chars(); for char in chars { @@ -364,7 +415,7 @@ pub fn option(option: &CommandOption) -> Result<(), CommandValidationError> { }); } - self::chat_input_name(name) + self::option_name(name) } /// Validate a list of command options for count, order, and internal validity. @@ -465,17 +516,17 @@ mod tests { } #[test] - fn test_chat_input_name() { - assert!(chat_input_name("hello-command").is_ok()); // Latin language - assert!(chat_input_name("Hello").is_err()); // Latin language with uppercase - assert!(chat_input_name("hello!").is_err()); // Latin language with non-alphanumeric + fn test_name_characters() { + assert!(name_characters("hello-command").is_ok()); // Latin language + assert!(name_characters("Hello").is_err()); // Latin language with uppercase + assert!(name_characters("hello!").is_err()); // Latin language with non-alphanumeric assert!(chat_input_name("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_ok()); // Russian - assert!(chat_input_name("\u{417}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_err()); // Russian with uppercase - assert!(chat_input_name("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}!").is_err()); // Russian with non-alphanumeric + assert!(name_characters("\u{417}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_err()); // Russian with uppercase + assert!(name_characters("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}!").is_err()); // Russian with non-alphanumeric - assert!(chat_input_name("\u{4f60}\u{597d}").is_ok()); // Chinese (no upper and lowercase variants) - assert!(chat_input_name("\u{4f60}\u{597d}\u{3002}").is_err()); // Chinese with non-alphanumeric + assert!(name_characters("\u{4f60}\u{597d}").is_ok()); // Chinese (no upper and lowercase variants) + assert!(name_characters("\u{4f60}\u{597d}\u{3002}").is_err()); // Chinese with non-alphanumeric } #[test] From 639301f833d2a1a44f8ce79981428126400cb9d2 Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Fri, 7 Jan 2022 21:42:38 +0100 Subject: [PATCH 7/9] fix: update test method --- validate/src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validate/src/command.rs b/validate/src/command.rs index b093f29f071..a7ba7c364eb 100644 --- a/validate/src/command.rs +++ b/validate/src/command.rs @@ -521,7 +521,7 @@ mod tests { assert!(name_characters("Hello").is_err()); // Latin language with uppercase assert!(name_characters("hello!").is_err()); // Latin language with non-alphanumeric - assert!(chat_input_name("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_ok()); // Russian + assert!(name_characters("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_ok()); // Russian assert!(name_characters("\u{417}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_err()); // Russian with uppercase assert!(name_characters("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}!").is_err()); // Russian with non-alphanumeric From e3b06760f1a16695e7d5722c1c9c89403f37a4e8 Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Fri, 7 Jan 2022 21:59:25 +0100 Subject: [PATCH 8/9] style: rename import to validate_chat_input_name --- .../application/command/create_global_command/chat_input.rs | 4 ++-- .../application/command/create_guild_command/chat_input.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/http/src/request/application/command/create_global_command/chat_input.rs b/http/src/request/application/command/create_global_command/chat_input.rs index d03de80a922..a58d2ad7340 100644 --- a/http/src/request/application/command/create_global_command/chat_input.rs +++ b/http/src/request/application/command/create_global_command/chat_input.rs @@ -11,7 +11,7 @@ use twilight_model::{ id::{marker::ApplicationMarker, Id}, }; use twilight_validate::command::{ - chat_input_name as validate_name, description as validate_description, + chat_input_name as validate_chat_input_name, description as validate_description, options as validate_options, CommandValidationError, }; @@ -41,7 +41,7 @@ impl<'a> CreateGlobalChatInputCommand<'a> { ) -> Result { validate_description(&description)?; - validate_name(name)?; + validate_chat_input_name(name)?; Ok(Self { application_id, diff --git a/http/src/request/application/command/create_guild_command/chat_input.rs b/http/src/request/application/command/create_guild_command/chat_input.rs index b24b41055e3..373bcc174fa 100644 --- a/http/src/request/application/command/create_guild_command/chat_input.rs +++ b/http/src/request/application/command/create_guild_command/chat_input.rs @@ -14,7 +14,7 @@ use twilight_model::{ }, }; use twilight_validate::command::{ - chat_input_name as validate_name, description as validate_description, + chat_input_name as validate_chat_input_name, description as validate_description, options as validate_options, CommandValidationError, }; @@ -47,7 +47,7 @@ impl<'a> CreateGuildChatInputCommand<'a> { ) -> Result { validate_description(&description)?; - validate_name(name)?; + validate_chat_input_name(name)?; Ok(Self { application_id, From 557694e1d59047022707f788cad47b4008c7cb16 Mon Sep 17 00:00:00 2001 From: baptiste0928 <22115890+baptiste0928@users.noreply.github.com> Date: Fri, 7 Jan 2022 22:03:20 +0100 Subject: [PATCH 9/9] fix: allow non-ascii literals --- validate/src/command.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/validate/src/command.rs b/validate/src/command.rs index a7ba7c364eb..135e36d753c 100644 --- a/validate/src/command.rs +++ b/validate/src/command.rs @@ -486,6 +486,8 @@ pub const fn guild_permissions(count: usize) -> Result<(), CommandValidationErro #[cfg(test)] mod tests { + #![allow(clippy::non_ascii_literal)] + use super::*; use twilight_model::{application::command::CommandType, id::Id}; @@ -521,12 +523,12 @@ mod tests { assert!(name_characters("Hello").is_err()); // Latin language with uppercase assert!(name_characters("hello!").is_err()); // Latin language with non-alphanumeric - assert!(name_characters("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_ok()); // Russian - assert!(name_characters("\u{417}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}").is_err()); // Russian with uppercase - assert!(name_characters("\u{437}\u{434}\u{440}\u{430}\u{441}\u{442}\u{438}!").is_err()); // Russian with non-alphanumeric + assert!(name_characters("здрасти").is_ok()); // Russian + assert!(name_characters("Здрасти").is_err()); // Russian with uppercase + assert!(name_characters("здрасти!").is_err()); // Russian with non-alphanumeric - assert!(name_characters("\u{4f60}\u{597d}").is_ok()); // Chinese (no upper and lowercase variants) - assert!(name_characters("\u{4f60}\u{597d}\u{3002}").is_err()); // Chinese with non-alphanumeric + assert!(name_characters("你好").is_ok()); // Chinese (no upper and lowercase variants) + assert!(name_characters("你好。").is_err()); // Chinese with non-alphanumeric } #[test]