From 25607001443a4c4fe8b999638b201aa7e722c56c Mon Sep 17 00:00:00 2001 From: ITOH Date: Sat, 17 Sep 2022 16:01:22 +0200 Subject: [PATCH 1/3] feat(http,model,util,validate)!: use `Cow<'a, [u8]>` for attachments Instead of taking `&'a [u8]` for attachments, which could cause lifetime issues when using functions like `fn create_attachment<'a>() -> Attachment<'a>`, `Cow<'a, u8>` can be used to support both creating attachments from owned and borrowed bytes. This is a followup idea to: https://github.com/twilight-rs/twilight/pull/1886 --- examples/model-webhook-slash.rs | 10 +++++----- twilight-http/src/client/interaction.rs | 2 +- .../application/interaction/create_followup.rs | 2 +- .../application/interaction/create_response.rs | 4 ++-- .../application/interaction/update_followup.rs | 2 +- .../application/interaction/update_response.rs | 2 +- twilight-http/src/request/attachment.rs | 4 ++-- .../src/request/channel/message/create_message.rs | 2 +- .../src/request/channel/message/update_message.rs | 2 +- .../src/request/channel/webhook/execute_webhook.rs | 2 +- .../channel/webhook/update_webhook_message.rs | 2 +- twilight-model/src/http/attachment.rs | 10 ++++++---- twilight-model/src/http/interaction.rs | 12 ++++++------ .../src/builder/interaction_response_data.rs | 12 ++++++------ twilight-validate/src/message.rs | 2 +- 15 files changed, 36 insertions(+), 34 deletions(-) diff --git a/examples/model-webhook-slash.rs b/examples/model-webhook-slash.rs index 5f0fa9b144c..207d5080249 100644 --- a/examples/model-webhook-slash.rs +++ b/examples/model-webhook-slash.rs @@ -25,12 +25,12 @@ static PUB_KEY: Lazy = Lazy::new(|| { /// /// Responses are made by giving a function that takes a Interaction and returns /// a InteractionResponse or a error. -async fn interaction_handler( +async fn interaction_handler<'a, F>( req: Request, f: impl Fn(Box) -> F, ) -> anyhow::Result> where - F: Future>, + F: Future>>, { // Check that the method used is a POST, all other methods are not allowed. if req.method() != Method::POST { @@ -133,7 +133,7 @@ where /// Interaction handler that matches on the name of the interaction that /// have been dispatched from Discord. -async fn handler(data: Box) -> anyhow::Result { +async fn handler<'a>(data: Box) -> anyhow::Result> { match data.name.as_ref() { "vroom" => vroom(data).await, "debug" => debug(data).await, @@ -142,7 +142,7 @@ async fn handler(data: Box) -> anyhow::Result } /// Example of a handler that returns the formatted version of the interaction. -async fn debug(data: Box) -> anyhow::Result { +async fn debug<'a>(data: Box) -> anyhow::Result> { Ok(InteractionResponse { kind: InteractionResponseType::ChannelMessageWithSource, data: Some(InteractionResponseData { @@ -153,7 +153,7 @@ async fn debug(data: Box) -> anyhow::Result { } /// Example of interaction that responds with a message saying "Vroom vroom". -async fn vroom(_: Box) -> anyhow::Result { +async fn vroom<'a>(_: Box) -> anyhow::Result> { Ok(InteractionResponse { kind: InteractionResponseType::ChannelMessageWithSource, data: Some(InteractionResponseData { diff --git a/twilight-http/src/client/interaction.rs b/twilight-http/src/client/interaction.rs index 09de692c265..1a8da3babae 100644 --- a/twilight-http/src/client/interaction.rs +++ b/twilight-http/src/client/interaction.rs @@ -81,7 +81,7 @@ impl<'a> InteractionClient<'a> { &'a self, interaction_id: Id, interaction_token: &'a str, - response: &'a InteractionResponse, + response: &'a InteractionResponse<'_>, ) -> CreateResponse<'a> { CreateResponse::new(self.client, interaction_id, interaction_token, response) } diff --git a/twilight-http/src/request/application/interaction/create_followup.rs b/twilight-http/src/request/application/interaction/create_followup.rs index a5c4f65f365..c8492e85cac 100644 --- a/twilight-http/src/request/application/interaction/create_followup.rs +++ b/twilight-http/src/request/application/interaction/create_followup.rs @@ -128,7 +128,7 @@ impl<'a> CreateFollowup<'a> { /// [`AttachmentFilename`]: twilight_validate::message::MessageValidationErrorType::AttachmentFilename pub fn attachments( mut self, - attachments: &'a [Attachment], + attachments: &'a [Attachment<'_>], ) -> Result { attachments.iter().try_for_each(validate_attachment)?; diff --git a/twilight-http/src/request/application/interaction/create_response.rs b/twilight-http/src/request/application/interaction/create_response.rs index 36410ba099b..60019baa581 100644 --- a/twilight-http/src/request/application/interaction/create_response.rs +++ b/twilight-http/src/request/application/interaction/create_response.rs @@ -17,7 +17,7 @@ use twilight_model::{ pub struct CreateResponse<'a> { interaction_id: Id, interaction_token: &'a str, - response: &'a InteractionResponse, + response: &'a InteractionResponse<'a>, http: &'a Client, } @@ -26,7 +26,7 @@ impl<'a> CreateResponse<'a> { http: &'a Client, interaction_id: Id, interaction_token: &'a str, - response: &'a InteractionResponse, + response: &'a InteractionResponse<'_>, ) -> Self { Self { interaction_id, diff --git a/twilight-http/src/request/application/interaction/update_followup.rs b/twilight-http/src/request/application/interaction/update_followup.rs index 5eaf0d0d34e..ca7cc9fdf5d 100644 --- a/twilight-http/src/request/application/interaction/update_followup.rs +++ b/twilight-http/src/request/application/interaction/update_followup.rs @@ -140,7 +140,7 @@ impl<'a> UpdateFollowup<'a> { /// [`AttachmentFilename`]: twilight_validate::message::MessageValidationErrorType::AttachmentFilename pub fn attachments( mut self, - attachments: &'a [Attachment], + attachments: &'a [Attachment<'_>], ) -> Result { attachments.iter().try_for_each(validate_attachment)?; diff --git a/twilight-http/src/request/application/interaction/update_response.rs b/twilight-http/src/request/application/interaction/update_response.rs index ab5ca40b8bf..3fc17e5b584 100644 --- a/twilight-http/src/request/application/interaction/update_response.rs +++ b/twilight-http/src/request/application/interaction/update_response.rs @@ -137,7 +137,7 @@ impl<'a> UpdateResponse<'a> { /// [`AttachmentFilename`]: twilight_validate::message::MessageValidationErrorType::AttachmentFilename pub fn attachments( mut self, - attachments: &'a [Attachment], + attachments: &'a [Attachment<'_>], ) -> Result { attachments.iter().try_for_each(validate_attachment)?; diff --git a/twilight-http/src/request/attachment.rs b/twilight-http/src/request/attachment.rs index 62a8f3ef1bc..fd353db3ec2 100644 --- a/twilight-http/src/request/attachment.rs +++ b/twilight-http/src/request/attachment.rs @@ -6,7 +6,7 @@ use twilight_model::{ }; pub struct AttachmentManager<'a> { - files: Vec<&'a Attachment>, + files: Vec<&'a Attachment<'a>>, ids: Vec>, } @@ -54,7 +54,7 @@ impl<'a> AttachmentManager<'a> { } #[must_use = "has no effect if not built into a Form"] - pub fn set_files(mut self, files: Vec<&'a Attachment>) -> Self { + pub fn set_files(mut self, files: Vec<&'a Attachment<'a>>) -> Self { self.files = files; self diff --git a/twilight-http/src/request/channel/message/create_message.rs b/twilight-http/src/request/channel/message/create_message.rs index c37b82ff387..41f1df8d210 100644 --- a/twilight-http/src/request/channel/message/create_message.rs +++ b/twilight-http/src/request/channel/message/create_message.rs @@ -135,7 +135,7 @@ impl<'a> CreateMessage<'a> { /// [`AttachmentFilename`]: twilight_validate::message::MessageValidationErrorType::AttachmentFilename pub fn attachments( mut self, - attachments: &'a [Attachment], + attachments: &'a [Attachment<'_>], ) -> Result { attachments.iter().try_for_each(validate_attachment)?; diff --git a/twilight-http/src/request/channel/message/update_message.rs b/twilight-http/src/request/channel/message/update_message.rs index 4b10dfeb338..f78bdc9ef96 100644 --- a/twilight-http/src/request/channel/message/update_message.rs +++ b/twilight-http/src/request/channel/message/update_message.rs @@ -144,7 +144,7 @@ impl<'a> UpdateMessage<'a> { /// [`AttachmentFilename`]: twilight_validate::message::MessageValidationErrorType::AttachmentFilename pub fn attachments( mut self, - attachments: &'a [Attachment], + attachments: &'a [Attachment<'a>], ) -> Result { attachments.iter().try_for_each(validate_attachment)?; diff --git a/twilight-http/src/request/channel/webhook/execute_webhook.rs b/twilight-http/src/request/channel/webhook/execute_webhook.rs index e74d605114d..8026e175ce0 100644 --- a/twilight-http/src/request/channel/webhook/execute_webhook.rs +++ b/twilight-http/src/request/channel/webhook/execute_webhook.rs @@ -145,7 +145,7 @@ impl<'a> ExecuteWebhook<'a> { /// [`AttachmentFilename`]: twilight_validate::message::MessageValidationErrorType::AttachmentFilename pub fn attachments( mut self, - attachments: &'a [Attachment], + attachments: &'a [Attachment<'_>], ) -> Result { attachments.iter().try_for_each(validate_attachment)?; diff --git a/twilight-http/src/request/channel/webhook/update_webhook_message.rs b/twilight-http/src/request/channel/webhook/update_webhook_message.rs index ac17d92c9ac..b00e10e1f91 100644 --- a/twilight-http/src/request/channel/webhook/update_webhook_message.rs +++ b/twilight-http/src/request/channel/webhook/update_webhook_message.rs @@ -136,7 +136,7 @@ impl<'a> UpdateWebhookMessage<'a> { /// [`AttachmentFilename`]: twilight_validate::message::MessageValidationErrorType::AttachmentFilename pub fn attachments( mut self, - attachments: &'a [Attachment], + attachments: &'a [Attachment<'_>], ) -> Result { attachments.iter().try_for_each(validate_attachment)?; diff --git a/twilight-model/src/http/attachment.rs b/twilight-model/src/http/attachment.rs index ff3ace157ab..8e3b86359f9 100644 --- a/twilight-model/src/http/attachment.rs +++ b/twilight-model/src/http/attachment.rs @@ -1,5 +1,7 @@ //! Models used when sending attachments to Discord. +use std::borrow::Cow; + use serde::{Deserialize, Serialize}; /// Attachments used in messages. @@ -25,14 +27,14 @@ use serde::{Deserialize, Serialize}; /// attachment.description("Raw data about Twilight Sparkle".to_owned()); /// ``` #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct Attachment { +pub struct Attachment<'a> { /// Description of the attachment, useful for screen readers and users /// requiring alt text. #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// Content of the file. #[serde(skip)] - pub file: Vec, + pub file: Cow<'a, [u8]>, /// Name of the file. /// /// Examples may be "twilight_sparkle.png", "cat.jpg", or "logs.txt". @@ -46,7 +48,7 @@ pub struct Attachment { pub id: u64, } -impl Attachment { +impl<'a> Attachment<'a> { /// Create an attachment from a filename and bytes. /// /// # Examples @@ -62,7 +64,7 @@ impl Attachment { /// /// let attachment = Attachment::from_bytes(filename, file_content, id); /// ``` - pub const fn from_bytes(filename: String, file: Vec, id: u64) -> Self { + pub const fn from_bytes(filename: String, file: Cow<'a, [u8]>, id: u64) -> Self { Self { description: None, file, diff --git a/twilight-model/src/http/interaction.rs b/twilight-model/src/http/interaction.rs index ce05c35bbc9..1a4d44f8f35 100644 --- a/twilight-model/src/http/interaction.rs +++ b/twilight-model/src/http/interaction.rs @@ -14,7 +14,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; /// /// [Discord Docs/Interaction Object]: https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct InteractionResponse { +pub struct InteractionResponse<'a> { /// Type of the response. #[serde(rename = "type")] pub kind: InteractionResponseType, @@ -31,18 +31,18 @@ pub struct InteractionResponse { /// [`Modal`]: InteractionResponseType::Modal /// [`UpdateMessage`]: InteractionResponseType::UpdateMessage #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, + pub data: Option>, } /// Data included in an interaction response. #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] -pub struct InteractionResponseData { +pub struct InteractionResponseData<'a> { /// Allowed mentions of the response. #[serde(skip_serializing_if = "Option::is_none")] pub allowed_mentions: Option, /// List of attachments on the response. #[serde(skip_serializing_if = "Option::is_none")] - pub attachments: Option>, + pub attachments: Option>>, /// List of autocomplete alternatives. /// /// Can only be used with @@ -126,7 +126,7 @@ mod tests { tts ); assert_impl_all!( - InteractionResponseData: Clone, + InteractionResponseData<'_>: Clone, Debug, Deserialize<'static>, PartialEq, @@ -187,7 +187,7 @@ mod tests { data: Some(InteractionResponseData { attachments: Some(Vec::from([Attachment { description: None, - file: "file data".into(), + file: "file data".as_bytes().into(), filename: "filename.jpg".into(), id: 1, }])), diff --git a/twilight-util/src/builder/interaction_response_data.rs b/twilight-util/src/builder/interaction_response_data.rs index 017b813ce0d..c478760ae6e 100644 --- a/twilight-util/src/builder/interaction_response_data.rs +++ b/twilight-util/src/builder/interaction_response_data.rs @@ -35,9 +35,9 @@ use twilight_model::{ /// ``` #[derive(Clone, Debug)] #[must_use = "builders have no effect if unused"] -pub struct InteractionResponseDataBuilder(InteractionResponseData); +pub struct InteractionResponseDataBuilder<'a>(InteractionResponseData<'a>); -impl InteractionResponseDataBuilder { +impl<'a> InteractionResponseDataBuilder<'a> { /// Create a new builder to construct an [`InteractionResponseData`]. pub const fn new() -> Self { Self(InteractionResponseData { @@ -57,7 +57,7 @@ impl InteractionResponseDataBuilder { /// Consume the builder, returning an [`InteractionResponseData`]. #[allow(clippy::missing_const_for_fn)] #[must_use = "builders have no effect if unused"] - pub fn build(self) -> InteractionResponseData { + pub fn build(self) -> InteractionResponseData<'a> { self.0 } @@ -74,7 +74,7 @@ impl InteractionResponseDataBuilder { /// Set the attachments of the message. /// /// Defaults to [`None`]. - pub fn attachments(mut self, attachments: impl IntoIterator) -> Self { + pub fn attachments(mut self, attachments: impl IntoIterator>) -> Self { self.0.attachments = Some(attachments.into_iter().collect()); self @@ -161,7 +161,7 @@ impl InteractionResponseDataBuilder { } } -impl Default for InteractionResponseDataBuilder { +impl<'a> Default for InteractionResponseDataBuilder<'a> { fn default() -> Self { Self::new() } @@ -178,7 +178,7 @@ mod tests { }; assert_impl_all!( - InteractionResponseDataBuilder: Clone, + InteractionResponseDataBuilder<'_>: Clone, Debug, Default, Send, diff --git a/twilight-validate/src/message.rs b/twilight-validate/src/message.rs index c7abc63de8b..18a26bbdb17 100644 --- a/twilight-validate/src/message.rs +++ b/twilight-validate/src/message.rs @@ -199,7 +199,7 @@ pub enum MessageValidationErrorType { /// /// [`AttachmentDescriptionTooLarge`]: MessageValidationErrorType::AttachmentDescriptionTooLarge /// [`AttachmentFilename`]: MessageValidationErrorType::AttachmentFilename -pub fn attachment(attachment: &Attachment) -> Result<(), MessageValidationError> { +pub fn attachment(attachment: &Attachment<'_>) -> Result<(), MessageValidationError> { attachment_filename(&attachment.filename)?; if let Some(description) = &attachment.description { From a93eda3fbd2f8cc2251aa69bf5b1cc7f964dab7f Mon Sep 17 00:00:00 2001 From: ITOH Date: Sat, 17 Sep 2022 16:19:38 +0200 Subject: [PATCH 2/3] fix docs --- twilight-model/src/http/attachment.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/twilight-model/src/http/attachment.rs b/twilight-model/src/http/attachment.rs index 8e3b86359f9..be1db123832 100644 --- a/twilight-model/src/http/attachment.rs +++ b/twilight-model/src/http/attachment.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; /// description for screen readers: /// /// ``` +/// use std::borrow::Cow; /// use twilight_model::http::attachment::Attachment; /// /// let filename = "twilight_sparkle.json".to_owned(); @@ -23,7 +24,7 @@ use serde::{Deserialize, Serialize}; /// .to_vec(); /// let id = 1; /// -/// let mut attachment = Attachment::from_bytes(filename, file_content, id); +/// let mut attachment = Attachment::from_bytes(filename, Cow::from(file_content), id); /// attachment.description("Raw data about Twilight Sparkle".to_owned()); /// ``` #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] @@ -56,13 +57,14 @@ impl<'a> Attachment<'a> { /// Create an attachment with a grocery list named "grocerylist.txt": /// /// ``` + /// use std::borrowed::Cow; /// use twilight_model::http::attachment::Attachment; /// /// let filename = "grocerylist.txt".to_owned(); /// let file_content = b"Apples\nGrapes\nLemonade".to_vec(); /// let id = 1; /// - /// let attachment = Attachment::from_bytes(filename, file_content, id); + /// let attachment = Attachment::from_bytes(filename, Cow::from(file_content), id); /// ``` pub const fn from_bytes(filename: String, file: Cow<'a, [u8]>, id: u64) -> Self { Self { From 5d232877dadf665ed22cf64abf371e26011e200a Mon Sep 17 00:00:00 2001 From: ITOH Date: Sat, 17 Sep 2022 16:28:08 +0200 Subject: [PATCH 3/3] fix wrong path --- twilight-model/src/http/attachment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilight-model/src/http/attachment.rs b/twilight-model/src/http/attachment.rs index be1db123832..eb0c0b4c32e 100644 --- a/twilight-model/src/http/attachment.rs +++ b/twilight-model/src/http/attachment.rs @@ -57,7 +57,7 @@ impl<'a> Attachment<'a> { /// Create an attachment with a grocery list named "grocerylist.txt": /// /// ``` - /// use std::borrowed::Cow; + /// use std::borrow::Cow; /// use twilight_model::http::attachment::Attachment; /// /// let filename = "grocerylist.txt".to_owned();