diff --git a/twilight-http/src/request/application/interaction/create_followup.rs b/twilight-http/src/request/application/interaction/create_followup.rs index 71bb511cd2..bacf9b0a92 100644 --- a/twilight-http/src/request/application/interaction/create_followup.rs +++ b/twilight-http/src/request/application/interaction/create_followup.rs @@ -151,23 +151,18 @@ impl<'a> CreateFollowup<'a> { /// /// Calling this method multiple times will clear previous calls. /// + /// Components are validated when the request is built so flags set after + /// this method are taken into account. + /// /// # Errors /// /// Refer to the errors section of /// [`twilight_validate::component::component`] for a list of errors that /// may be returned as a result of validating each provided component. - pub fn components(mut self, components: &'a [Component]) -> Self { - self.fields = self.fields.and_then(|mut fields| { - validate_components( - components, - fields - .flags - .is_some_and(|flags| flags.contains(MessageFlags::IS_COMPONENTS_V2)), - )?; + pub const fn components(mut self, components: &'a [Component]) -> Self { + if let Ok(fields) = self.fields.as_mut() { fields.components = Some(components); - - Ok(fields) - }); + } self } @@ -296,6 +291,16 @@ impl IntoFuture for CreateFollowup<'_> { impl TryIntoRequest for CreateFollowup<'_> { fn try_into_request(self) -> Result { let mut fields = self.fields.map_err(Error::validation)?; + + if let Some(components) = fields.components { + validate_components( + components, + fields + .flags + .is_some_and(|flags| flags.contains(MessageFlags::IS_COMPONENTS_V2)), + ) + .map_err(Error::validation)?; + } let mut request = Request::builder(&Route::ExecuteWebhook { thread_id: None, token: self.token, @@ -349,6 +354,29 @@ mod tests { use std::error::Error; use twilight_model::id::Id; + #[test] + fn components_v2_before_flags() { + use twilight_model::channel::message::{Component, MessageFlags, component::TextDisplay}; + + let application_id = Id::new(1); + let client = Client::new(String::new()); + let components = [Component::TextDisplay(TextDisplay { + content: "test".to_owned(), + id: None, + })]; + let token = "foo".to_owned(); + + assert!( + client + .interaction(application_id) + .create_followup(&token) + .components(&components) + .flags(MessageFlags::IS_COMPONENTS_V2) + .try_into_request() + .is_ok() + ); + } + #[test] fn create_followup_message() -> Result<(), Box> { let application_id = Id::new(1); diff --git a/twilight-http/src/request/channel/message/create_message.rs b/twilight-http/src/request/channel/message/create_message.rs index e38f090a20..1fd658da1f 100644 --- a/twilight-http/src/request/channel/message/create_message.rs +++ b/twilight-http/src/request/channel/message/create_message.rs @@ -158,23 +158,18 @@ impl<'a> CreateMessage<'a> { /// /// Calling this method will clear previous calls. /// + /// Components are validated when the request is built so flags set after + /// this method are taken into account. + /// /// # Errors /// /// Refer to the errors section of /// [`twilight_validate::component::component`] for a list of errors that /// may be returned as a result of validating each provided component. - pub fn components(mut self, components: &'a [Component]) -> Self { - self.fields = self.fields.and_then(|mut fields| { - validate_components( - components, - fields - .flags - .is_some_and(|flags| flags.contains(MessageFlags::IS_COMPONENTS_V2)), - )?; + pub const fn components(mut self, components: &'a [Component]) -> Self { + if let Ok(fields) = self.fields.as_mut() { fields.components = Some(components); - - Ok(fields) - }); + } self } @@ -263,11 +258,12 @@ impl<'a> CreateMessage<'a> { /// Set the message's flags. /// - /// The only supported flags are [`SUPPRESS_EMBEDS`] and - /// [`SUPPRESS_NOTIFICATIONS`]. + /// The only supported flags are [`SUPPRESS_EMBEDS`], [`SUPPRESS_NOTIFICATIONS`], and + /// [`IS_COMPONENTS_V2`]. /// /// [`SUPPRESS_EMBEDS`]: MessageFlags::SUPPRESS_EMBEDS /// [`SUPPRESS_NOTIFICATIONS`]: MessageFlags::SUPPRESS_NOTIFICATIONS + /// [`IS_COMPONENTS_V2`]: MessageFlags::IS_COMPONENTS_V2 pub const fn flags(mut self, flags: MessageFlags) -> Self { if let Ok(fields) = self.fields.as_mut() { fields.flags = Some(flags); @@ -407,6 +403,16 @@ impl IntoFuture for CreateMessage<'_> { impl TryIntoRequest for CreateMessage<'_> { fn try_into_request(self) -> Result { let mut fields = self.fields.map_err(Error::validation)?; + + if let Some(components) = fields.components { + validate_components( + components, + fields + .flags + .is_some_and(|flags| flags.contains(MessageFlags::IS_COMPONENTS_V2)), + ) + .map_err(Error::validation)?; + } let mut request = Request::builder(&Route::CreateMessage { channel_id: self.channel_id.get(), }); @@ -441,3 +447,34 @@ impl TryIntoRequest for CreateMessage<'_> { request.build() } } + +#[cfg(test)] +mod tests { + use super::*; + use twilight_model::channel::message::component::TextDisplay; + + #[test] + fn components_v2_before_flags() { + let client = Client::new(String::new()); + let components = [Component::TextDisplay(TextDisplay { + content: "test".to_owned(), + id: None, + })]; + + assert!( + client + .create_message(Id::new(1)) + .components(&components) + .flags(MessageFlags::IS_COMPONENTS_V2) + .try_into_request() + .is_ok() + ); + assert!( + client + .create_message(Id::new(1)) + .components(&components) + .try_into_request() + .is_err() + ); + } +} diff --git a/twilight-http/src/request/channel/thread/create_forum_thread/message.rs b/twilight-http/src/request/channel/thread/create_forum_thread/message.rs index 143231005e..3c89cf7fd5 100644 --- a/twilight-http/src/request/channel/thread/create_forum_thread/message.rs +++ b/twilight-http/src/request/channel/thread/create_forum_thread/message.rs @@ -13,8 +13,7 @@ use twilight_model::{ }; use twilight_validate::message::{ MessageValidationError, attachment_filename as validate_attachment_filename, - components as validate_components, content as validate_content, embeds as validate_embeds, - sticker_ids as validate_sticker_ids, + content as validate_content, embeds as validate_embeds, sticker_ids as validate_sticker_ids, }; /// Contents of the first message in the new forum thread. @@ -93,25 +92,18 @@ impl<'a> CreateForumThreadMessage<'a> { /// /// Requires a webhook owned by the application. /// + /// Components are validated when the request is built so flags set after + /// this method are taken into account. + /// /// # Errors /// /// Refer to the errors section of /// [`twilight_validate::component::component`] for a list of errors that /// may be returned as a result of validating each provided component. - pub fn components(mut self, components: &'a [Component]) -> Self { - self.0 = self.0.and_then(|mut inner| { - validate_components( - components, - inner - .fields - .message - .flags - .is_some_and(|f| f.contains(MessageFlags::IS_COMPONENTS_V2)), - )?; + pub const fn components(mut self, components: &'a [Component]) -> Self { + if let Ok(inner) = self.0.as_mut() { inner.fields.message.components = Some(components); - - Ok(inner) - }); + } self } @@ -170,11 +162,12 @@ impl<'a> CreateForumThreadMessage<'a> { /// Set the message's flags. /// - /// The only supported flags are [`SUPPRESS_EMBEDS`] and - /// [`SUPPRESS_NOTIFICATIONS`]. + /// The only supported flags are [`SUPPRESS_EMBEDS`], [`SUPPRESS_NOTIFICATIONS`], and + /// [`IS_COMPONENTS_V2`]. /// /// [`SUPPRESS_EMBEDS`]: MessageFlags::SUPPRESS_EMBEDS /// [`SUPPRESS_NOTIFICATIONS`]: MessageFlags::SUPPRESS_NOTIFICATIONS + /// [`IS_COMPONENTS_V2`]: MessageFlags::IS_COMPONENTS_V2 pub const fn flags(mut self, flags: MessageFlags) -> Self { if let Ok(inner) = self.0.as_mut() { inner.fields.message.flags = Some(flags); @@ -242,3 +235,29 @@ impl TryIntoRequest for CreateForumThreadMessage<'_> { .and_then(CreateForumThread::try_into_request) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{client::Client, request::TryIntoRequest}; + use twilight_model::channel::message::component::TextDisplay; + + #[test] + fn components_v2_before_flags() { + let client = Client::new(String::new()); + let components = [Component::TextDisplay(TextDisplay { + content: "test".to_owned(), + id: None, + })]; + + assert!( + client + .create_forum_thread(Id::new(1), "thread") + .message() + .components(&components) + .flags(MessageFlags::IS_COMPONENTS_V2) + .try_into_request() + .is_ok() + ); + } +} diff --git a/twilight-http/src/request/channel/thread/create_forum_thread/mod.rs b/twilight-http/src/request/channel/thread/create_forum_thread/mod.rs index cde5f236d0..c2f50ea234 100644 --- a/twilight-http/src/request/channel/thread/create_forum_thread/mod.rs +++ b/twilight-http/src/request/channel/thread/create_forum_thread/mod.rs @@ -12,12 +12,13 @@ use crate::{ }; use serde::{Deserialize, Serialize}; use twilight_model::{ - channel::{Channel, Message, thread::AutoArchiveDuration}, + channel::{Channel, Message, message::MessageFlags, thread::AutoArchiveDuration}, id::{ Id, marker::{ChannelMarker, TagMarker}, }, }; +use twilight_validate::message::components as validate_components; #[derive(Deserialize, Serialize)] pub struct ForumThread { @@ -118,6 +119,17 @@ impl<'a> CreateForumThread<'a> { } fn try_into_request(mut self) -> Result { + if let Some(components) = self.fields.message.components { + validate_components( + components, + self.fields + .message + .flags + .is_some_and(|flags| flags.contains(MessageFlags::IS_COMPONENTS_V2)), + ) + .map_err(Error::validation)?; + } + let mut request = Request::builder(&Route::CreateForumThread { channel_id: self.channel_id.get(), }); diff --git a/twilight-http/src/request/channel/webhook/execute_webhook.rs b/twilight-http/src/request/channel/webhook/execute_webhook.rs index a3ecd89431..2c3bd813eb 100644 --- a/twilight-http/src/request/channel/webhook/execute_webhook.rs +++ b/twilight-http/src/request/channel/webhook/execute_webhook.rs @@ -174,23 +174,18 @@ impl<'a> ExecuteWebhook<'a> { /// /// Requires a webhook owned by the application. /// + /// Components are validated when the request is built so flags set after + /// this method are taken into account. + /// /// # Errors /// /// Refer to the errors section of /// [`twilight_validate::component::component`] for a list of errors that /// may be returned as a result of validating each provided component. - pub fn components(mut self, components: &'a [Component]) -> Self { - self.fields = self.fields.and_then(|mut fields| { - validate_components( - components, - fields - .flags - .is_some_and(|flags| flags.contains(MessageFlags::IS_COMPONENTS_V2)), - )?; + pub const fn components(mut self, components: &'a [Component]) -> Self { + if let Ok(fields) = self.fields.as_mut() { fields.components = Some(components); - - Ok(fields) - }); + } self } @@ -407,6 +402,16 @@ impl IntoFuture for ExecuteWebhook<'_> { impl TryIntoRequest for ExecuteWebhook<'_> { fn try_into_request(self) -> Result { let mut fields = self.fields.map_err(Error::validation)?; + + if let Some(components) = fields.components { + validate_components( + components, + fields + .flags + .is_some_and(|flags| flags.contains(MessageFlags::IS_COMPONENTS_V2)), + ) + .map_err(Error::validation)?; + } let mut request = Request::builder(&Route::ExecuteWebhook { thread_id: self.thread_id.map(Id::get), token: self.token, @@ -453,3 +458,27 @@ impl TryIntoRequest for ExecuteWebhook<'_> { request.build() } } + +#[cfg(test)] +mod tests { + use super::*; + use twilight_model::channel::message::component::TextDisplay; + + #[test] + fn components_v2_before_flags() { + let client = Client::new(String::new()); + let components = [Component::TextDisplay(TextDisplay { + content: "test".to_owned(), + id: None, + })]; + + assert!( + client + .execute_webhook(Id::new(1), "token") + .components(&components) + .flags(MessageFlags::IS_COMPONENTS_V2) + .try_into_request() + .is_ok() + ); + } +}