From 7b99731509fd39da0b672bcd11d1147dd9dd28fe Mon Sep 17 00:00:00 2001 From: Gnome! Date: Sat, 6 Jan 2024 00:14:25 +0000 Subject: [PATCH] Rewrite builders to take `Cow`s (#2688) --- examples/e14_slash_commands/Cargo.toml | 2 +- .../src/commands/attachmentinput.rs | 2 +- .../e14_slash_commands/src/commands/id.rs | 2 +- .../e14_slash_commands/src/commands/modal.rs | 2 +- .../src/commands/numberinput.rs | 2 +- .../e14_slash_commands/src/commands/ping.rs | 2 +- .../src/commands/welcome.rs | 30 +- .../src/commands/wonderful_command.rs | 2 +- examples/e14_slash_commands/src/main.rs | 2 +- examples/e17_message_components/src/main.rs | 5 +- .../e19_interactions_endpoint/src/main.rs | 2 +- examples/testing/src/main.rs | 13 +- src/builder/add_member.rs | 30 +- src/builder/bot_auth_parameters.rs | 28 +- src/builder/create_allowed_mentions.rs | 35 ++- src/builder/create_attachment.rs | 48 +-- src/builder/create_channel.rs | 36 +-- src/builder/create_command.rs | 228 ++++++++------ src/builder/create_command_permission.rs | 14 +- src/builder/create_components.rs | 260 ++++++++++------ src/builder/create_embed.rs | 288 +++++++++++------- src/builder/create_forum_post.rs | 27 +- src/builder/create_forum_tag.rs | 17 +- src/builder/create_interaction_response.rs | 141 +++++---- .../create_interaction_response_followup.rs | 52 ++-- src/builder/create_message.rs | 80 +++-- src/builder/create_poll.rs | 53 ++-- src/builder/create_scheduled_event.rs | 37 ++- src/builder/create_stage_instance.rs | 8 +- src/builder/create_sticker.rs | 24 +- src/builder/create_thread.rs | 8 +- src/builder/create_webhook.rs | 10 +- src/builder/edit_automod_rule.rs | 27 +- src/builder/edit_channel.rs | 36 +-- src/builder/edit_guild.rs | 34 ++- src/builder/edit_guild_welcome_screen.rs | 60 ++-- src/builder/edit_interaction_response.rs | 26 +- src/builder/edit_member.rs | 30 +- src/builder/edit_message.rs | 46 +-- src/builder/edit_profile.rs | 16 +- src/builder/edit_role.rs | 20 +- src/builder/edit_scheduled_event.rs | 30 +- src/builder/edit_stage_instance.rs | 6 +- src/builder/edit_sticker.rs | 14 +- src/builder/edit_thread.rs | 12 +- src/builder/edit_webhook.rs | 2 +- src/builder/edit_webhook_message.rs | 49 +-- src/builder/execute_webhook.rs | 52 ++-- src/builder/get_entitlements.rs | 16 +- src/builder/mod.rs | 9 +- src/framework/standard/help_commands.rs | 2 +- src/http/client.rs | 35 ++- src/http/multipart.rs | 14 +- src/http/request.rs | 4 +- src/model/application/command.rs | 19 +- src/model/application/command_interaction.rs | 10 +- .../application/component_interaction.rs | 10 +- src/model/application/modal_interaction.rs | 8 +- src/model/channel/channel_id.rs | 13 +- src/model/channel/guild_channel.rs | 13 +- src/model/channel/message.rs | 13 +- src/model/channel/private_channel.rs | 13 +- src/model/guild/guild_id.rs | 10 +- src/model/guild/member.rs | 4 +- src/model/guild/mod.rs | 10 +- src/model/guild/partial_guild.rs | 8 +- src/model/monetization.rs | 2 +- src/model/user.rs | 22 +- src/model/webhook.rs | 4 +- src/utils/quick_modal.rs | 20 +- 70 files changed, 1267 insertions(+), 942 deletions(-) diff --git a/examples/e14_slash_commands/Cargo.toml b/examples/e14_slash_commands/Cargo.toml index 4e73adeb7f2..d586c584823 100644 --- a/examples/e14_slash_commands/Cargo.toml +++ b/examples/e14_slash_commands/Cargo.toml @@ -2,7 +2,7 @@ name = "e14_slash_commands" version = "0.1.0" authors = ["my name "] -edition = "2018" +edition = "2021" [dependencies] serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "collector"] } diff --git a/examples/e14_slash_commands/src/commands/attachmentinput.rs b/examples/e14_slash_commands/src/commands/attachmentinput.rs index 21924fe2027..1dce65a1c14 100644 --- a/examples/e14_slash_commands/src/commands/attachmentinput.rs +++ b/examples/e14_slash_commands/src/commands/attachmentinput.rs @@ -12,7 +12,7 @@ pub fn run(options: &[ResolvedOption]) -> String { } } -pub fn register() -> CreateCommand { +pub fn register() -> CreateCommand<'static> { CreateCommand::new("attachmentinput") .description("Test command for attachment input") .add_option( diff --git a/examples/e14_slash_commands/src/commands/id.rs b/examples/e14_slash_commands/src/commands/id.rs index 74e29911191..47da776fb08 100644 --- a/examples/e14_slash_commands/src/commands/id.rs +++ b/examples/e14_slash_commands/src/commands/id.rs @@ -12,7 +12,7 @@ pub fn run(options: &[ResolvedOption]) -> String { } } -pub fn register() -> CreateCommand { +pub fn register() -> CreateCommand<'static> { CreateCommand::new("id").description("Get a user id").add_option( CreateCommandOption::new(CommandOptionType::User, "id", "The user to lookup") .required(true), diff --git a/examples/e14_slash_commands/src/commands/modal.rs b/examples/e14_slash_commands/src/commands/modal.rs index 1f3e7f918a1..1874ac6e80b 100644 --- a/examples/e14_slash_commands/src/commands/modal.rs +++ b/examples/e14_slash_commands/src/commands/modal.rs @@ -26,6 +26,6 @@ pub async fn run(ctx: &Context, interaction: &CommandInteraction) -> Result<(), Ok(()) } -pub fn register() -> CreateCommand { +pub fn register() -> CreateCommand<'static> { CreateCommand::new("modal").description("Asks some details about you") } diff --git a/examples/e14_slash_commands/src/commands/numberinput.rs b/examples/e14_slash_commands/src/commands/numberinput.rs index f7643bd3cca..18b77edf27a 100644 --- a/examples/e14_slash_commands/src/commands/numberinput.rs +++ b/examples/e14_slash_commands/src/commands/numberinput.rs @@ -1,7 +1,7 @@ use serenity::builder::{CreateCommand, CreateCommandOption}; use serenity::model::application::CommandOptionType; -pub fn register() -> CreateCommand { +pub fn register() -> CreateCommand<'static> { CreateCommand::new("numberinput") .description("Test command for number input") .add_option( diff --git a/examples/e14_slash_commands/src/commands/ping.rs b/examples/e14_slash_commands/src/commands/ping.rs index cd92b879919..6970a84e4fc 100644 --- a/examples/e14_slash_commands/src/commands/ping.rs +++ b/examples/e14_slash_commands/src/commands/ping.rs @@ -5,6 +5,6 @@ pub fn run(_options: &[ResolvedOption]) -> String { "Hey, I'm alive!".to_string() } -pub fn register() -> CreateCommand { +pub fn register() -> CreateCommand<'static> { CreateCommand::new("ping").description("A ping command") } diff --git a/examples/e14_slash_commands/src/commands/welcome.rs b/examples/e14_slash_commands/src/commands/welcome.rs index e11a98d6c7b..08d3bd86f61 100644 --- a/examples/e14_slash_commands/src/commands/welcome.rs +++ b/examples/e14_slash_commands/src/commands/welcome.rs @@ -1,7 +1,16 @@ +use std::borrow::Cow; +use std::collections::HashMap; + use serenity::builder::{CreateCommand, CreateCommandOption}; use serenity::model::application::CommandOptionType; -pub fn register() -> CreateCommand { +fn new_map<'a>(key: &'a str, value: &'a str) -> HashMap, Cow<'a, str>> { + let mut map = HashMap::with_capacity(1); + map.insert(Cow::Borrowed(key), Cow::Borrowed(value)); + map +} + +pub fn register() -> CreateCommand<'static> { CreateCommand::new("welcome") .description("Welcome a user") .name_localized("de", "begrüßen") @@ -20,27 +29,28 @@ pub fn register() -> CreateCommand { .add_string_choice_localized( "Welcome to our cool server! Ask me if you need help", "pizza", - [( + new_map( "de", "Willkommen auf unserem coolen Server! Frag mich, falls du Hilfe brauchst", - )], + ), + ) + .add_string_choice_localized( + "Hey, do you want a coffee?", + "coffee", + new_map("de", "Hey, willst du einen Kaffee?"), ) - .add_string_choice_localized("Hey, do you want a coffee?", "coffee", [( - "de", - "Hey, willst du einen Kaffee?", - )]) .add_string_choice_localized( "Welcome to the club, you're now a good person. Well, I hope.", "club", - [( + new_map( "de", "Willkommen im Club, du bist jetzt ein guter Mensch. Naja, hoffentlich.", - )], + ), ) .add_string_choice_localized( "I hope that you brought a controller to play together!", "game", - [("de", "Ich hoffe du hast einen Controller zum Spielen mitgebracht!")], + new_map("de", "Ich hoffe du hast einen Controller zum Spielen mitgebracht!"), ), ) } diff --git a/examples/e14_slash_commands/src/commands/wonderful_command.rs b/examples/e14_slash_commands/src/commands/wonderful_command.rs index 95e4f1761d8..d1f991a6427 100644 --- a/examples/e14_slash_commands/src/commands/wonderful_command.rs +++ b/examples/e14_slash_commands/src/commands/wonderful_command.rs @@ -1,5 +1,5 @@ use serenity::builder::CreateCommand; -pub fn register() -> CreateCommand { +pub fn register() -> CreateCommand<'static> { CreateCommand::new("wonderful_command").description("An amazing command") } diff --git a/examples/e14_slash_commands/src/main.rs b/examples/e14_slash_commands/src/main.rs index 70e5cea2a29..ac2ab28f6e3 100644 --- a/examples/e14_slash_commands/src/main.rs +++ b/examples/e14_slash_commands/src/main.rs @@ -49,7 +49,7 @@ impl EventHandler for Handler { ); let commands = guild_id - .set_commands(&ctx.http, vec![ + .set_commands(&ctx.http, &[ commands::ping::register(), commands::id::register(), commands::welcome::register(), diff --git a/examples/e17_message_components/src/main.rs b/examples/e17_message_components/src/main.rs index c70203354a9..81d3b944d78 100644 --- a/examples/e17_message_components/src/main.rs +++ b/examples/e17_message_components/src/main.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::env; use std::time::Duration; @@ -39,13 +40,13 @@ impl EventHandler for Handler { &ctx, CreateMessage::new().content("Please select your favorite animal").select_menu( CreateSelectMenu::new("animal_select", CreateSelectMenuKind::String { - options: vec![ + options: Cow::Borrowed(&[ CreateSelectMenuOption::new("🐈 meow", "Cat"), CreateSelectMenuOption::new("🐕 woof", "Dog"), CreateSelectMenuOption::new("🐎 neigh", "Horse"), CreateSelectMenuOption::new("🦙 hoooooooonk", "Alpaca"), CreateSelectMenuOption::new("🦀 crab rave", "Ferris"), - ], + ]), }) .custom_id("animal_select") .placeholder("No animal selected"), diff --git a/examples/e19_interactions_endpoint/src/main.rs b/examples/e19_interactions_endpoint/src/main.rs index 3f4c784ea63..a3ae19c5943 100644 --- a/examples/e19_interactions_endpoint/src/main.rs +++ b/examples/e19_interactions_endpoint/src/main.rs @@ -5,7 +5,7 @@ use serenity::model::application::*; type Error = Box; -fn handle_command(interaction: CommandInteraction) -> CreateInteractionResponse { +fn handle_command(interaction: CommandInteraction) -> CreateInteractionResponse<'static> { CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().content(format!( "Hello from interactions webhook HTTP server! <@{}>", interaction.user.id diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs index 76b059526aa..ea479e1e5e6 100644 --- a/examples/testing/src/main.rs +++ b/examples/testing/src/main.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serenity::builder::*; use serenity::model::prelude::*; use serenity::prelude::*; @@ -98,10 +100,10 @@ async fn message(ctx: &Context, msg: Message) -> Result<(), serenity::Error> { CreateButton::new_link("https://google.com").emoji('🔍').label("Search"), ) .select_menu(CreateSelectMenu::new("3", CreateSelectMenuKind::String { - options: vec![ + options: Cow::Borrowed(&[ CreateSelectMenuOption::new("foo", "foo"), CreateSelectMenuOption::new("bar", "bar"), - ], + ]), })), ) .await?; @@ -162,7 +164,8 @@ async fn message(ctx: &Context, msg: Message) -> Result<(), serenity::Error> { channel_id .edit_thread( &ctx, - EditThread::new().applied_tags(forum.available_tags.iter().map(|t| t.id)), + EditThread::new() + .applied_tags(forum.available_tags.iter().map(|t| t.id).collect::>()), ) .await?; } else if msg.content == "embedrace" { @@ -337,10 +340,10 @@ async fn interaction( CreateInteractionResponse::Message( CreateInteractionResponseMessage::new() .select_menu(CreateSelectMenu::new("0", CreateSelectMenuKind::String { - options: vec![ + options: Cow::Borrowed(&[ CreateSelectMenuOption::new("foo", "foo"), CreateSelectMenuOption::new("bar", "bar"), - ], + ]), })) .select_menu(CreateSelectMenu::new( "1", diff --git a/src/builder/add_member.rs b/src/builder/add_member.rs index 2e1dd13d768..beab54c84e5 100644 --- a/src/builder/add_member.rs +++ b/src/builder/add_member.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + #[cfg(feature = "http")] use super::Builder; #[cfg(feature = "http")] @@ -11,25 +13,25 @@ use crate::model::prelude::*; /// [Discord docs](https://discord.com/developers/docs/resources/guild#add-guild-member). #[derive(Clone, Debug, Serialize)] #[must_use] -pub struct AddMember { - access_token: String, +pub struct AddMember<'a> { + access_token: Cow<'a, str>, #[serde(skip_serializing_if = "Option::is_none")] - nick: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - roles: Vec, + nick: Option>, + #[serde(skip_serializing_if = "<[RoleId]>::is_empty")] + roles: Cow<'a, [RoleId]>, #[serde(skip_serializing_if = "Option::is_none")] mute: Option, #[serde(skip_serializing_if = "Option::is_none")] deaf: Option, } -impl AddMember { +impl<'a> AddMember<'a> { /// Constructs a new builder with the given access token, leaving all other fields empty. - pub fn new(access_token: String) -> Self { + pub fn new(access_token: impl Into>) -> Self { Self { - access_token, + access_token: access_token.into(), + roles: Cow::default(), nick: None, - roles: Vec::new(), mute: None, deaf: None, } @@ -38,7 +40,7 @@ impl AddMember { /// Sets the OAuth2 access token for this request, replacing the current one. /// /// Requires the access token to have the `guilds.join` scope granted. - pub fn access_token(mut self, access_token: impl Into) -> Self { + pub fn access_token(mut self, access_token: impl Into>) -> Self { self.access_token = access_token.into(); self } @@ -48,7 +50,7 @@ impl AddMember { /// Requires the [Manage Nicknames] permission. /// /// [Manage Nicknames]: crate::model::permissions::Permissions::MANAGE_NICKNAMES - pub fn nickname(mut self, nickname: impl Into) -> Self { + pub fn nickname(mut self, nickname: impl Into>) -> Self { self.nick = Some(nickname.into()); self } @@ -58,8 +60,8 @@ impl AddMember { /// Requires the [Manage Roles] permission. /// /// [Manage Roles]: crate::model::permissions::Permissions::MANAGE_ROLES - pub fn roles(mut self, roles: impl IntoIterator>) -> Self { - self.roles = roles.into_iter().map(Into::into).collect(); + pub fn roles(mut self, roles: impl Into>) -> Self { + self.roles = roles.into(); self } @@ -86,7 +88,7 @@ impl AddMember { #[cfg(feature = "http")] #[async_trait::async_trait] -impl Builder for AddMember { +impl Builder for AddMember<'_> { type Context<'ctx> = (GuildId, UserId); type Built = Option; diff --git a/src/builder/bot_auth_parameters.rs b/src/builder/bot_auth_parameters.rs index 8fa4c5f4321..acdd94f7cf6 100644 --- a/src/builder/bot_auth_parameters.rs +++ b/src/builder/bot_auth_parameters.rs @@ -1,3 +1,6 @@ +use std::borrow::Cow; +use std::fmt::Write; + use arrayvec::ArrayVec; use url::Url; @@ -7,18 +10,28 @@ use crate::http::Http; use crate::internal::prelude::*; use crate::model::prelude::*; +fn join_to_string(sep: char, iter: impl Iterator) -> String { + let mut buf = String::new(); + for item in iter { + write!(buf, "{item}{sep}").unwrap(); + } + + buf.truncate(buf.len() - 1); + buf +} + /// A builder for constructing an invite link with custom OAuth2 scopes. #[derive(Debug, Clone, Default)] #[must_use] -pub struct CreateBotAuthParameters { +pub struct CreateBotAuthParameters<'a> { client_id: Option, - scopes: Vec, + scopes: Cow<'a, [Scope]>, permissions: Permissions, guild_id: Option, disable_guild_select: bool, } -impl CreateBotAuthParameters { +impl<'a> CreateBotAuthParameters<'a> { /// Equivalent to [`Self::default`]. pub fn new() -> Self { Self::default() @@ -35,10 +48,7 @@ impl CreateBotAuthParameters { } if !self.scopes.is_empty() { - valid_data.push(( - "scope", - self.scopes.iter().map(ToString::to_string).collect::>().join(" "), - )); + valid_data.push(("scope", join_to_string(',', self.scopes.iter()))); } if bits != 0 { @@ -84,8 +94,8 @@ impl CreateBotAuthParameters { /// **Note**: This needs to include the [`Bot`] scope. /// /// [`Bot`]: Scope::Bot - pub fn scopes(mut self, scopes: &[Scope]) -> Self { - self.scopes = scopes.to_vec(); + pub fn scopes(mut self, scopes: impl Into>) -> Self { + self.scopes = scopes.into(); self } diff --git a/src/builder/create_allowed_mentions.rs b/src/builder/create_allowed_mentions.rs index 6655c967405..f739fcfcf05 100644 --- a/src/builder/create_allowed_mentions.rs +++ b/src/builder/create_allowed_mentions.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use arrayvec::ArrayVec; use serde::{Deserialize, Serialize}; @@ -34,17 +36,20 @@ impl ParseAction { /// ```rust,no_run /// # use serenity::builder::CreateMessage; /// # use serenity::model::channel::Message; +/// # use serenity::model::id::*; /// # /// # async fn run() -> Result<(), Box> { /// use serenity::builder::CreateAllowedMentions as Am; /// /// // Mention only the user 110372470472613888 /// # let m = CreateMessage::new(); -/// m.allowed_mentions(Am::new().users(vec![110372470472613888])); +/// m.allowed_mentions(Am::new().users([UserId::new(110372470472613888)].as_slice())); /// /// // Mention all users and the role 182894738100322304 /// # let m = CreateMessage::new(); -/// m.allowed_mentions(Am::new().all_users(true).roles(vec![182894738100322304])); +/// m.allowed_mentions( +/// Am::new().all_users(true).roles([RoleId::new(182894738100322304)].as_slice()), +/// ); /// /// // Mention all roles and nothing else /// # let m = CreateMessage::new(); @@ -57,13 +62,15 @@ impl ParseAction { /// // Mention everyone and the users 182891574139682816, 110372470472613888 /// # let m = CreateMessage::new(); /// m.allowed_mentions( -/// Am::new().everyone(true).users(vec![182891574139682816, 110372470472613888]), +/// Am::new() +/// .everyone(true) +/// .users([UserId::new(182891574139682816), UserId::new(110372470472613888)].as_slice()), /// ); /// /// // Mention everyone and the message author. /// # let m = CreateMessage::new(); /// # let msg: Message = unimplemented!(); -/// m.allowed_mentions(Am::new().everyone(true).users(vec![msg.author.id])); +/// m.allowed_mentions(Am::new().everyone(true).users([msg.author.id].as_slice())); /// # Ok(()) /// # } /// ``` @@ -71,15 +78,15 @@ impl ParseAction { /// [Discord docs](https://discord.com/developers/docs/resources/channel#allowed-mentions-object). #[derive(Clone, Debug, Default, Serialize, PartialEq)] #[must_use] -pub struct CreateAllowedMentions { +pub struct CreateAllowedMentions<'a> { parse: ArrayVec, - users: Vec, - roles: Vec, + users: Cow<'a, [UserId]>, + roles: Cow<'a, [RoleId]>, #[serde(skip_serializing_if = "Option::is_none")] replied_user: Option, } -impl CreateAllowedMentions { +impl<'a> CreateAllowedMentions<'a> { /// Equivalent to [`Self::default`]. pub fn new() -> Self { Self::default() @@ -113,29 +120,29 @@ impl CreateAllowedMentions { /// Sets the *specific* users that will be allowed mentionable. #[inline] - pub fn users(mut self, users: impl IntoIterator>) -> Self { - self.users = users.into_iter().map(Into::into).collect(); + pub fn users(mut self, users: impl Into>) -> Self { + self.users = users.into(); self } /// Clear the list of mentionable users. #[inline] pub fn empty_users(mut self) -> Self { - self.users.clear(); + self.users = Cow::default(); self } /// Sets the *specific* roles that will be allowed mentionable. #[inline] - pub fn roles(mut self, roles: impl IntoIterator>) -> Self { - self.roles = roles.into_iter().map(Into::into).collect(); + pub fn roles(mut self, roles: impl Into>) -> Self { + self.roles = roles.into(); self } /// Clear the list of mentionable roles. #[inline] pub fn empty_roles(mut self) -> Self { - self.roles.clear(); + self.roles = Cow::default(); self } diff --git a/src/builder/create_attachment.rs b/src/builder/create_attachment.rs index de7f346fe7a..2cef0a6b83f 100644 --- a/src/builder/create_attachment.rs +++ b/src/builder/create_attachment.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::path::Path; use tokio::fs::File; @@ -19,18 +20,21 @@ use crate::model::id::AttachmentId; #[derive(Clone, Debug, Serialize, PartialEq)] #[non_exhaustive] #[must_use] -pub struct CreateAttachment { +pub struct CreateAttachment<'a> { pub(crate) id: u64, // Placeholder ID will be filled in when sending the request - pub filename: String, - pub description: Option, + pub filename: Cow<'static, str>, + pub description: Option>, #[serde(skip)] - pub data: Vec, + pub data: Cow<'static, [u8]>, } -impl CreateAttachment { +impl<'a> CreateAttachment<'a> { /// Builds an [`CreateAttachment`] from the raw attachment data. - pub fn bytes(data: impl Into>, filename: impl Into) -> CreateAttachment { + pub fn bytes( + data: impl Into>, + filename: impl Into>, + ) -> Self { CreateAttachment { data: data.into(), filename: filename.into(), @@ -44,7 +48,7 @@ impl CreateAttachment { /// # Errors /// /// [`Error::Io`] if reading the file fails. - pub async fn path(path: impl AsRef) -> Result { + pub async fn path(path: impl AsRef) -> Result { let mut file = File::open(path.as_ref()).await?; let mut data = Vec::new(); file.read_to_end(&mut data).await?; @@ -56,7 +60,7 @@ impl CreateAttachment { ) })?; - Ok(CreateAttachment::bytes(data, filename.to_string_lossy().to_string())) + Ok(CreateAttachment::bytes(data, filename.to_string_lossy().into_owned())) } /// Builds an [`CreateAttachment`] by reading from a file handler. @@ -64,7 +68,7 @@ impl CreateAttachment { /// # Errors /// /// [`Error::Io`] error if reading the file fails. - pub async fn file(file: &File, filename: impl Into) -> Result { + pub async fn file(file: &File, filename: impl Into>) -> Result { let mut data = Vec::new(); file.try_clone().await?.read_to_end(&mut data).await?; @@ -77,7 +81,7 @@ impl CreateAttachment { /// /// [`Error::Url`] if the URL is invalid, [`Error::Http`] if downloading the data fails. #[cfg(feature = "http")] - pub async fn url(http: impl AsRef, url: &str) -> Result { + pub async fn url(http: impl AsRef, url: &str) -> Result { let url = Url::parse(url).map_err(|_| Error::Url(url.to_string()))?; let response = http.as_ref().client.get(url.clone()).send().await?; @@ -88,7 +92,7 @@ impl CreateAttachment { .and_then(Iterator::last) .ok_or_else(|| Error::Url(url.to_string()))?; - Ok(CreateAttachment::bytes(data, filename)) + Ok(CreateAttachment::bytes(data, filename.to_owned())) } /// Converts the stored data to the base64 representation. @@ -106,7 +110,7 @@ impl CreateAttachment { } /// Sets a description for the file (max 1024 characters). - pub fn description(mut self, description: impl Into) -> Self { + pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(description.into()); self } @@ -119,8 +123,8 @@ struct ExistingAttachment { #[derive(Debug, Clone, serde::Serialize, PartialEq)] #[serde(untagged)] -enum NewOrExisting { - New(CreateAttachment), +enum NewOrExisting<'a> { + New(CreateAttachment<'a>), Existing(ExistingAttachment), } @@ -146,7 +150,7 @@ enum NewOrExisting { /// /// ```rust,no_run /// # use serenity::all::*; -/// # async fn foo_(ctx: Http, mut msg: Message, my_attachment: CreateAttachment) -> Result<(), Error> { +/// # async fn foo_(ctx: Http, mut msg: Message, my_attachment: CreateAttachment<'_>) -> Result<(), Error> { /// msg.edit(ctx, EditMessage::new().attachments( /// EditAttachments::keep_all(&msg).add(my_attachment) /// )).await?; @@ -157,7 +161,7 @@ enum NewOrExisting { /// /// ```rust,no_run /// # use serenity::all::*; -/// # async fn foo_(ctx: Http, mut msg: Message, my_attachment: CreateAttachment) -> Result<(), Error> { +/// # async fn foo_(ctx: Http, mut msg: Message, my_attachment: CreateAttachment<'_>) -> Result<(), Error> { /// msg.edit(ctx, EditMessage::new().attachments( /// EditAttachments::new().keep(msg.attachments[0].id) /// )).await?; @@ -168,7 +172,7 @@ enum NewOrExisting { /// /// ```rust,no_run /// # use serenity::all::*; -/// # async fn foo_(ctx: Http, mut msg: Message, my_attachment: CreateAttachment) -> Result<(), Error> { +/// # async fn foo_(ctx: Http, mut msg: Message, my_attachment: CreateAttachment<'_>) -> Result<(), Error> { /// msg.edit(ctx, EditMessage::new().attachments( /// EditAttachments::keep_all(&msg).remove(msg.attachments[0].id) /// )).await?; @@ -182,11 +186,11 @@ enum NewOrExisting { #[derive(Default, Debug, Clone, serde::Serialize, PartialEq)] #[serde(transparent)] #[must_use] -pub struct EditAttachments { - new_and_existing_attachments: Vec, +pub struct EditAttachments<'a> { + new_and_existing_attachments: Vec>, } -impl EditAttachments { +impl<'a> EditAttachments<'a> { /// An empty attachments builder. /// /// Existing attachments are not kept by default, either. See [`Self::keep_all()`] or @@ -245,7 +249,7 @@ impl EditAttachments { /// Adds a new attachment to the attachment list. #[allow(clippy::should_implement_trait)] // Clippy thinks add == std::ops::Add::add - pub fn add(mut self, attachment: CreateAttachment) -> Self { + pub fn add(mut self, attachment: CreateAttachment<'a>) -> Self { self.new_and_existing_attachments.push(NewOrExisting::New(attachment)); self } @@ -253,7 +257,7 @@ impl EditAttachments { /// Clones all new attachments into a new Vec, keeping only data and filename, because those /// are needed for the multipart form data. The data is taken out of `self` in the process, so /// this method can only be called once. - pub(crate) fn take_files(&mut self) -> Vec { + pub(crate) fn take_files(&mut self) -> Vec> { let mut id_placeholder = 0; let mut files = Vec::new(); diff --git a/src/builder/create_channel.rs b/src/builder/create_channel.rs index 12fee5eca05..f5b45e489f5 100644 --- a/src/builder/create_channel.rs +++ b/src/builder/create_channel.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + #[cfg(feature = "http")] use super::Builder; #[cfg(feature = "http")] @@ -14,12 +16,12 @@ use crate::model::prelude::*; #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateChannel<'a> { - name: String, + name: Cow<'a, str>, #[serde(rename = "type")] kind: ChannelType, #[serde(skip_serializing_if = "Option::is_none")] - topic: Option, + topic: Option>, #[serde(skip_serializing_if = "Option::is_none")] bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -28,22 +30,22 @@ pub struct CreateChannel<'a> { rate_limit_per_user: Option, #[serde(skip_serializing_if = "Option::is_none")] position: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - permission_overwrites: Vec, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + permission_overwrites: Cow<'a, [PermissionOverwrite]>, #[serde(skip_serializing_if = "Option::is_none")] parent_id: Option, #[serde(skip_serializing_if = "Option::is_none")] nsfw: Option, #[serde(skip_serializing_if = "Option::is_none")] - rtc_region: Option, + rtc_region: Option>, #[serde(skip_serializing_if = "Option::is_none")] video_quality_mode: Option, #[serde(skip_serializing_if = "Option::is_none")] default_auto_archive_duration: Option, #[serde(skip_serializing_if = "Option::is_none")] default_reaction_emoji: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - available_tags: Vec, + #[serde(skip_serializing_if = "<[_]>::is_empty")] + available_tags: Cow<'a, [ForumTag]>, #[serde(skip_serializing_if = "Option::is_none")] default_sort_order: Option, @@ -54,7 +56,7 @@ pub struct CreateChannel<'a> { impl<'a> CreateChannel<'a> { /// Creates a builder with the given name, setting [`Self::kind`] to [`ChannelType::Text`] and /// leaving all other fields empty. - pub fn new(name: impl Into) -> Self { + pub fn new(name: impl Into>) -> Self { Self { name: name.into(), nsfw: None, @@ -65,13 +67,13 @@ impl<'a> CreateChannel<'a> { user_limit: None, rate_limit_per_user: None, kind: ChannelType::Text, - permission_overwrites: Vec::new(), + permission_overwrites: Cow::default(), audit_log_reason: None, rtc_region: None, video_quality_mode: None, default_auto_archive_duration: None, default_reaction_emoji: None, - available_tags: Vec::new(), + available_tags: Cow::default(), default_sort_order: None, } } @@ -79,7 +81,7 @@ impl<'a> CreateChannel<'a> { /// Specify how to call this new channel, replacing the current value as set in [`Self::new`]. /// /// **Note**: Must be between 2 and 100 characters long. - pub fn name(mut self, name: impl Into) -> Self { + pub fn name(mut self, name: impl Into>) -> Self { self.name = name.into(); self } @@ -103,7 +105,7 @@ impl<'a> CreateChannel<'a> { /// Channel topic (0-1024 characters) /// /// Only for [`ChannelType::Text`], [`ChannelType::News`], [`ChannelType::Forum`] - pub fn topic(mut self, topic: impl Into) -> Self { + pub fn topic(mut self, topic: impl Into>) -> Self { self.topic = Some(topic.into()); self } @@ -190,8 +192,8 @@ impl<'a> CreateChannel<'a> { /// # Ok(()) /// # } /// ``` - pub fn permissions(mut self, perms: impl IntoIterator) -> Self { - self.permission_overwrites = perms.into_iter().map(Into::into).collect(); + pub fn permissions(mut self, perms: impl Into>) -> Self { + self.permission_overwrites = perms.into(); self } @@ -204,7 +206,7 @@ impl<'a> CreateChannel<'a> { /// Channel voice region id of the voice or stage channel, automatic when not set /// /// Only for [`ChannelType::Voice`] and [`ChannelType::Stage`] - pub fn rtc_region(mut self, rtc_region: String) -> Self { + pub fn rtc_region(mut self, rtc_region: Cow<'a, str>) -> Self { self.rtc_region = Some(rtc_region); self } @@ -240,8 +242,8 @@ impl<'a> CreateChannel<'a> { /// Set of tags that can be used in a forum channel /// /// Only for [`ChannelType::Forum`] - pub fn available_tags(mut self, available_tags: impl IntoIterator) -> Self { - self.available_tags = available_tags.into_iter().collect(); + pub fn available_tags(mut self, available_tags: impl Into>) -> Self { + self.available_tags = available_tags.into(); self } diff --git a/src/builder/create_command.rs b/src/builder/create_command.rs index 5b1da3c1b9d..bb56430c532 100644 --- a/src/builder/create_command.rs +++ b/src/builder/create_command.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + #[cfg(feature = "http")] use super::Builder; #[cfg(feature = "http")] @@ -14,45 +16,73 @@ use crate::model::prelude::*; /// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure). #[derive(Clone, Debug, Serialize)] #[must_use] -pub struct CreateCommandOption(CommandOption); +pub struct CreateCommandOption<'a> { + #[serde(rename = "type")] + kind: CommandOptionType, + name: Cow<'a, str>, + #[serde(skip_serializing_if = "Option::is_none")] + name_localizations: Option, Cow<'a, str>>>, + description: Cow<'a, str>, + #[serde(skip_serializing_if = "Option::is_none")] + description_localizations: Option, Cow<'a, str>>>, + #[serde(default)] + required: bool, + #[serde(default)] + choices: Cow<'a, [CreateCommandOptionChoice<'a>]>, + #[serde(default)] + options: Cow<'a, [CreateCommandOption<'a>]>, + #[serde(default)] + channel_types: Cow<'a, [ChannelType]>, + #[serde(default)] + min_value: Option, + #[serde(default)] + max_value: Option, + #[serde(default)] + min_length: Option, + #[serde(default)] + max_length: Option, + #[serde(default)] + autocomplete: bool, +} -impl CreateCommandOption { +impl<'a> CreateCommandOption<'a> { /// Creates a new builder with the given option type, name, and description, leaving all other /// fields empty. pub fn new( kind: CommandOptionType, - name: impl Into, - description: impl Into, + name: impl Into>, + description: impl Into>, ) -> Self { - Self(CommandOption { + Self { kind, - name: name.into().into(), + name: name.into(), name_localizations: None, - description: description.into().into(), + description: description.into(), description_localizations: None, - __generated_flags: CommandOptionGeneratedFlags::empty(), + required: false, + autocomplete: false, min_value: None, max_value: None, min_length: None, max_length: None, - channel_types: FixedArray::default(), - choices: Vec::new(), - options: Vec::new(), - }) + channel_types: Cow::default(), + choices: Cow::default(), + options: Cow::default(), + } } /// Sets the `CommandOptionType`, replacing the current value as set in [`Self::new`]. pub fn kind(mut self, kind: CommandOptionType) -> Self { - self.0.kind = kind; + self.kind = kind; self } /// Sets the name of the option, replacing the current value as set in [`Self::new`]. /// /// **Note**: Must be between 1 and 32 lowercase characters, matching `r"^[\w-]{1,32}$"`. - pub fn name(mut self, name: impl Into) -> Self { - self.0.name = name.into().into(); + pub fn name(mut self, name: impl Into>) -> Self { + self.name = name.into(); self } @@ -66,8 +96,12 @@ impl CreateCommandOption { /// .name_localized("zh-CN", "岁数") /// # ; /// ``` - pub fn name_localized(mut self, locale: impl Into, name: impl Into) -> Self { - let map = self.0.name_localizations.get_or_insert_with(Default::default); + pub fn name_localized( + mut self, + locale: impl Into>, + name: impl Into>, + ) -> Self { + let map = self.name_localizations.get_or_insert_with(Default::default); map.insert(locale.into(), name.into()); self } @@ -75,8 +109,8 @@ impl CreateCommandOption { /// Sets the description for the option, replacing the current value as set in [`Self::new`]. /// /// **Note**: Must be between 1 and 100 characters. - pub fn description(mut self, description: impl Into) -> Self { - self.0.description = description.into().into(); + pub fn description(mut self, description: impl Into>) -> Self { + self.description = description.into(); self } /// Specifies a localized description of the option. @@ -91,10 +125,10 @@ impl CreateCommandOption { /// ``` pub fn description_localized( mut self, - locale: impl Into, - description: impl Into, + locale: impl Into>, + description: impl Into>, ) -> Self { - let map = self.0.description_localizations.get_or_insert_with(Default::default); + let map = self.description_localizations.get_or_insert_with(Default::default); map.insert(locale.into(), description.into()); self } @@ -103,7 +137,7 @@ impl CreateCommandOption { /// /// **Note**: This defaults to `false`. pub fn required(mut self, required: bool) -> Self { - self.0.set_required(required); + self.required = required; self } @@ -111,9 +145,9 @@ impl CreateCommandOption { /// /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100 /// characters. Value must be between -2^53 and 2^53. - pub fn add_int_choice(self, name: impl Into, value: i64) -> Self { - self.add_choice(CommandOptionChoice { - name: name.into().into(), + pub fn add_int_choice(self, name: impl Into>, value: i64) -> Self { + self.add_choice(CreateCommandOptionChoice { + name: name.into(), value: Value::from(value), name_localizations: None, }) @@ -122,16 +156,14 @@ impl CreateCommandOption { /// Adds a localized optional int-choice. See [`Self::add_int_choice`] for more info. pub fn add_int_choice_localized( self, - name: impl Into, + name: impl Into>, value: i64, - locales: impl IntoIterator, impl Into)>, + locales: impl Into, Cow<'a, str>>>, ) -> Self { - self.add_choice(CommandOptionChoice { - name: name.into().into(), - value: Value::from(value), - name_localizations: Some( - locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(), - ), + self.add_choice(CreateCommandOptionChoice { + name: name.into(), + value: value.into(), + name_localizations: Some(locales.into()), }) } @@ -139,9 +171,13 @@ impl CreateCommandOption { /// /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100 /// characters. Value must be up to 100 characters. - pub fn add_string_choice(self, name: impl Into, value: impl Into) -> Self { - self.add_choice(CommandOptionChoice { - name: name.into().into(), + pub fn add_string_choice( + self, + name: impl Into>, + value: impl Into, + ) -> Self { + self.add_choice(CreateCommandOptionChoice { + name: name.into(), value: Value::String(value.into()), name_localizations: None, }) @@ -150,16 +186,14 @@ impl CreateCommandOption { /// Adds a localized optional string-choice. See [`Self::add_string_choice`] for more info. pub fn add_string_choice_localized( self, - name: impl Into, + name: impl Into>, value: impl Into, - locales: impl IntoIterator, impl Into)>, + locales: impl Into, Cow<'a, str>>>, ) -> Self { - self.add_choice(CommandOptionChoice { - name: name.into().into(), + self.add_choice(CreateCommandOptionChoice { + name: name.into(), value: Value::String(value.into()), - name_localizations: Some( - locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(), - ), + name_localizations: Some(locales.into()), }) } @@ -167,9 +201,9 @@ impl CreateCommandOption { /// /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100 /// characters. Value must be between -2^53 and 2^53. - pub fn add_number_choice(self, name: impl Into, value: f64) -> Self { - self.add_choice(CommandOptionChoice { - name: name.into().into(), + pub fn add_number_choice(self, name: impl Into>, value: f64) -> Self { + self.add_choice(CreateCommandOptionChoice { + name: name.into(), value: Value::from(value), name_localizations: None, }) @@ -178,21 +212,19 @@ impl CreateCommandOption { /// Adds a localized optional number-choice. See [`Self::add_number_choice`] for more info. pub fn add_number_choice_localized( self, - name: impl Into, + name: impl Into>, value: f64, - locales: impl IntoIterator, impl Into)>, + locales: impl Into, Cow<'a, str>>>, ) -> Self { - self.add_choice(CommandOptionChoice { - name: name.into().into(), + self.add_choice(CreateCommandOptionChoice { + name: name.into(), value: Value::from(value), - name_localizations: Some( - locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(), - ), + name_localizations: Some(locales.into()), }) } - fn add_choice(mut self, value: CommandOptionChoice) -> Self { - self.0.choices.push(value); + fn add_choice(mut self, value: CreateCommandOptionChoice<'a>) -> Self { + self.choices.to_mut().push(value); self } @@ -202,7 +234,7 @@ impl CreateCommandOption { /// - May not be set to `true` if `choices` are set /// - Options using `autocomplete` are not confined to only use given choices pub fn set_autocomplete(mut self, value: bool) -> Self { - self.0.set_autocomplete(value); + self.autocomplete = value; self } @@ -218,9 +250,9 @@ impl CreateCommandOption { /// [`SubCommand`]: crate::model::application::CommandOptionType::SubCommand pub fn set_sub_options( mut self, - sub_options: impl IntoIterator, + sub_options: impl Into]>>, ) -> Self { - self.0.options = sub_options.into_iter().map(|o| o.0).collect(); + self.options = sub_options.into(); self } @@ -231,40 +263,40 @@ impl CreateCommandOption { /// /// [`SubCommandGroup`]: crate::model::application::CommandOptionType::SubCommandGroup /// [`SubCommand`]: crate::model::application::CommandOptionType::SubCommand - pub fn add_sub_option(mut self, sub_option: CreateCommandOption) -> Self { - self.0.options.push(sub_option.0); + pub fn add_sub_option(mut self, sub_option: CreateCommandOption<'a>) -> Self { + self.options.to_mut().push(sub_option); self } /// If the option is a [`Channel`], it will only be able to show these types. /// /// [`Channel`]: crate::model::application::CommandOptionType::Channel - pub fn channel_types(mut self, channel_types: Vec) -> Self { - self.0.channel_types = channel_types.into(); + pub fn channel_types(mut self, channel_types: impl Into>) -> Self { + self.channel_types = channel_types.into(); self } /// Sets the minimum permitted value for this integer option pub fn min_int_value(mut self, value: i64) -> Self { - self.0.min_value = Some(value.into()); + self.min_value = Some(value.into()); self } /// Sets the maximum permitted value for this integer option pub fn max_int_value(mut self, value: i64) -> Self { - self.0.max_value = Some(value.into()); + self.max_value = Some(value.into()); self } /// Sets the minimum permitted value for this number option pub fn min_number_value(mut self, value: f64) -> Self { - self.0.min_value = serde_json::Number::from_f64(value); + self.min_value = serde_json::Number::from_f64(value); self } /// Sets the maximum permitted value for this number option pub fn max_number_value(mut self, value: f64) -> Self { - self.0.max_value = serde_json::Number::from_f64(value); + self.max_value = serde_json::Number::from_f64(value); self } @@ -272,7 +304,7 @@ impl CreateCommandOption { /// /// The value of `min_length` must be greater or equal to `0`. pub fn min_length(mut self, value: u16) -> Self { - self.0.min_length = Some(value); + self.min_length = Some(value); self } @@ -281,7 +313,7 @@ impl CreateCommandOption { /// /// The value of `max_length` must be greater or equal to `1`. pub fn max_length(mut self, value: u16) -> Self { - self.0.max_length = Some(value); + self.max_length = Some(value); self } @@ -298,15 +330,15 @@ impl CreateCommandOption { /// - [guild command](https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command-json-params) #[derive(Clone, Debug, Serialize)] #[must_use] -pub struct CreateCommand { - name: FixedString, - name_localizations: HashMap, +pub struct CreateCommand<'a> { + name: Cow<'a, str>, + name_localizations: HashMap, Cow<'a, str>>, #[serde(skip_serializing_if = "Option::is_none")] - description: Option, - description_localizations: HashMap, - options: Vec, + description: Option>, + description_localizations: HashMap, Cow<'a, str>>, + options: Cow<'a, [CreateCommandOption<'a>]>, #[serde(skip_serializing_if = "Option::is_none")] - default_member_permissions: Option, + default_member_permissions: Option, #[serde(skip_serializing_if = "Option::is_none")] dm_permission: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -321,13 +353,13 @@ pub struct CreateCommand { handler: Option, } -impl CreateCommand { +impl<'a> CreateCommand<'a> { /// Creates a new builder with the given name, leaving all other fields empty. - pub fn new(name: impl Into) -> Self { + pub fn new(name: impl Into>) -> Self { Self { kind: None, - name: name.into().into(), + name: name.into(), name_localizations: HashMap::new(), description: None, description_localizations: HashMap::new(), @@ -337,7 +369,7 @@ impl CreateCommand { integration_types: None, contexts: None, - options: Vec::new(), + options: Cow::default(), nsfw: false, handler: None, } @@ -349,8 +381,8 @@ impl CreateCommand { /// **Note**: Must be between 1 and 32 lowercase characters, matching `r"^[\w-]{1,32}$"`. Two /// global commands of the same app cannot have the same name. Two guild-specific commands of /// the same app cannot have the same name. - pub fn name(mut self, name: impl Into) -> Self { - self.name = name.into().into(); + pub fn name(mut self, name: impl Into>) -> Self { + self.name = name.into(); self } @@ -363,7 +395,11 @@ impl CreateCommand { /// .name_localized("el", "γενέθλια") /// # ; /// ``` - pub fn name_localized(mut self, locale: impl Into, name: impl Into) -> Self { + pub fn name_localized( + mut self, + locale: impl Into>, + name: impl Into>, + ) -> Self { self.name_localizations.insert(locale.into(), name.into()); self } @@ -376,7 +412,7 @@ impl CreateCommand { /// Specifies the default permissions required to execute the command. pub fn default_member_permissions(mut self, permissions: Permissions) -> Self { - self.default_member_permissions = Some(permissions.bits().to_string()); + self.default_member_permissions = Some(permissions); self } @@ -390,7 +426,7 @@ impl CreateCommand { /// Specifies the description of the application command. /// /// **Note**: Must be between 1 and 100 characters long. - pub fn description(mut self, description: impl Into) -> Self { + pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(description.into()); self } @@ -405,8 +441,8 @@ impl CreateCommand { /// ``` pub fn description_localized( mut self, - locale: impl Into, - description: impl Into, + locale: impl Into>, + description: impl Into>, ) -> Self { self.description_localizations.insert(locale.into(), description.into()); self @@ -415,16 +451,16 @@ impl CreateCommand { /// Adds an application command option for the application command. /// /// **Note**: Application commands can have up to 25 options. - pub fn add_option(mut self, option: CreateCommandOption) -> Self { - self.options.push(option); + pub fn add_option(mut self, option: CreateCommandOption<'a>) -> Self { + self.options.to_mut().push(option); self } /// Sets all the application command options for the application command. /// /// **Note**: Application commands can have up to 25 options. - pub fn set_options(mut self, options: Vec) -> Self { - self.options = options; + pub fn set_options(mut self, options: impl Into]>>) -> Self { + self.options = options.into(); self } @@ -470,7 +506,7 @@ impl CreateCommand { #[cfg(feature = "http")] #[async_trait::async_trait] -impl Builder for CreateCommand { +impl Builder for CreateCommand<'_> { type Context<'ctx> = (Option, Option); type Built = Command; @@ -504,3 +540,11 @@ impl Builder for CreateCommand { } } } + +#[derive(Clone, Debug, Serialize)] +struct CreateCommandOptionChoice<'a> { + pub name: Cow<'a, str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub name_localizations: Option, Cow<'a, str>>>, + pub value: Value, +} diff --git a/src/builder/create_command_permission.rs b/src/builder/create_command_permission.rs index 9177ea4651f..fad0fb5ada8 100644 --- a/src/builder/create_command_permission.rs +++ b/src/builder/create_command_permission.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + #[cfg(feature = "http")] use super::Builder; #[cfg(feature = "http")] @@ -14,21 +16,21 @@ use crate::model::prelude::*; // `permissions` is added to the HTTP endpoint #[derive(Clone, Debug, Default, Serialize)] #[must_use] -pub struct EditCommandPermissions { - permissions: Vec, +pub struct EditCommandPermissions<'a> { + permissions: Cow<'a, [CreateCommandPermission]>, } -impl EditCommandPermissions { - pub fn new(permissions: Vec) -> Self { +impl<'a> EditCommandPermissions<'a> { + pub fn new(permissions: impl Into>) -> Self { Self { - permissions, + permissions: permissions.into(), } } } #[cfg(feature = "http")] #[async_trait::async_trait] -impl Builder for EditCommandPermissions { +impl Builder for EditCommandPermissions<'_> { type Context<'ctx> = (GuildId, CommandId); type Built = CommandPermissions; diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index 8c339a8d79d..192abd8e8de 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde::Serialize; use crate::model::prelude::*; @@ -7,14 +9,14 @@ use crate::model::prelude::*; /// [Discord docs](https://discord.com/developers/docs/interactions/message-components#component-object). #[derive(Clone, Debug, PartialEq)] #[must_use] -pub enum CreateActionRow { - Buttons(Vec), - SelectMenu(CreateSelectMenu), +pub enum CreateActionRow<'a> { + Buttons(Vec>), + SelectMenu(CreateSelectMenu<'a>), /// Only valid in modals! - InputText(CreateInputText), + InputText(CreateInputText<'a>), } -impl serde::Serialize for CreateActionRow { +impl serde::Serialize for CreateActionRow<'_> { fn serialize(&self, serializer: S) -> Result { use serde::ser::SerializeMap as _; @@ -34,66 +36,82 @@ impl serde::Serialize for CreateActionRow { /// A builder for creating a button component in a message #[derive(Clone, Debug, Serialize, PartialEq)] #[must_use] -pub struct CreateButton(Button); +pub struct CreateButton<'a> { + style: ButtonStyle, + #[serde(rename = "type")] + kind: ComponentType, + #[serde(skip_serializing_if = "Option::is_none")] + url: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + custom_id: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + sku_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + label: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + emoji: Option, + #[serde(default)] + disabled: bool, +} -impl CreateButton { +impl<'a> CreateButton<'a> { /// Creates a link button to the given URL. You must also set [`Self::label`] and/or /// [`Self::emoji`] after this. /// /// Clicking this button _will not_ trigger an interaction event in your bot. - pub fn new_link(url: impl Into) -> Self { - Self(Button { + pub fn new_link(url: impl Into>) -> Self { + Self { + style: ButtonStyle::Unknown(5), kind: ComponentType::Button, - data: ButtonKind::Link { - url: url.into().into(), - }, + url: Some(url.into()), + custom_id: None, + sku_id: None, label: None, emoji: None, disabled: false, - }) + } } /// Creates a new premium button associated with the given SKU. /// /// Clicking this button _will not_ trigger an interaction event in your bot. pub fn new_premium(sku_id: impl Into) -> Self { - Self(Button { + Self { + style: ButtonStyle::Unknown(6), kind: ComponentType::Button, - data: ButtonKind::Premium { - sku_id: sku_id.into(), - }, - label: None, + url: None, + custom_id: None, emoji: None, + label: None, + sku_id: Some(sku_id.into()), disabled: false, - }) + } } /// Creates a normal button with the given custom ID. You must also set [`Self::label`] and/or /// [`Self::emoji`] after this. - pub fn new(custom_id: impl Into) -> Self { - Self(Button { + pub fn new(custom_id: impl Into>) -> Self { + Self { kind: ComponentType::Button, - data: ButtonKind::NonLink { - style: ButtonStyle::Primary, - custom_id: custom_id.into().into(), - }, + style: ButtonStyle::Primary, + url: None, + custom_id: Some(custom_id.into()), + sku_id: None, label: None, emoji: None, disabled: false, - }) + } } /// Sets the custom id of the button, a developer-defined identifier. Replaces the current /// value as set in [`Self::new`]. /// /// Has no effect on link buttons and premium buttons. - pub fn custom_id(mut self, id: impl Into) -> Self { - if let ButtonKind::NonLink { - custom_id, .. - } = &mut self.0.data - { - *custom_id = id.into().into(); + pub fn custom_id(mut self, id: impl Into>) -> Self { + if self.url.is_none() { + self.custom_id = Some(id.into()); } + self } @@ -101,37 +119,57 @@ impl CreateButton { /// /// Has no effect on link buttons and premium buttons. pub fn style(mut self, new_style: ButtonStyle) -> Self { - if let ButtonKind::NonLink { - style, .. - } = &mut self.0.data - { - *style = new_style; + if self.url.is_none() { + self.style = new_style; } + self } /// Sets label of the button. - pub fn label(mut self, label: impl Into) -> Self { - self.0.label = Some(label.into().into()); + pub fn label(mut self, label: impl Into>) -> Self { + self.label = Some(label.into()); self } /// Sets emoji of the button. pub fn emoji(mut self, emoji: impl Into) -> Self { - self.0.emoji = Some(emoji.into()); + self.emoji = Some(emoji.into()); self } /// Sets the disabled state for the button. pub fn disabled(mut self, disabled: bool) -> Self { - self.0.disabled = disabled; + self.disabled = disabled; self } } -impl From