From 3b4713e8c3c03b53484082cbad17e17b668e6545 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Sun, 3 Dec 2023 12:43:27 +0000 Subject: [PATCH 001/159] Remove temporary `Vec` usage in the `http` module (#2624, #2646) This avoids having to allocate to store fixed length (replaced with normal array) or fixed capacity (replaced with `ArrayVec`) collections as vectors for the purposes of putting them through the `Request` plumbing. Slight behavioral change - before, setting `params` to `Some(vec![])` would still append a question mark to the end of the url. Now, we check if the params array `is_empty` instead of `is_some`, so the question mark won't be appended if the params list is empty. Co-authored-by: Michael Krasnitski <42564254+mkrasnitski@users.noreply.github.com> --- src/http/client.rs | 451 +++++++++++++++++++++------------------ src/http/ratelimiting.rs | 13 +- src/http/request.rs | 37 ++-- 3 files changed, 273 insertions(+), 228 deletions(-) diff --git a/src/http/client.rs b/src/http/client.rs index 659e0c98c8f..2ab32fc43c5 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -5,6 +5,7 @@ use std::num::NonZeroU64; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; +use arrayvec::ArrayVec; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use reqwest::header::{HeaderMap as Headers, HeaderValue}; #[cfg(feature = "utils")] @@ -246,7 +247,7 @@ impl Http { guild_id, user_id, }, - params: None, + params: [].into(), }) .await?; @@ -279,7 +280,7 @@ impl Http { role_id, user_id, }, - params: None, + params: [].into(), }) .await } @@ -311,7 +312,7 @@ impl Http { guild_id, user_id, }, - params: Some(vec![("delete_message_seconds", delete_message_seconds.to_string())]), + params: [("delete_message_seconds", delete_message_seconds.to_string())].into(), }) .await } @@ -334,7 +335,7 @@ impl Http { route: Route::GuildBulkBan { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -355,7 +356,7 @@ impl Http { route: Route::ChannelTyping { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -384,7 +385,7 @@ impl Http { route: Route::GuildChannels { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -401,7 +402,7 @@ impl Http { headers: audit_log_reason.map(reason_into_header), method: LightMethod::Post, route: Route::StageInstances, - params: None, + params: [].into(), }) .await } @@ -425,7 +426,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }) .await } @@ -447,7 +448,7 @@ impl Http { route: Route::ChannelThreads { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -482,7 +483,7 @@ impl Http { route: Route::ChannelForumPosts { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -508,7 +509,7 @@ impl Http { route: Route::GuildEmojis { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -527,7 +528,7 @@ impl Http { route: Route::Emojis { application_id: self.try_application_id()?, }, - params: None, + params: [].into(), }) .await } @@ -550,7 +551,7 @@ impl Http { application_id: self.try_application_id()?, token: interaction_token, }, - params: None, + params: [].into(), }; if files.is_empty() { @@ -585,7 +586,7 @@ impl Http { route: Route::Commands { application_id: self.try_application_id()?, }, - params: None, + params: [].into(), }) .await } @@ -603,7 +604,7 @@ impl Http { route: Route::Commands { application_id: self.try_application_id()?, }, - params: None, + params: [].into(), }) .await } @@ -623,7 +624,7 @@ impl Http { application_id: self.try_application_id()?, guild_id, }, - params: None, + params: [].into(), }) .await } @@ -668,7 +669,7 @@ impl Http { headers: None, method: LightMethod::Post, route: Route::Guilds, - params: None, + params: [].into(), }) .await } @@ -694,7 +695,7 @@ impl Http { application_id: self.try_application_id()?, guild_id, }, - params: None, + params: [].into(), }) .await } @@ -723,7 +724,7 @@ impl Http { guild_id, integration_id, }, - params: None, + params: [].into(), }) .await } @@ -750,7 +751,7 @@ impl Http { interaction_id, token: interaction_token, }, - params: None, + params: [].into(), }; if files.is_empty() { @@ -792,7 +793,7 @@ impl Http { route: Route::ChannelInvites { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -816,7 +817,7 @@ impl Http { channel_id, target_id, }, - params: None, + params: [].into(), }) .await } @@ -831,7 +832,7 @@ impl Http { headers: None, method: LightMethod::Post, route: Route::UserMeDmChannels, - params: None, + params: [].into(), }) .await } @@ -853,7 +854,7 @@ impl Http { message_id, reaction: &reaction_type.as_data(), }, - params: Some(vec![("burst", burst.to_string())]), + params: [("burst", burst.to_string())].into(), }) .await } @@ -894,7 +895,7 @@ impl Http { route: Route::GuildRoles { guild_id, }, - params: None, + params: [].into(), }) .await?; @@ -927,7 +928,7 @@ impl Http { route: Route::GuildScheduledEvents { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -956,7 +957,7 @@ impl Http { route: Route::GuildStickers { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -995,7 +996,7 @@ impl Http { route: Route::Entitlements { application_id: self.try_application_id()?, }, - params: None, + params: [].into(), }) .await } @@ -1042,7 +1043,7 @@ impl Http { route: Route::ChannelWebhooks { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -1061,7 +1062,7 @@ impl Http { route: Route::Channel { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -1080,7 +1081,7 @@ impl Http { route: Route::StageInstance { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -1103,7 +1104,7 @@ impl Http { guild_id, emoji_id, }, - params: None, + params: [].into(), }) .await } @@ -1119,7 +1120,7 @@ impl Http { application_id: self.try_application_id()?, emoji_id, }, - params: None, + params: [].into(), }) .await } @@ -1140,7 +1141,7 @@ impl Http { token: interaction_token, message_id, }, - params: None, + params: [].into(), }) .await } @@ -1156,7 +1157,7 @@ impl Http { application_id: self.try_application_id()?, command_id, }, - params: None, + params: [].into(), }) .await } @@ -1171,7 +1172,7 @@ impl Http { route: Route::Guild { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -1192,7 +1193,7 @@ impl Http { guild_id, command_id, }, - params: None, + params: [].into(), }) .await } @@ -1213,7 +1214,7 @@ impl Http { guild_id, integration_id, }, - params: None, + params: [].into(), }) .await } @@ -1232,7 +1233,7 @@ impl Http { route: Route::Invite { code, }, - params: None, + params: [].into(), }) .await } @@ -1253,7 +1254,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }) .await } @@ -1273,7 +1274,7 @@ impl Http { route: Route::ChannelMessagesBulkDelete { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -1310,7 +1311,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }) .await } @@ -1332,7 +1333,7 @@ impl Http { message_id, reaction: &reaction_type.as_data(), }, - params: None, + params: [].into(), }) .await } @@ -1351,7 +1352,7 @@ impl Http { application_id: self.try_application_id()?, token: interaction_token, }, - params: None, + params: [].into(), }) .await } @@ -1372,7 +1373,7 @@ impl Http { channel_id, target_id, }, - params: None, + params: [].into(), }) .await } @@ -1396,7 +1397,7 @@ impl Http { user_id, reaction: &reaction_type.as_data(), }, - params: None, + params: [].into(), }) .await } @@ -1418,7 +1419,7 @@ impl Http { message_id, reaction: &reaction_type.as_data(), }, - params: None, + params: [].into(), }) .await } @@ -1439,7 +1440,7 @@ impl Http { guild_id, role_id, }, - params: None, + params: [].into(), }) .await } @@ -1464,7 +1465,7 @@ impl Http { guild_id, event_id, }, - params: None, + params: [].into(), }) .await } @@ -1487,7 +1488,7 @@ impl Http { guild_id, sticker_id, }, - params: None, + params: [].into(), }) .await } @@ -1504,7 +1505,7 @@ impl Http { application_id: self.try_application_id()?, entitlement_id, }, - params: None, + params: [].into(), }) .await } @@ -1541,7 +1542,7 @@ impl Http { route: Route::Webhook { webhook_id, }, - params: None, + params: [].into(), }) .await } @@ -1582,7 +1583,7 @@ impl Http { webhook_id, token, }, - params: None, + params: [].into(), }) .await } @@ -1604,7 +1605,7 @@ impl Http { route: Route::Channel { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -1624,7 +1625,7 @@ impl Http { route: Route::StageInstance { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -1650,7 +1651,7 @@ impl Http { guild_id, emoji_id, }, - params: None, + params: [].into(), }) .await } @@ -1674,7 +1675,7 @@ impl Http { application_id: self.try_application_id()?, emoji_id, }, - params: None, + params: [].into(), }) .await } @@ -1701,7 +1702,7 @@ impl Http { token: interaction_token, message_id, }, - params: None, + params: [].into(), }; if new_attachments.is_empty() { @@ -1737,7 +1738,7 @@ impl Http { token: interaction_token, message_id, }, - params: None, + params: [].into(), }) .await } @@ -1763,7 +1764,7 @@ impl Http { application_id: self.try_application_id()?, command_id, }, - params: None, + params: [].into(), }) .await } @@ -1785,7 +1786,7 @@ impl Http { route: Route::Guild { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -1813,7 +1814,7 @@ impl Http { guild_id, command_id, }, - params: None, + params: [].into(), }) .await } @@ -1841,7 +1842,7 @@ impl Http { guild_id, command_id, }, - params: None, + params: [].into(), }) .await } @@ -1862,7 +1863,7 @@ impl Http { route: Route::GuildChannels { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -1889,7 +1890,7 @@ impl Http { route: Route::GuildMfa { guild_id, }, - params: None, + params: [].into(), }) .await .map(|mfa: GuildMfaLevel| mfa.level) @@ -1912,7 +1913,7 @@ impl Http { route: Route::GuildWidget { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -1934,7 +1935,7 @@ impl Http { route: Route::GuildWelcomeScreen { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -1959,7 +1960,7 @@ impl Http { guild_id, user_id, }, - params: None, + params: [].into(), }) .await?; @@ -1989,7 +1990,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }; if new_attachments.is_empty() { @@ -2022,7 +2023,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }) .await } @@ -2044,7 +2045,7 @@ impl Http { route: Route::GuildMemberMe { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -2069,7 +2070,7 @@ impl Http { route: Route::GuildMemberMe { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -2091,7 +2092,7 @@ impl Http { route: Route::ChannelFollowNews { channel_id: news_channel_id, }, - params: None, + params: [].into(), }) .await } @@ -2110,7 +2111,7 @@ impl Http { application_id: self.try_application_id()?, token: interaction_token, }, - params: None, + params: [].into(), }) .await } @@ -2135,7 +2136,7 @@ impl Http { application_id: self.try_application_id()?, token: interaction_token, }, - params: None, + params: [].into(), }; if new_attachments.is_empty() { @@ -2161,7 +2162,7 @@ impl Http { headers: None, method: LightMethod::Patch, route: Route::UserMe, - params: None, + params: [].into(), }) .await } @@ -2184,7 +2185,7 @@ impl Http { guild_id, role_id, }, - params: None, + params: [].into(), }) .await?; @@ -2218,7 +2219,7 @@ impl Http { route: Route::GuildRoles { guild_id, }, - params: None, + params: [].into(), }) .await?; @@ -2255,7 +2256,7 @@ impl Http { guild_id, event_id, }, - params: None, + params: [].into(), }) .await } @@ -2282,7 +2283,7 @@ impl Http { guild_id, sticker_id, }, - params: None, + params: [].into(), }) .await?; @@ -2308,7 +2309,7 @@ impl Http { route: Route::Channel { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -2358,7 +2359,7 @@ impl Http { guild_id, user_id, }, - params: None, + params: [].into(), }) .await } @@ -2409,7 +2410,7 @@ impl Http { route: Route::GuildVoiceStateMe { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -2431,7 +2432,7 @@ impl Http { route: Route::ChannelVoiceStatus { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -2482,7 +2483,7 @@ impl Http { route: Route::Webhook { webhook_id, }, - params: None, + params: [].into(), }) .await } @@ -2530,7 +2531,7 @@ impl Http { webhook_id, token, }, - params: None, + params: [].into(), }) .await } @@ -2594,7 +2595,9 @@ impl Http { files: Vec, map: &impl serde::Serialize, ) -> Result> { - let mut params = vec![("wait", wait.to_string())]; + let mut params = ArrayVec::<_, 2>::new(); + + params.push(("wait", wait.to_string())); if let Some(thread_id) = thread_id { params.push(("thread_id", thread_id.to_string())); } @@ -2608,7 +2611,7 @@ impl Http { webhook_id, token, }, - params: Some(params), + params, }; if files.is_empty() { @@ -2638,6 +2641,11 @@ impl Http { token: &str, message_id: MessageId, ) -> Result { + let mut params = ArrayVec::<_, 1>::new(); + if let Some(thread_id) = thread_id { + params.push(("thread_id", thread_id.to_string())); + } + self.fire(Request { body: None, multipart: None, @@ -2648,7 +2656,7 @@ impl Http { token, message_id, }, - params: thread_id.map(|thread_id| vec![("thread_id", thread_id.to_string())]), + params, }) .await } @@ -2663,6 +2671,11 @@ impl Http { map: &impl serde::Serialize, new_attachments: Vec, ) -> Result { + let mut params = ArrayVec::<_, 1>::new(); + if let Some(thread_id) = thread_id { + params.push(("thread_id", thread_id.to_string())); + } + let mut request = Request { body: None, multipart: None, @@ -2673,7 +2686,7 @@ impl Http { token, message_id, }, - params: thread_id.map(|thread_id| vec![("thread_id", thread_id.to_string())]), + params, }; if new_attachments.is_empty() { @@ -2697,6 +2710,11 @@ impl Http { token: &str, message_id: MessageId, ) -> Result<()> { + let mut params = ArrayVec::<_, 1>::new(); + if let Some(thread_id) = thread_id { + params.push(("thread_id", thread_id.to_string())); + } + self.wind(204, Request { body: None, multipart: None, @@ -2707,7 +2725,7 @@ impl Http { token, message_id, }, - params: thread_id.map(|thread_id| vec![("thread_id", thread_id.to_string())]), + params, }) .await } @@ -2729,7 +2747,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::StatusMaintenancesActive, - params: None, + params: [].into(), }) .await?; @@ -2752,7 +2770,7 @@ impl Http { target: Option, limit: Option, ) -> Result> { - let mut params = vec![]; + let mut params = ArrayVec::<_, 2>::new(); if let Some(limit) = limit { params.push(("limit", limit.to_string())); @@ -2773,7 +2791,7 @@ impl Http { route: Route::GuildBans { guild_id, }, - params: Some(params), + params, }) .await } @@ -2787,7 +2805,7 @@ impl Http { before: Option, limit: Option, ) -> Result { - let mut params = vec![]; + let mut params = ArrayVec::<_, 4>::new(); if let Some(action_type) = action_type { params.push(("action_type", action_type.num().to_string())); } @@ -2809,7 +2827,7 @@ impl Http { route: Route::GuildAuditLogs { guild_id, }, - params: Some(params), + params, }) .await } @@ -2826,7 +2844,7 @@ impl Http { route: Route::GuildAutomodRules { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -2844,7 +2862,7 @@ impl Http { guild_id, rule_id, }, - params: None, + params: [].into(), }) .await } @@ -2868,7 +2886,7 @@ impl Http { route: Route::GuildAutomodRules { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -2894,7 +2912,7 @@ impl Http { guild_id, rule_id, }, - params: None, + params: [].into(), }) .await } @@ -2917,7 +2935,7 @@ impl Http { guild_id, rule_id, }, - params: None, + params: [].into(), }) .await } @@ -2930,7 +2948,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::GatewayBot, - params: None, + params: [].into(), }) .await } @@ -2945,7 +2963,7 @@ impl Http { route: Route::ChannelInvites { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -2963,7 +2981,7 @@ impl Http { route: Route::ChannelThreadMembers { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -2978,7 +2996,7 @@ impl Http { route: Route::GuildThreadsActive { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -2990,7 +3008,7 @@ impl Http { before: Option, limit: Option, ) -> Result { - let mut params = vec![]; + let mut params = ArrayVec::<_, 2>::new(); if let Some(before) = before { params.push(("before", before.to_string())); } @@ -3006,7 +3024,7 @@ impl Http { route: Route::ChannelArchivedPublicThreads { channel_id, }, - params: Some(params), + params, }) .await } @@ -3018,7 +3036,7 @@ impl Http { before: Option, limit: Option, ) -> Result { - let mut params = vec![]; + let mut params = ArrayVec::<_, 2>::new(); if let Some(before) = before { params.push(("before", before.to_string())); } @@ -3034,7 +3052,7 @@ impl Http { route: Route::ChannelArchivedPrivateThreads { channel_id, }, - params: Some(params), + params, }) .await } @@ -3046,7 +3064,7 @@ impl Http { before: Option, limit: Option, ) -> Result { - let mut params = vec![]; + let mut params = ArrayVec::<_, 2>::new(); if let Some(before) = before { params.push(("before", before.to_string())); } @@ -3062,7 +3080,7 @@ impl Http { route: Route::ChannelJoinedPrivateThreads { channel_id, }, - params: Some(params), + params, }) .await } @@ -3077,7 +3095,7 @@ impl Http { route: Route::ChannelThreadMemberMe { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -3092,7 +3110,7 @@ impl Http { route: Route::ChannelThreadMemberMe { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -3112,7 +3130,7 @@ impl Http { channel_id, user_id, }, - params: None, + params: [].into(), }) .await } @@ -3132,7 +3150,7 @@ impl Http { channel_id, user_id, }, - params: None, + params: [].into(), }) .await } @@ -3152,7 +3170,7 @@ impl Http { channel_id, user_id, }, - params: Some(vec![("with_member", with_member.to_string())]), + params: [("with_member", with_member.to_string())].into(), }) .await } @@ -3186,7 +3204,7 @@ impl Http { route: Route::ChannelWebhooks { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -3201,7 +3219,7 @@ impl Http { route: Route::Channel { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -3216,7 +3234,7 @@ impl Http { route: Route::GuildChannels { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3231,7 +3249,7 @@ impl Http { route: Route::StageInstance { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -3250,7 +3268,7 @@ impl Http { users: Vec, } - let mut params = Vec::with_capacity(2); + let mut params = ArrayVec::<_, 2>::new(); if let Some(after) = after { params.push(("after", after.to_string())); } @@ -3270,7 +3288,7 @@ impl Http { message_id, answer_id, }, - params: Some(params), + params, }) .await?; @@ -3291,7 +3309,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }) .await } @@ -3306,7 +3324,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::Oauth2ApplicationCurrent, - params: None, + params: [].into(), }) .await } @@ -3319,7 +3337,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::UserMe, - params: None, + params: [].into(), }) .await } @@ -3334,7 +3352,7 @@ impl Http { route: Route::GuildEmojis { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3350,7 +3368,7 @@ impl Http { guild_id, emoji_id, }, - params: None, + params: [].into(), }) .await } @@ -3372,7 +3390,7 @@ impl Http { route: Route::Emojis { application_id: self.try_application_id()?, }, - params: None, + params: [].into(), }) .await?; @@ -3390,7 +3408,7 @@ impl Http { application_id: self.try_application_id()?, emoji_id, }, - params: None, + params: [].into(), }) .await } @@ -3407,7 +3425,7 @@ impl Http { guild_id: Option, exclude_ended: Option, ) -> Result> { - let mut params = vec![]; + let mut params = ArrayVec::<_, 7>::new(); if let Some(user_id) = user_id { params.push(("user_id", user_id.to_string())); } @@ -3441,7 +3459,7 @@ impl Http { route: Route::Entitlements { application_id: self.try_application_id()?, }, - params: Some(params), + params, }) .await } @@ -3454,7 +3472,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::Gateway, - params: None, + params: [].into(), }) .await } @@ -3469,7 +3487,7 @@ impl Http { route: Route::Commands { application_id: self.try_application_id()?, }, - params: None, + params: [].into(), }) .await } @@ -3484,7 +3502,7 @@ impl Http { route: Route::Commands { application_id: self.try_application_id()?, }, - params: Some(vec![("with_localizations", true.to_string())]), + params: [("with_localizations", String::from("true"))].into(), }) .await } @@ -3500,7 +3518,7 @@ impl Http { application_id: self.try_application_id()?, command_id, }, - params: None, + params: [].into(), }) .await } @@ -3515,7 +3533,7 @@ impl Http { route: Route::Guild { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3530,7 +3548,7 @@ impl Http { route: Route::Guild { guild_id, }, - params: Some(vec![("with_counts", true.to_string())]), + params: [("with_counts", String::from("true"))].into(), }) .await } @@ -3546,7 +3564,7 @@ impl Http { application_id: self.try_application_id()?, guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3566,7 +3584,7 @@ impl Http { application_id: self.try_application_id()?, guild_id, }, - params: Some(vec![("with_localizations", true.to_string())]), + params: [("with_localizations", String::from("true"))].into(), }) .await } @@ -3587,7 +3605,7 @@ impl Http { guild_id, command_id, }, - params: None, + params: [].into(), }) .await } @@ -3606,7 +3624,7 @@ impl Http { application_id: self.try_application_id()?, guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3627,7 +3645,7 @@ impl Http { guild_id, command_id, }, - params: None, + params: [].into(), }) .await } @@ -3644,7 +3662,7 @@ impl Http { route: Route::GuildWidget { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3659,7 +3677,7 @@ impl Http { route: Route::GuildPreview { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3674,7 +3692,7 @@ impl Http { route: Route::GuildWelcomeScreen { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3689,7 +3707,7 @@ impl Http { route: Route::GuildIntegrations { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3704,7 +3722,7 @@ impl Http { route: Route::GuildInvites { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3716,7 +3734,7 @@ impl Http { code: String, } - self.fire::(Request { + self.fire(Request { body: None, multipart: None, headers: None, @@ -3724,10 +3742,10 @@ impl Http { route: Route::GuildVanityUrl { guild_id, }, - params: None, + params: [].into(), }) .await - .map(|x| x.code) + .map(|x: GuildVanityUrl| x.code) } /// Gets the members of a guild. Optionally pass a `limit` and the Id of the user to offset the @@ -3744,8 +3762,8 @@ impl Http { } } - let mut params = - vec![("limit", limit.unwrap_or(constants::MEMBER_FETCH_LIMIT).to_string())]; + let mut params = ArrayVec::<_, 2>::new(); + params.push(("limit", limit.unwrap_or(constants::MEMBER_FETCH_LIMIT).to_string())); if let Some(after) = after { params.push(("after", after.to_string())); } @@ -3759,7 +3777,7 @@ impl Http { route: Route::GuildMembers { guild_id, }, - params: Some(params), + params, }) .await?; @@ -3784,7 +3802,7 @@ impl Http { route: Route::GuildPrune { guild_id, }, - params: Some(vec![("days", days.to_string())]), + params: [("days", days.to_string())].into(), }) .await } @@ -3800,7 +3818,7 @@ impl Http { route: Route::GuildRegions { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -3817,7 +3835,7 @@ impl Http { guild_id, role_id, }, - params: None, + params: [].into(), }) .await?; @@ -3839,7 +3857,7 @@ impl Http { route: Route::GuildRoles { guild_id, }, - params: None, + params: [].into(), }) .await?; @@ -3874,7 +3892,7 @@ impl Http { guild_id, event_id, }, - params: Some(vec![("with_user_count", with_user_count.to_string())]), + params: [("with_user_count", with_user_count.to_string())].into(), }) .await } @@ -3897,7 +3915,7 @@ impl Http { route: Route::GuildScheduledEvents { guild_id, }, - params: Some(vec![("with_user_count", with_user_count.to_string())]), + params: [("with_user_count", with_user_count.to_string())].into(), }) .await } @@ -3926,7 +3944,7 @@ impl Http { target: Option, with_member: Option, ) -> Result> { - let mut params = vec![]; + let mut params = ArrayVec::<_, 3>::new(); if let Some(limit) = limit { params.push(("limit", limit.to_string())); } @@ -3949,7 +3967,7 @@ impl Http { guild_id, event_id, }, - params: Some(params), + params, }) .await } @@ -3965,7 +3983,7 @@ impl Http { route: Route::GuildStickers { guild_id, }, - params: None, + params: [].into(), }) .await?; @@ -3996,7 +4014,7 @@ impl Http { guild_id, sticker_id, }, - params: None, + params: [].into(), }) .await?; @@ -4036,7 +4054,7 @@ impl Http { route: Route::GuildWebhooks { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -4072,7 +4090,7 @@ impl Http { target: Option, limit: Option, ) -> Result> { - let mut params = vec![]; + let mut params = ArrayVec::<_, 2>::new(); if let Some(limit) = limit { params.push(("limit", limit.to_string())); } @@ -4089,7 +4107,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::UserMeGuilds, - params: Some(params), + params, }) .await } @@ -4133,7 +4151,7 @@ impl Http { route: Route::UserMeGuildMember { guild_id, }, - params: None, + params: [].into(), }) .await?; @@ -4165,10 +4183,9 @@ impl Http { #[cfg(feature = "utils")] let code = crate::utils::parse_invite(code); - let mut params = vec![ - ("member_counts", member_counts.to_string()), - ("expiration", expiration.to_string()), - ]; + let mut params = ArrayVec::<_, 3>::new(); + params.push(("member_counts", member_counts.to_string())); + params.push(("expiration", expiration.to_string())); if let Some(event_id) = event_id { params.push(("event_id", event_id.to_string())); } @@ -4181,7 +4198,7 @@ impl Http { route: Route::Invite { code, }, - params: Some(params), + params, }) .await } @@ -4198,7 +4215,7 @@ impl Http { guild_id, user_id, }, - params: None, + params: [].into(), }) .await?; @@ -4224,7 +4241,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }) .await } @@ -4236,7 +4253,7 @@ impl Http { target: Option, limit: Option, ) -> Result> { - let mut params = vec![]; + let mut params = ArrayVec::<_, 2>::new(); if let Some(limit) = limit { params.push(("limit", limit.to_string())); } @@ -4256,7 +4273,7 @@ impl Http { route: Route::ChannelMessages { channel_id, }, - params: Some(params), + params, }) .await } @@ -4271,7 +4288,7 @@ impl Http { route: Route::StickerPack { sticker_pack_id, }, - params: None, + params: [].into(), }) .await } @@ -4283,16 +4300,16 @@ impl Http { sticker_packs: Vec, } - self.fire::(Request { + self.fire(Request { body: None, multipart: None, headers: None, method: LightMethod::Get, route: Route::StickerPacks, - params: None, + params: [].into(), }) .await - .map(|s| s.sticker_packs) + .map(|s: StickerPacks| s.sticker_packs) } /// Gets all pins of a channel. @@ -4305,7 +4322,7 @@ impl Http { route: Route::ChannelPins { channel_id, }, - params: None, + params: [].into(), }) .await } @@ -4319,7 +4336,8 @@ impl Http { limit: u8, after: Option, ) -> Result> { - let mut params = vec![("limit", limit.to_string())]; + let mut params = ArrayVec::<_, 2>::new(); + params.push(("limit", limit.to_string())); if let Some(after) = after { params.push(("after", after.to_string())); } @@ -4333,7 +4351,7 @@ impl Http { message_id, reaction: &reaction_type.as_data(), }, - params: Some(params), + params, }) .await } @@ -4348,7 +4366,7 @@ impl Http { route: Route::Skus { application_id: self.try_application_id()?, }, - params: None, + params: [].into(), }) .await } @@ -4363,7 +4381,7 @@ impl Http { route: Route::Sticker { sticker_id, }, - params: None, + params: [].into(), }) .await } @@ -4385,7 +4403,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::StatusIncidentsUnresolved, - params: None, + params: [].into(), }) .await?; @@ -4409,7 +4427,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::StatusMaintenancesUpcoming, - params: None, + params: [].into(), }) .await?; @@ -4426,7 +4444,7 @@ impl Http { route: Route::User { user_id, }, - params: None, + params: [].into(), }) .await } @@ -4443,7 +4461,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::UserMeConnections, - params: None, + params: [].into(), }) .await } @@ -4456,7 +4474,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::UserMeDmChannels, - params: None, + params: [].into(), }) .await } @@ -4469,7 +4487,7 @@ impl Http { headers: None, method: LightMethod::Get, route: Route::VoiceRegions, - params: None, + params: [].into(), }) .await } @@ -4503,7 +4521,7 @@ impl Http { route: Route::Webhook { webhook_id, }, - params: None, + params: [].into(), }) .await } @@ -4543,7 +4561,7 @@ impl Http { webhook_id, token, }, - params: None, + params: [].into(), }) .await } @@ -4580,7 +4598,7 @@ impl Http { webhook_id, token, }, - params: None, + params: [].into(), }) .await } @@ -4601,7 +4619,7 @@ impl Http { guild_id, user_id, }, - params: None, + params: [].into(), }) .await } @@ -4616,7 +4634,7 @@ impl Http { route: Route::UserMeGuild { guild_id, }, - params: None, + params: [].into(), }) .await } @@ -4640,7 +4658,7 @@ impl Http { route: Route::ChannelMessages { channel_id, }, - params: None, + params: [].into(), }; if files.is_empty() { @@ -4672,7 +4690,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }) .await } @@ -4693,7 +4711,7 @@ impl Http { guild_id, user_id, }, - params: None, + params: [].into(), }) .await } @@ -4720,7 +4738,7 @@ impl Http { user_id, role_id, }, - params: None, + params: [].into(), }) .await } @@ -4742,10 +4760,11 @@ impl Http { route: Route::GuildMembersSearch { guild_id, }, - params: Some(vec![ + params: [ ("query", query.to_string()), ("limit", limit.unwrap_or(constants::MEMBER_FETCH_LIMIT).to_string()), - ]), + ] + .into(), }) .await?; @@ -4775,7 +4794,7 @@ impl Http { route: Route::GuildPrune { guild_id, }, - params: Some(vec![("days", days.to_string())]), + params: [("days", days.to_string())].into(), }) .await } @@ -4795,7 +4814,7 @@ impl Http { guild_id, integration_id, }, - params: None, + params: [].into(), }) .await } @@ -4856,7 +4875,7 @@ impl Http { channel_id, message_id, }, - params: None, + params: [].into(), }) .await } @@ -4886,9 +4905,9 @@ impl Http { /// let channel_id = ChannelId::new(381880193700069377); /// let route = Route::ChannelMessages { channel_id }; /// - /// let mut request = Request::new(route, LightMethod::Post).body(Some(bytes)); + /// let mut request = Request::new(route, LightMethod::Post, []).body(Some(bytes)); /// - /// let message = http.fire::(request).await?; + /// let message: Message = http.fire(request).await?; /// /// println!("Message content: {}", message.content); /// # Ok(()) @@ -4898,7 +4917,10 @@ impl Http { /// # Errors /// /// If there is an error, it will be either [`Error::Http`] or [`Error::Json`]. - pub async fn fire(&self, req: Request<'_>) -> Result { + pub async fn fire( + &self, + req: Request<'_, MAX_PARAMS>, + ) -> Result { let response = self.request(req).await?; decode_resp(response).await } @@ -4926,7 +4948,7 @@ impl Http { /// let channel_id = ChannelId::new(381880193700069377); /// let route = Route::ChannelMessages { channel_id }; /// - /// let mut request = Request::new(route, LightMethod::Post).body(Some(bytes)); + /// let mut request = Request::new(route, LightMethod::Post, []).body(Some(bytes)); /// /// let response = http.request(request).await?; /// @@ -4935,7 +4957,10 @@ impl Http { /// # } /// ``` #[instrument] - pub async fn request(&self, req: Request<'_>) -> Result { + pub async fn request( + &self, + req: Request<'_, MAX_PARAMS>, + ) -> Result { let method = req.method.reqwest_method(); let response = if let Some(ratelimiter) = &self.ratelimiter { ratelimiter.perform(req).await? @@ -4958,7 +4983,11 @@ impl Http { /// /// This is a function that performs a light amount of work and returns an empty tuple, so it's /// called "self.wind" to denote that it's lightweight. - pub(super) async fn wind(&self, expected: u16, req: Request<'_>) -> Result<()> { + pub(super) async fn wind( + &self, + expected: u16, + req: Request<'_, MAX_PARAMS>, + ) -> Result<()> { let route = req.route; let method = req.method.reqwest_method(); let response = self.request(req).await?; diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs index 6b595294846..c4a9f3e2748 100644 --- a/src/http/ratelimiting.rs +++ b/src/http/ratelimiting.rs @@ -179,7 +179,10 @@ impl Ratelimiter { /// /// Only error kind that may be returned is [`Error::Http`]. #[instrument] - pub async fn perform(&self, req: Request<'_>) -> Result { + pub async fn perform( + &self, + req: Request<'_, MAX_PARAMS>, + ) -> Result { loop { // This will block if another thread hit the global ratelimit. drop(self.global.lock().await); @@ -278,9 +281,9 @@ pub struct Ratelimit { impl Ratelimit { #[instrument(skip(ratelimit_callback))] - pub async fn pre_hook( + pub async fn pre_hook( &mut self, - req: &Request<'_>, + req: &Request<'_, MAX_PARAMS>, ratelimit_callback: &(dyn Fn(RatelimitInfo) + Send + Sync), ) { if self.limit() == 0 { @@ -324,10 +327,10 @@ impl Ratelimit { } #[instrument(skip(ratelimit_callback))] - pub async fn post_hook( + pub async fn post_hook( &mut self, response: &Response, - req: &Request<'_>, + req: &Request<'_, MAX_PARAMS>, ratelimit_callback: &(dyn Fn(RatelimitInfo) + Send + Sync), absolute_ratelimits: bool, ) -> Result { diff --git a/src/http/request.rs b/src/http/request.rs index 0419116fb6b..80235c37928 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -1,5 +1,6 @@ use std::fmt::Write; +use arrayvec::ArrayVec; use reqwest::header::{ HeaderMap as Headers, HeaderValue, @@ -18,28 +19,32 @@ use crate::constants; use crate::internal::prelude::*; #[deprecated = "use Request directly now"] -pub type RequestBuilder<'a> = Request<'a>; +pub type RequestBuilder<'a, const MAX_PARAMS: usize> = Request<'a, MAX_PARAMS>; #[derive(Clone, Debug)] #[must_use] -pub struct Request<'a> { +pub struct Request<'a, const MAX_PARAMS: usize> { pub(super) body: Option>, pub(super) multipart: Option, pub(super) headers: Option, pub(super) method: LightMethod, pub(super) route: Route<'a>, - pub(super) params: Option>, + pub(super) params: ArrayVec<(&'static str, String), MAX_PARAMS>, } -impl<'a> Request<'a> { - pub const fn new(route: Route<'a>, method: LightMethod) -> Self { +impl<'a, const MAX_PARAMS: usize> Request<'a, MAX_PARAMS> { + pub fn new( + route: Route<'a>, + method: LightMethod, + params: [(&'static str, String); MAX_PARAMS], + ) -> Self { Self { body: None, multipart: None, headers: None, method, route, - params: None, + params: params.into(), } } @@ -58,8 +63,8 @@ impl<'a> Request<'a> { self } - pub fn params(mut self, params: Option>) -> Self { - self.params = params; + pub fn params(mut self, params: [(&'static str, String); MAX_PARAMS]) -> Self { + self.params = params.into(); self } @@ -77,9 +82,9 @@ impl<'a> Request<'a> { path = path.replace("https://discord.com", proxy.trim_end_matches('/')); } - if let Some(params) = self.params { + if !self.params.is_empty() { path += "?"; - for (param, value) in params { + for (param, value) in self.params { write!(path, "&{param}={value}").unwrap(); } } @@ -138,11 +143,19 @@ impl<'a> Request<'a> { #[must_use] pub fn params_ref(&self) -> Option<&[(&'static str, String)]> { - self.params.as_deref() + if self.params.is_empty() { + None + } else { + Some(&self.params) + } } #[must_use] pub fn params_mut(&mut self) -> Option<&mut [(&'static str, String)]> { - self.params.as_deref_mut() + if self.params.is_empty() { + None + } else { + Some(&mut self.params) + } } } From 401b369049e4cbc2ec6b80b06576f3a71f19902d Mon Sep 17 00:00:00 2001 From: Gnome! Date: Sun, 3 Dec 2023 19:33:06 +0000 Subject: [PATCH 002/159] Remove deprecated items (#2645) --- src/collector.rs | 14 ----- src/http/request.rs | 3 - src/model/channel/guild_channel.rs | 26 --------- src/model/channel/mod.rs | 4 -- src/model/event.rs | 1 - src/model/gateway.rs | 15 ----- src/model/guild/mod.rs | 84 --------------------------- src/model/guild/partial_guild.rs | 11 ---- src/model/permissions.rs | 9 --- src/model/sticker.rs | 39 ------------- src/utils/argument_convert/channel.rs | 2 +- src/utils/argument_convert/member.rs | 2 +- src/utils/argument_convert/role.rs | 2 +- src/utils/argument_convert/user.rs | 2 +- src/utils/mod.rs | 48 ++++++--------- 15 files changed, 22 insertions(+), 240 deletions(-) diff --git a/src/collector.rs b/src/collector.rs index 5a1a9abaf88..232e50e6c38 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -1,6 +1,3 @@ -// Or we'll get deprecation warnings from our own deprecated type (seriously Rust?) -#![allow(deprecated)] - use futures::future::pending; use futures::{Stream, StreamExt as _}; @@ -127,12 +124,6 @@ macro_rules! make_specific_collector { stream.take_until(Box::pin(timeout)) } - /// Deprecated, use [`Self::stream()`] instead. - #[deprecated = "use `.stream()` instead"] - pub fn build(self) -> impl Stream { - self.stream() - } - #[doc = concat!("Returns the next [`", stringify!($item_type), "`] which passes the filters.")] #[doc = concat!("You can also call `.await` on the [`", stringify!($collector_type), "`] directly.")] pub async fn next(self) -> Option<$item_type> { @@ -195,8 +186,3 @@ make_specific_collector!( channel_id: ChannelId => message.channel_id == *channel_id, guild_id: GuildId => message.guild_id.map_or(true, |g| g == *guild_id), ); -make_specific_collector!( - #[deprecated = "prefer the stand-alone collect() function to collect arbitrary events"] - EventCollector, Event, - event => event, -); diff --git a/src/http/request.rs b/src/http/request.rs index 80235c37928..a051ef22e11 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -18,9 +18,6 @@ use super::{HttpError, LightMethod}; use crate::constants; use crate::internal::prelude::*; -#[deprecated = "use Request directly now"] -pub type RequestBuilder<'a, const MAX_PARAMS: usize> = Request<'a, MAX_PARAMS>; - #[derive(Clone, Debug)] #[must_use] pub struct Request<'a, const MAX_PARAMS: usize> { diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index 34b02c49953..1caaa8fa690 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -774,32 +774,6 @@ impl GuildChannel { Ok(guild.user_permissions_in(self, member)) } - /// Calculates the permissions of a role. - /// - /// The Id of the argument must be a [`Role`] of the [`Guild`] that the channel is in. - /// - /// # Errors - /// - /// Returns a [`ModelError::GuildNotFound`] if the channel's guild could not be found in the - /// [`Cache`]. - /// - /// Returns a [`ModelError::RoleNotFound`] if the given role could not be found in the - /// [`Cache`]. - #[cfg(feature = "cache")] - #[inline] - #[deprecated = "this function ignores other roles the user may have as well as user-specific permissions; use Guild::user_permissions_in instead"] - #[allow(deprecated)] - pub fn permissions_for_role( - &self, - cache: impl AsRef, - role_id: impl Into, - ) -> Result { - let guild = self.guild(&cache).ok_or(Error::Model(ModelError::GuildNotFound))?; - let role = - guild.roles.get(&role_id.into()).ok_or(Error::Model(ModelError::RoleNotFound))?; - guild.role_permissions_in(self, role) - } - /// Pins a [`Message`] to the channel. /// /// **Note**: Requires the [Manage Messages] permission. diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index fc36df27605..821a5b60c15 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -28,10 +28,6 @@ use crate::json::*; use crate::model::prelude::*; use crate::model::utils::is_false; -#[deprecated = "use CreateAttachment instead"] -#[cfg(feature = "model")] -pub type AttachmentType<'a> = crate::builder::CreateAttachment; - /// A container for any channel. #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Serialize)] diff --git a/src/model/event.rs b/src/model/event.rs index 10e67420756..5c8e019ab87 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -546,7 +546,6 @@ impl MessageUpdateEvent { pub fn apply_to_message(&self, message: &mut Message) { // Destructure, so we get an `unused` warning when we forget to process one of the fields // in this method - #[allow(deprecated)] // yes rust, exhaustive means exhaustive, even the deprecated ones let Self { id, channel_id, diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 2c54eba7026..877b5fc948f 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -487,9 +487,6 @@ bitflags! { /// - GUILD_BAN_ADD /// - GUILD_BAN_REMOVE const GUILD_MODERATION = 1 << 2; - /// Backwards compatibility with old gateway event name. Same as GUILD_MODERATION - #[deprecated = "Use [`Self::GUILD_MODERATION`] instead"] - const GUILD_BANS = 1 << 2; /// Enables the following gateway events: /// - GUILD_EMOJIS_UPDATE @@ -631,18 +628,6 @@ impl GatewayIntents { self.contains(Self::GUILD_MEMBERS) } - /// Shorthand for checking that the set of intents contains the [GUILD_BANS] intent. - /// - /// [GUILD_BANS]: Self::GUILD_BANS - /// - /// This is the same as calling guild_moderation since Discord changed the name - #[must_use] - #[deprecated = "Use [`Self::guild_moderation`] instead"] - pub const fn guild_bans(self) -> bool { - #[allow(deprecated)] // this is a deprecated method itself - self.contains(Self::GUILD_BANS) - } - /// Shorthand for checking that the set of intents contains the [GUILD_MODERATION] intent. /// /// [GUILD_MODERATION]: Self::GUILD_MODERATION diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 5100a429963..130ac707780 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -2083,49 +2083,6 @@ impl Guild { }) } - /// Calculate a [`Role`]'s permissions in a given channel in the guild. - /// - /// # Errors - /// - /// Will return an [`Error::Model`] if the [`Role`] or [`Channel`] is not from this [`Guild`]. - #[inline] - #[deprecated = "this function ignores other roles the user may have as well as user-specific permissions; use user_permissions_in instead"] - pub fn role_permissions_in(&self, channel: &GuildChannel, role: &Role) -> Result { - Self::role_permissions_in_(channel, role, self.id) - } - - /// Helper function that can also be used from [`PartialGuild`]. - pub(crate) fn role_permissions_in_( - channel: &GuildChannel, - role: &Role, - guild_id: GuildId, - ) -> Result { - // Fail if the role or channel is not from this guild. - if role.guild_id != guild_id || channel.guild_id != guild_id { - return Err(Error::Model(ModelError::WrongGuild)); - } - - let mut permissions = role.permissions; - - if permissions.contains(Permissions::ADMINISTRATOR) { - return Ok(Self::remove_unnecessary_voice_permissions(channel, Permissions::all())); - } - - for overwrite in &channel.permission_overwrites { - if let PermissionOverwriteType::Role(permissions_role_id) = overwrite.kind { - if permissions_role_id == role.id { - permissions = (permissions & !overwrite.deny) | overwrite.allow; - - break; - } - } - } - - Self::remove_unusable_permissions(&mut permissions); - - Ok(permissions) - } - /// Retrieves the count of the number of [`Member`]s that would be pruned with the number of /// given days. /// @@ -2155,47 +2112,6 @@ impl Guild { self.id.prune_count(cache_http.http(), days).await } - pub(crate) fn remove_unusable_permissions(permissions: &mut Permissions) { - // No SEND_MESSAGES => no message-sending-related actions - // If the member does not have the `SEND_MESSAGES` permission, then throw out message-able - // permissions. - if !permissions.contains(Permissions::SEND_MESSAGES) { - *permissions &= !(Permissions::SEND_TTS_MESSAGES - | Permissions::MENTION_EVERYONE - | Permissions::EMBED_LINKS - | Permissions::ATTACH_FILES); - } - - // If the permission does not have the `VIEW_CHANNEL` permission, then throw out actionable - // permissions. - if !permissions.contains(Permissions::VIEW_CHANNEL) { - *permissions &= !(Permissions::KICK_MEMBERS - | Permissions::BAN_MEMBERS - | Permissions::ADMINISTRATOR - | Permissions::MANAGE_GUILD - | Permissions::CHANGE_NICKNAME - | Permissions::MANAGE_NICKNAMES); - } - } - - pub(crate) fn remove_unnecessary_voice_permissions( - channel: &GuildChannel, - mut permissions: Permissions, - ) -> Permissions { - // If this is a text channel, then throw out voice permissions. - if channel.kind == ChannelType::Text { - permissions &= !(Permissions::CONNECT - | Permissions::SPEAK - | Permissions::MUTE_MEMBERS - | Permissions::DEAFEN_MEMBERS - | Permissions::MOVE_MEMBERS - | Permissions::USE_VAD - | Permissions::STREAM); - } - - permissions - } - /// Re-orders the channels of the guild. /// /// Although not required, you should specify all channels' positions, regardless of whether diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index e0cf38a5bd8..c9c5cc9a8ef 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -1349,17 +1349,6 @@ impl PartialGuild { ) } - /// Calculate a [`Role`]'s permissions in a given channel in the guild. - /// - /// # Errors - /// - /// Returns [`Error::Model`] if the [`Role`] or [`Channel`] is not from this [`Guild`]. - #[inline] - #[deprecated = "this function ignores other roles the user may have as well as user-specific permissions; use user_permissions_in instead"] - pub fn role_permissions_in(&self, channel: &GuildChannel, role: &Role) -> Result { - Guild::role_permissions_in_(channel, role, self.id) - } - /// Gets the number of [`Member`]s that would be pruned with the given number of days. /// /// Requires the [Kick Members] permission. diff --git a/src/model/permissions.rs b/src/model/permissions.rs index 85bcd2d2546..5a39b1fe3df 100644 --- a/src/model/permissions.rs +++ b/src/model/permissions.rs @@ -322,8 +322,6 @@ bitflags::bitflags! { /// Allows for editing and deleting emojis, stickers, and soundboard sounds created by all /// users. const MANAGE_GUILD_EXPRESSIONS = 1 << 30; - #[deprecated = "use `Permissions::MANAGE_GUILD_EXPRESSIONS` instead"] - const MANAGE_EMOJIS_AND_STICKERS = 1 << 30; /// Allows members to use application commands, including slash commands and context menu /// commands. const USE_APPLICATION_COMMANDS = 1 << 31; @@ -621,13 +619,6 @@ impl Permissions { self.contains(Self::MANAGE_CHANNELS) } - #[deprecated = "use `manage_guild_expressions` instead"] - #[must_use] - pub const fn manage_emojis_and_stickers(self) -> bool { - #[allow(deprecated)] - self.contains(Self::MANAGE_EMOJIS_AND_STICKERS) - } - /// Shorthand for checking that the set of permissions contains the [Manage Events] permission. /// /// [Manage Events]: Self::MANAGE_EVENTS diff --git a/src/model/sticker.rs b/src/model/sticker.rs index 7aee60ba0db..5b0dd9963ea 100644 --- a/src/model/sticker.rs +++ b/src/model/sticker.rs @@ -22,23 +22,6 @@ impl StickerPackId { #[cfg(feature = "model")] impl StickerId { - /// Delete a guild sticker. - /// - /// **Note**: If the sticker was created by the current user, requires either the [Create Guild - /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild - /// Expressions] permission is required. - /// - /// # Errors - /// - /// Returns [`Error::Http`] if the current user lacks permission. - /// - /// [Create Guild Expressions]: Permissions::CREATE_GUILD_EXPRESSIONS - /// [Manage Guild Expressions]: Permissions::MANAGE_GUILD_EXPRESSIONS - #[deprecated = "use `GuildId::delete_sticker` instead"] - pub async fn delete(self, http: impl AsRef, guild_id: impl Into) -> Result<()> { - guild_id.into().delete_sticker(http, self).await - } - /// Requests the sticker via the REST API to get a [`Sticker`] with all details. /// /// # Errors @@ -48,28 +31,6 @@ impl StickerId { pub async fn to_sticker(self, http: impl AsRef) -> Result { http.as_ref().get_sticker(self).await } - - /// Edits the sticker. - /// - /// **Note**: If the sticker was created by the current user, requires either the [Create Guild - /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild - /// Expressions] permission is required. - /// - /// # Errors - /// - /// Returns [`Error::Http`] if the current user lacks permission, or if invalid data is given. - /// - /// [Create Guild Expressions]: Permissions::CREATE_GUILD_EXPRESSIONS - /// [Manage Guild Expressions]: Permissions::MANAGE_GUILD_EXPRESSIONS - #[deprecated = "use `GuildId::edit_sticker` instead"] - pub async fn edit( - self, - cache_http: impl CacheHttp, - guild_id: impl Into, - builder: EditSticker<'_>, - ) -> Result { - guild_id.into().edit_sticker(cache_http, self, builder).await - } } /// The smallest amount of data required to render a sticker. diff --git a/src/utils/argument_convert/channel.rs b/src/utils/argument_convert/channel.rs index f31cfacb9de..f5bf17534f1 100644 --- a/src/utils/argument_convert/channel.rs +++ b/src/utils/argument_convert/channel.rs @@ -79,7 +79,7 @@ async fn lookup_channel_global( /// /// The lookup strategy is as follows (in order): /// 1. Lookup by ID. -/// 2. [Lookup by mention](`crate::utils::parse_channel`). +/// 2. [Lookup by mention](`crate::utils::parse_channel_mention`). /// 3. Lookup by name. #[async_trait::async_trait] impl ArgumentConvert for Channel { diff --git a/src/utils/argument_convert/member.rs b/src/utils/argument_convert/member.rs index bcc6988e768..c589f165990 100644 --- a/src/utils/argument_convert/member.rs +++ b/src/utils/argument_convert/member.rs @@ -35,7 +35,7 @@ impl fmt::Display for MemberParseError { /// /// The lookup strategy is as follows (in order): /// 1. Lookup by ID. -/// 2. [Lookup by mention](`crate::utils::parse_username`). +/// 2. [Lookup by mention](`crate::utils::parse_user_mention`). /// 3. [Lookup by name#discrim](`crate::utils::parse_user_tag`). /// 4. Lookup by name /// 5. Lookup by nickname diff --git a/src/utils/argument_convert/role.rs b/src/utils/argument_convert/role.rs index e446ef5d68b..6ea6807c2a1 100644 --- a/src/utils/argument_convert/role.rs +++ b/src/utils/argument_convert/role.rs @@ -45,7 +45,7 @@ impl fmt::Display for RoleParseError { /// /// The lookup strategy is as follows (in order): /// 1. Lookup by ID -/// 2. [Lookup by mention](`crate::utils::parse_role`). +/// 2. [Lookup by mention](`crate::utils::parse_role_mention`). /// 3. Lookup by name (case-insensitive) #[async_trait::async_trait] impl ArgumentConvert for Role { diff --git a/src/utils/argument_convert/user.rs b/src/utils/argument_convert/user.rs index b99ab60e779..fb9a00073a6 100644 --- a/src/utils/argument_convert/user.rs +++ b/src/utils/argument_convert/user.rs @@ -59,7 +59,7 @@ fn lookup_by_global_cache(ctx: impl CacheHttp, s: &str) -> Option { /// /// The lookup strategy is as follows (in order): /// 1. Lookup by ID. -/// 2. [Lookup by mention](`crate::utils::parse_username`). +/// 2. [Lookup by mention](`crate::utils::parse_user_mention`). /// 3. [Lookup by name#discrim](`crate::utils::parse_user_tag`). /// 4. Lookup by name #[async_trait::async_trait] diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 324376bfe87..367c28bd9f8 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -113,22 +113,22 @@ pub fn parse_user_tag(s: &str) -> Option<(&str, Option)> { /// /// ```rust /// use serenity::model::id::UserId; -/// use serenity::utils::parse_username; +/// use serenity::utils::parse_user_mention; /// /// // regular username mention -/// assert_eq!(parse_username("<@114941315417899012>"), Some(UserId::new(114941315417899012))); +/// assert_eq!(parse_user_mention("<@114941315417899012>"), Some(UserId::new(114941315417899012))); /// /// // nickname mention -/// assert_eq!(parse_username("<@!114941315417899012>"), Some(UserId::new(114941315417899012))); +/// assert_eq!(parse_user_mention("<@!114941315417899012>"), Some(UserId::new(114941315417899012))); /// ``` /// /// Asserting that an invalid username or nickname mention returns [`None`]: /// /// ```rust -/// use serenity::utils::parse_username; +/// use serenity::utils::parse_user_mention; /// -/// assert!(parse_username("<@1149413154aa17899012").is_none()); -/// assert!(parse_username("<@!11494131541789a90b1c2").is_none()); +/// assert!(parse_user_mention("<@1149413154aa17899012").is_none()); +/// assert!(parse_user_mention("<@!11494131541789a90b1c2").is_none()); /// ``` /// /// [`User`]: crate::model::user::User @@ -148,11 +148,6 @@ pub fn parse_user_mention(mention: &str) -> Option { } } -#[deprecated = "use `utils::parse_user_mention` instead"] -pub fn parse_username(mention: impl AsRef) -> Option { - parse_user_mention(mention.as_ref()) -} - /// Retrieves an Id from a role mention. /// /// If the mention is invalid, then [`None`] is returned. @@ -163,17 +158,17 @@ pub fn parse_username(mention: impl AsRef) -> Option { /// /// ```rust /// use serenity::model::id::RoleId; -/// use serenity::utils::parse_role; +/// use serenity::utils::parse_role_mention; /// -/// assert_eq!(parse_role("<@&136107769680887808>"), Some(RoleId::new(136107769680887808))); +/// assert_eq!(parse_role_mention("<@&136107769680887808>"), Some(RoleId::new(136107769680887808))); /// ``` /// /// Asserting that an invalid role mention returns [`None`]: /// /// ```rust -/// use serenity::utils::parse_role; +/// use serenity::utils::parse_role_mention; /// -/// assert!(parse_role("<@&136107769680887808").is_none()); +/// assert!(parse_role_mention("<@&136107769680887808").is_none()); /// ``` /// /// [`Role`]: crate::model::guild::Role @@ -191,11 +186,6 @@ pub fn parse_role_mention(mention: &str) -> Option { } } -#[deprecated = "use `utils::parse_role_mention` instead"] -pub fn parse_role(mention: impl AsRef) -> Option { - parse_role_mention(mention.as_ref()) -} - /// Retrieves an Id from a channel mention. /// /// If the channel mention is invalid, then [`None`] is returned. @@ -206,18 +196,21 @@ pub fn parse_role(mention: impl AsRef) -> Option { /// /// ```rust /// use serenity::model::id::ChannelId; -/// use serenity::utils::parse_channel; +/// use serenity::utils::parse_channel_mention; /// -/// assert_eq!(parse_channel("<#81384788765712384>"), Some(ChannelId::new(81384788765712384))); +/// assert_eq!( +/// parse_channel_mention("<#81384788765712384>"), +/// Some(ChannelId::new(81384788765712384)) +/// ); /// ``` /// /// Asserting that an invalid channel mention returns [`None`]: /// /// ```rust -/// use serenity::utils::parse_channel; +/// use serenity::utils::parse_channel_mention; /// -/// assert!(parse_channel("<#!81384788765712384>").is_none()); -/// assert!(parse_channel("<#81384788765712384").is_none()); +/// assert!(parse_channel_mention("<#!81384788765712384>").is_none()); +/// assert!(parse_channel_mention("<#81384788765712384").is_none()); /// ``` /// /// [`Channel`]: crate::model::channel::Channel @@ -235,11 +228,6 @@ pub fn parse_channel_mention(mention: &str) -> Option { } } -#[deprecated = "use `utils::parse_channel_mention` instead"] -pub fn parse_channel(mention: impl AsRef) -> Option { - parse_channel_mention(mention.as_ref()) -} - /// Retrieves the animated state, name and Id from an emoji mention, in the form of an /// [`EmojiIdentifier`]. /// From 4fa51eaae26e07036f6ad18c603e47713d0b74b1 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Fri, 8 Dec 2023 11:01:44 +0000 Subject: [PATCH 003/159] Remove more deprecated items (#2651) --- src/model/guild/emoji.rs | 119 ------------------------------- src/model/guild/guild_id.rs | 2 - src/model/guild/mod.rs | 2 - src/model/guild/partial_guild.rs | 2 - src/model/guild/role.rs | 24 ------- 5 files changed, 149 deletions(-) diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs index 982323df10c..a9572c41a20 100644 --- a/src/model/guild/emoji.rs +++ b/src/model/guild/emoji.rs @@ -1,18 +1,8 @@ use std::fmt; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::cache::Cache; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::http::CacheHttp; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::internal::prelude::*; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::model::id::GuildId; use crate::model::id::{EmojiId, RoleId}; use crate::model::user::User; use crate::model::utils::default_true; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::model::ModelError; /// Represents a custom guild emoji, which can either be created using the API, or via an /// integration. Emojis created using the API only work within the guild it was created in. @@ -54,115 +44,6 @@ pub struct Emoji { #[cfg(feature = "model")] impl Emoji { - /// Deletes the emoji. This method requires the cache to fetch the guild ID. - /// - /// **Note**: If the emoji was created by the current user, requires either the [Create Guild - /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild - /// Expressions] permission is required. - /// - /// # Examples - /// - /// Delete a given emoji: - /// - /// ```rust,no_run - /// # use serenity::client::Context; - /// # use serenity::model::prelude::Emoji; - /// # - /// # async fn example(ctx: &Context, emoji: Emoji) -> Result<(), Box> { - /// // assuming emoji has been set already - /// match emoji.delete(&ctx).await { - /// Ok(()) => println!("Emoji deleted."), - /// Err(_) => println!("Could not delete emoji."), - /// } - /// # Ok(()) - /// # } - /// ``` - /// - /// # Errors - /// - /// Returns [`Error::Http`] if the current user lacks permission, or may return - /// [`ModelError::ItemMissing`] if the emoji is not in the cache. - /// - /// [Create Guild Expressions]: crate::model::Permissions::CREATE_GUILD_EXPRESSIONS - /// [Manage Guild Expressions]: crate::model::Permissions::MANAGE_GUILD_EXPRESSIONS - #[deprecated = "Use Guild(Id)::delete_emoji, this performs a loop over all guilds!"] - #[cfg(feature = "cache")] - #[allow(deprecated)] - #[inline] - pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<()> { - let guild_id = self.try_find_guild_id(&cache_http)?; - guild_id.delete_emoji(cache_http.http(), self).await - } - - /// Edits the emoji by updating it with a new name. This method requires the cache to fetch the - /// guild ID. - /// - /// **Note**: If the emoji was created by the current user, requires either the [Create Guild - /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild - /// Expressions] permission is required. - /// - /// # Errors - /// - /// Returns [`Error::Http`] if the current user lacks permission, or if an invalid name is - /// given. - /// - /// [Create Guild Expressions]: crate::model::Permissions::CREATE_GUILD_EXPRESSIONS - /// [Manage Guild Expressions]: crate::model::Permissions::MANAGE_GUILD_EXPRESSIONS - #[deprecated = "Use Guild(Id)::edit_emoji, this performs a loop over all guilds!"] - #[cfg(feature = "cache")] - #[allow(deprecated)] - pub async fn edit(&mut self, cache_http: impl CacheHttp, name: &str) -> Result<()> { - let guild_id = self.try_find_guild_id(&cache_http)?; - *self = guild_id.edit_emoji(cache_http.http(), self.id, name).await?; - Ok(()) - } - - /// Finds the [`Guild`] that owns the emoji by looking through the Cache. - /// - /// [`Guild`]: super::Guild - /// - /// # Examples - /// - /// Print the guild id that owns this emoji: - /// - /// ```rust,no_run - /// # use serenity::cache::Cache; - /// # use serenity::model::guild::Emoji; - /// # - /// # fn run(cache: Cache, emoji: Emoji) { - /// // assuming emoji has been set already - /// if let Some(guild_id) = emoji.find_guild_id(&cache) { - /// println!("{} is owned by {}", emoji.name, guild_id); - /// } - /// # } - /// ``` - #[deprecated = "This performs a loop over all guilds and should not be used."] - #[cfg(feature = "cache")] - #[allow(deprecated)] - #[must_use] - pub fn find_guild_id(&self, cache: impl AsRef) -> Option { - for guild_entry in cache.as_ref().guilds.iter() { - let guild = guild_entry.value(); - - if guild.emojis.contains_key(&self.id) { - return Some(guild.id); - } - } - - None - } - - #[deprecated = "This performs a loop over all guilds and should not be used."] - #[cfg(feature = "cache")] - #[allow(deprecated)] - #[inline] - fn try_find_guild_id(&self, cache_http: impl CacheHttp) -> Result { - cache_http - .cache() - .and_then(|c| self.find_guild_id(c)) - .ok_or(Error::Model(ModelError::ItemMissing)) - } - /// Generates a URL to the emoji's image. /// /// # Examples diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs index 165e9e90249..697c4bd1d3c 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -632,8 +632,6 @@ impl GuildId { /// Edits an [`Emoji`]'s name in the guild. /// - /// Also see [`Emoji::edit`] if you have the `cache` and `methods` features enabled. - /// /// **Note**: If the emoji was created by the current user, requires either the [Create Guild /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild /// Expressions] permission is required. diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 130ac707780..9779136f991 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -1139,8 +1139,6 @@ impl Guild { /// Edits an [`Emoji`]'s name in the guild. /// - /// Also see [`Emoji::edit`] if you have the `cache` and `model` features enabled. - /// /// **Note**: If the emoji was created by the current user, requires either the [Create Guild /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild /// Expressions] permission is required. diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index c9c5cc9a8ef..fac1277010a 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -784,8 +784,6 @@ impl PartialGuild { /// Edits an [`Emoji`]'s name in the guild. /// - /// Also see [`Emoji::edit`] if you have the `cache` and `methods` features enabled. - /// /// **Note**: If the emoji was created by the current user, requires either the [Create Guild /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild /// Expressions] permission is required. diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs index 76e82e4ba16..3b8528738f9 100644 --- a/src/model/guild/role.rs +++ b/src/model/guild/role.rs @@ -3,8 +3,6 @@ use std::fmt; #[cfg(feature = "model")] use crate::builder::EditRole; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::cache::Cache; #[cfg(feature = "model")] use crate::http::Http; #[cfg(all(feature = "cache", feature = "model"))] @@ -177,28 +175,6 @@ impl PartialOrd for Role { } } -#[cfg(feature = "model")] -impl RoleId { - /// Tries to find the [`Role`] by its Id in the cache. - #[cfg(feature = "cache")] - #[deprecated = "Use Guild::roles. This performs a loop over the entire cache!"] - pub fn to_role_cached(self, cache: impl AsRef) -> Option { - for guild_entry in cache.as_ref().guilds.iter() { - let guild = guild_entry.value(); - - if !guild.roles.contains_key(&self) { - continue; - } - - if let Some(role) = guild.roles.get(&self) { - return Some(role.clone()); - } - } - - None - } -} - impl From for RoleId { /// Gets the Id of a role. fn from(role: Role) -> RoleId { From cc1bd45ad126cc6f6fee9b80798a95055edfe503 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Fri, 8 Dec 2023 11:12:02 +0000 Subject: [PATCH 004/159] Remove `*_arc` methods (#2654) These are unnecessary. Accepting `impl Into>` allows passing either `T` or `Arc`. --- src/client/mod.rs | 40 +++++++--------------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index ed5981c17e8..0abd23ad83e 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -204,31 +204,12 @@ impl ClientBuilder { /// Sets the voice gateway handler to be used. It will receive voice events sent over the /// gateway and then consider - based on its settings - whether to dispatch a command. - /// - /// *Info*: If a reference to the voice_manager is required for manual dispatch, use the - /// [`Self::voice_manager_arc`]-method instead. #[cfg(feature = "voice")] - pub fn voice_manager(mut self, voice_manager: V) -> Self + pub fn voice_manager(mut self, voice_manager: impl Into>) -> Self where V: VoiceGatewayManager + 'static, { - self.voice_manager = Some(Arc::new(voice_manager)); - - self - } - - /// This method allows to pass an [`Arc`]'ed `voice_manager` - this step is done for you in the - /// [`voice_manager`]-method, if you don't need the extra control. You can provide a clone and - /// keep the original to manually dispatch. - /// - /// [`voice_manager`]: Self::voice_manager - #[cfg(feature = "voice")] - pub fn voice_manager_arc( - mut self, - voice_manager: Arc, - ) -> Self { - self.voice_manager = Some(voice_manager); - + self.voice_manager = Some(voice_manager.into()); self } @@ -270,18 +251,11 @@ impl ClientBuilder { } /// Adds an event handler with multiple methods for each possible event. - pub fn event_handler(mut self, event_handler: H) -> Self { - self.event_handlers.push(Arc::new(event_handler)); - - self - } - - /// Adds an event handler with multiple methods for each possible event. Passed by Arc. - pub fn event_handler_arc( - mut self, - event_handler_arc: Arc, - ) -> Self { - self.event_handlers.push(event_handler_arc); + pub fn event_handler(mut self, event_handler: impl Into>) -> Self + where + H: EventHandler + 'static, + { + self.event_handlers.push(event_handler.into()); self } From 3f224bb8b1d3f16e2577279e75d5bd2728011d7d Mon Sep 17 00:00:00 2001 From: Gnome! Date: Sat, 9 Dec 2023 20:21:26 +0000 Subject: [PATCH 005/159] Clean up `ShardManager`/`ShardQueuer`/`ShardRunner` (#2653) --- src/cache/event.rs | 6 +- src/cache/mod.rs | 7 ++- src/client/dispatch.rs | 9 ++- src/client/event_handler.rs | 4 +- src/client/mod.rs | 61 +++++++------------ src/gateway/bridge/mod.rs | 8 ++- src/gateway/bridge/shard_manager.rs | 88 ++++++++++----------------- src/gateway/bridge/shard_messenger.rs | 87 +++++--------------------- src/gateway/bridge/shard_queuer.rs | 39 ++++++------ src/gateway/bridge/shard_runner.rs | 4 +- src/gateway/bridge/voice.rs | 10 +-- src/gateway/shard.rs | 57 ++++++----------- src/model/gateway.rs | 8 +-- src/model/guild/guild_id.rs | 6 +- src/model/guild/mod.rs | 4 +- src/model/guild/partial_guild.rs | 4 +- src/model/id.rs | 6 +- src/model/invite.rs | 4 +- src/utils/mod.rs | 13 +++- 19 files changed, 164 insertions(+), 261 deletions(-) diff --git a/src/cache/event.rs b/src/cache/event.rs index ff57e00ed35..4ca869663ac 100644 --- a/src/cache/event.rs +++ b/src/cache/event.rs @@ -1,4 +1,5 @@ use std::collections::HashSet; +use std::num::NonZeroU16; use super::{Cache, CacheUpdate}; use crate::model::channel::{GuildChannel, Message}; @@ -478,12 +479,13 @@ impl CacheUpdate for ReadyEvent { let mut guilds_to_remove = vec![]; let ready_guilds_hashset = self.ready.guilds.iter().map(|status| status.id).collect::>(); - let shard_data = self.ready.shard.unwrap_or_else(|| ShardInfo::new(ShardId(1), 1)); + let shard_data = + self.ready.shard.unwrap_or_else(|| ShardInfo::new(ShardId(1), NonZeroU16::MIN)); for guild_entry in cache.guilds.iter() { let guild = guild_entry.key(); // Only handle data for our shard. - if crate::utils::shard_id(*guild, shard_data.total) == shard_data.id.0 + if crate::utils::shard_id(*guild, shard_data.total.get()) == shard_data.id.0 && !ready_guilds_hashset.contains(guild) { guilds_to_remove.push(*guild); diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 03b767e02b1..63a1922fd94 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -26,6 +26,7 @@ use std::collections::{HashSet, VecDeque}; use std::hash::Hash; +use std::num::NonZeroU16; #[cfg(feature = "temp_cache")] use std::sync::Arc; #[cfg(feature = "temp_cache")] @@ -127,7 +128,7 @@ pub type ChannelMessagesRef<'a> = CacheRef<'a, ChannelId, HashMap, pub has_sent_shards_ready: bool, } @@ -286,7 +287,7 @@ impl Cache { message_queue: DashMap::default(), shard_data: RwLock::new(CachedShardData { - total: 1, + total: NonZeroU16::MIN, connected: HashSet::new(), has_sent_shards_ready: false, }), @@ -539,7 +540,7 @@ impl Cache { /// Returns the number of shards. #[inline] - pub fn shard_count(&self) -> u32 { + pub fn shard_count(&self) -> NonZeroU16 { self.shard_data.read().total } diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index b42eebe1e0c..7ec22ba64f2 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -347,13 +347,12 @@ fn update_cache_with_event( #[cfg(feature = "cache")] { let mut shards = cache.shard_data.write(); - if shards.connected.len() as u32 == shards.total && !shards.has_sent_shards_ready { + if shards.connected.len() == shards.total.get() as usize + && !shards.has_sent_shards_ready + { shards.has_sent_shards_ready = true; - let total = shards.total; - drop(shards); - extra_event = Some(FullEvent::ShardsReady { - total_shards: total, + total_shards: shards.total, }); } } diff --git a/src/client/event_handler.rs b/src/client/event_handler.rs index 22ae3a1dee0..c06f172d98b 100644 --- a/src/client/event_handler.rs +++ b/src/client/event_handler.rs @@ -1,3 +1,5 @@ +use std::num::NonZeroU16; + use async_trait::async_trait; use super::context::Context; @@ -117,7 +119,7 @@ event_handler! { /// Dispatched when every shard has received a Ready event #[cfg(feature = "cache")] - ShardsReady { total_shards: u32 } => async fn shards_ready(&self, ctx: Context); + ShardsReady { total_shards: NonZeroU16 } => async fn shards_ready(&self, ctx: Context); /// Dispatched when a channel is created. /// diff --git a/src/client/mod.rs b/src/client/mod.rs index 0abd23ad83e..6d993de8866 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -24,6 +24,7 @@ mod error; mod event_handler; use std::future::IntoFuture; +use std::num::NonZeroU16; use std::ops::Range; use std::sync::Arc; #[cfg(feature = "framework")] @@ -32,7 +33,7 @@ use std::sync::OnceLock; use futures::channel::mpsc::UnboundedReceiver as Receiver; use futures::future::BoxFuture; use futures::StreamExt as _; -use tokio::sync::{Mutex, RwLock}; +use tokio::sync::RwLock; use tracing::{debug, error, info, instrument}; use typemap_rev::{TypeMap, TypeMapKey}; @@ -59,6 +60,7 @@ use crate::internal::prelude::*; use crate::model::gateway::GatewayIntents; use crate::model::id::ApplicationId; use crate::model::user::OnlineStatus; +use crate::utils::check_shard_total; /// A builder implementing [`IntoFuture`] building a [`Client`] to interact with Discord. #[cfg(feature = "gateway")] @@ -335,13 +337,13 @@ impl IntoFuture for ClientBuilder { let cache = Arc::new(Cache::new_with_settings(self.cache_settings)); Box::pin(async move { - let ws_url = Arc::new(Mutex::new(match http.get_gateway().await { - Ok(response) => response.url, + let (ws_url, shard_total) = match http.get_bot_gateway().await { + Ok(response) => (Arc::from(response.url), response.shards), Err(err) => { - tracing::warn!("HTTP request to get gateway URL failed: {}", err); - "wss://gateway.discord.gg".to_string() + tracing::warn!("HTTP request to get gateway URL failed: {err}"); + (Arc::from("wss://gateway.discord.gg"), NonZeroU16::MIN) }, - })); + }; #[cfg(feature = "framework")] let framework_cell = Arc::new(OnceLock::new()); @@ -351,12 +353,10 @@ impl IntoFuture for ClientBuilder { raw_event_handlers, #[cfg(feature = "framework")] framework: Arc::clone(&framework_cell), - shard_index: 0, - shard_init: 0, - shard_total: 0, #[cfg(feature = "voice")] voice_manager: voice_manager.clone(), ws_url: Arc::clone(&ws_url), + shard_total, #[cfg(feature = "cache")] cache: Arc::clone(&cache), http: Arc::clone(&http), @@ -590,11 +590,7 @@ pub struct Client { #[cfg(feature = "voice")] pub voice_manager: Option>, /// URL that the client's shards will use to connect to the gateway. - /// - /// This is likely not important for production usage and is, at best, used for debugging. - /// - /// This is wrapped in an `Arc>` so all shards will have an updated value available. - pub ws_url: Arc>, + pub ws_url: Arc, /// The cache for the client. #[cfg(feature = "cache")] pub cache: Arc, @@ -642,7 +638,7 @@ impl Client { /// [gateway docs]: crate::gateway#sharding #[instrument(skip(self))] pub async fn start(&mut self) -> Result<()> { - self.start_connection(0, 0, 1).await + self.start_connection(0, 0, NonZeroU16::MIN).await } /// Establish the connection(s) and start listening for events. @@ -685,8 +681,7 @@ impl Client { pub async fn start_autosharded(&mut self) -> Result<()> { let (end, total) = { let res = self.http.get_bot_gateway().await?; - - (res.shards - 1, res.shards) + (res.shards.get() - 1, res.shards) }; self.start_connection(0, end, total).await @@ -747,8 +742,8 @@ impl Client { /// /// [gateway docs]: crate::gateway#sharding #[instrument(skip(self))] - pub async fn start_shard(&mut self, shard: u32, shards: u32) -> Result<()> { - self.start_connection(shard, shard, shards).await + pub async fn start_shard(&mut self, shard: u16, shards: u16) -> Result<()> { + self.start_connection(shard, shard, check_shard_total(shards)).await } /// Establish sharded connections and start listening for events. @@ -788,8 +783,8 @@ impl Client { /// /// [Gateway docs]: crate::gateway#sharding #[instrument(skip(self))] - pub async fn start_shards(&mut self, total_shards: u32) -> Result<()> { - self.start_connection(0, total_shards - 1, total_shards).await + pub async fn start_shards(&mut self, total_shards: u16) -> Result<()> { + self.start_connection(0, total_shards - 1, check_shard_total(total_shards)).await } /// Establish a range of sharded connections and start listening for events. @@ -829,26 +824,16 @@ impl Client { /// /// [Gateway docs]: crate::gateway#sharding #[instrument(skip(self))] - pub async fn start_shard_range(&mut self, range: Range, total_shards: u32) -> Result<()> { - self.start_connection(range.start, range.end, total_shards).await + pub async fn start_shard_range(&mut self, range: Range, total_shards: u16) -> Result<()> { + self.start_connection(range.start, range.end, check_shard_total(total_shards)).await } - /// Shard data layout is: - /// 0: first shard number to initialize - /// 1: shard number to initialize up to and including - /// 2: total number of shards the bot is sharding for - /// - /// Not all shards need to be initialized in this process. - /// - /// # Errors - /// - /// Returns a [`ClientError::Shutdown`] when all shards have shutdown due to an error. #[instrument(skip(self))] async fn start_connection( &mut self, - start_shard: u32, - end_shard: u32, - total_shards: u32, + start_shard: u16, + end_shard: u16, + total_shards: NonZeroU16, ) -> Result<()> { #[cfg(feature = "voice")] if let Some(voice_manager) = &self.voice_manager { @@ -859,11 +844,9 @@ impl Client { let init = end_shard - start_shard + 1; - self.shard_manager.set_shards(start_shard, init, total_shards).await; - debug!("Initializing shard info: {} - {}/{}", start_shard, init, total_shards); - if let Err(why) = self.shard_manager.initialize() { + if let Err(why) = self.shard_manager.initialize(start_shard, init, total_shards) { error!("Failed to boot a shard: {:?}", why); info!("Shutting down all shards"); diff --git a/src/gateway/bridge/mod.rs b/src/gateway/bridge/mod.rs index 45543d26711..4fc51c1cd87 100644 --- a/src/gateway/bridge/mod.rs +++ b/src/gateway/bridge/mod.rs @@ -50,6 +50,7 @@ mod shard_runner_message; mod voice; use std::fmt; +use std::num::NonZeroU16; use std::time::Duration as StdDuration; pub use self::event::ShardStageUpdateEvent; @@ -68,9 +69,10 @@ use crate::model::id::ShardId; /// A message to be sent to the [`ShardQueuer`]. #[derive(Clone, Debug)] pub enum ShardQueuerMessage { - /// Message to start a shard, where the 0-index element is the ID of the Shard to start and the - /// 1-index element is the total shards in use. - Start(ShardId, ShardId), + /// Message to set the shard total. + SetShardTotal(NonZeroU16), + /// Message to start a shard. + Start(ShardId), /// Message to shutdown the shard queuer. Shutdown, /// Message to dequeue/shutdown a shard. diff --git a/src/gateway/bridge/shard_manager.rs b/src/gateway/bridge/shard_manager.rs index d20ef98244f..270c9a12e49 100644 --- a/src/gateway/bridge/shard_manager.rs +++ b/src/gateway/bridge/shard_manager.rs @@ -1,5 +1,5 @@ use std::collections::{HashMap, VecDeque}; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::num::NonZeroU16; use std::sync::Arc; #[cfg(feature = "framework")] use std::sync::OnceLock; @@ -65,7 +65,10 @@ use crate::model::gateway::GatewayIntents; /// impl RawEventHandler for Handler {} /// /// # let http: Arc = unimplemented!(); -/// let ws_url = Arc::new(Mutex::new(http.get_gateway().await?.url)); +/// let gateway_info = http.get_bot_gateway().await?; +/// +/// let shard_total = gateway_info.shards; +/// let ws_url = Arc::from(gateway_info.url); /// let data = Arc::new(RwLock::new(TypeMap::new())); /// let event_handler = Arc::new(Handler) as Arc; /// let framework = Arc::new(StandardFramework::new()) as Arc; @@ -75,15 +78,10 @@ use crate::model::gateway::GatewayIntents; /// event_handlers: vec![event_handler], /// raw_event_handlers: vec![], /// framework: Arc::new(OnceLock::from(framework)), -/// // the shard index to start initiating from -/// shard_index: 0, -/// // the number of shards to initiate (this initiates 0, 1, and 2) -/// shard_init: 3, -/// // the total number of shards in use -/// shard_total: 5, /// # #[cfg(feature = "voice")] /// # voice_manager: None, /// ws_url, +/// shard_total, /// # #[cfg(feature = "cache")] /// # cache: unimplemented!(), /// # http, @@ -103,13 +101,6 @@ pub struct ShardManager { /// **Note**: It is highly unrecommended to mutate this yourself unless you need to. Instead /// prefer to use methods on this struct that are provided where possible. pub runners: Arc>>, - /// The index of the first shard to initialize, 0-indexed. - // Atomics are used here to allow for mutation without requiring a mutable reference to self. - shard_index: AtomicU32, - /// The number of shards to initialize. - shard_init: AtomicU32, - /// The total shards in use, 1-indexed. - shard_total: AtomicU32, shard_queuer: Sender, // We can safely use a Mutex for this field, as it is only ever used in one single place // and only is ever used to receive a single message @@ -131,10 +122,7 @@ impl ShardManager { let manager = Arc::new(Self { return_value_tx: Mutex::new(return_value_tx), - shard_index: AtomicU32::new(opt.shard_index), - shard_init: AtomicU32::new(opt.shard_init), shard_queuer: shard_queue_tx, - shard_total: AtomicU32::new(opt.shard_total), shard_shutdown: Mutex::new(shutdown_recv), shard_shutdown_send: shutdown_send, runners: Arc::clone(&runners), @@ -155,6 +143,7 @@ impl ShardManager { #[cfg(feature = "voice")] voice_manager: opt.voice_manager, ws_url: opt.ws_url, + shard_total: opt.shard_total, #[cfg(feature = "cache")] cache: opt.cache, http: opt.http, @@ -182,34 +171,22 @@ impl ShardManager { /// This will communicate shard boots with the [`ShardQueuer`] so that they are properly /// queued. #[instrument(skip(self))] - pub fn initialize(&self) -> Result<()> { - let shard_index = self.shard_index.load(Ordering::Relaxed); - let shard_init = self.shard_init.load(Ordering::Relaxed); - let shard_total = self.shard_total.load(Ordering::Relaxed); - + pub fn initialize( + &self, + shard_index: u16, + shard_init: u16, + shard_total: NonZeroU16, + ) -> Result<()> { let shard_to = shard_index + shard_init; + self.set_shard_total(shard_total); for shard_id in shard_index..shard_to { - self.boot([ShardId(shard_id), ShardId(shard_total)]); + self.boot(ShardId(shard_id)); } Ok(()) } - /// Sets the new sharding information for the manager. - /// - /// This will shutdown all existing shards. - /// - /// This will _not_ instantiate the new shards. - #[instrument(skip(self))] - pub async fn set_shards(&self, index: u32, init: u32, total: u32) { - self.shutdown_all().await; - - self.shard_index.store(index, Ordering::Relaxed); - self.shard_init.store(init, Ordering::Relaxed); - self.shard_total.store(total, Ordering::Relaxed); - } - /// Restarts a shard runner. /// /// This sends a shutdown signal to a shard's associated [`ShardRunner`], and then queues a @@ -232,12 +209,9 @@ impl ShardManager { /// [`ShardRunner`]: super::ShardRunner #[instrument(skip(self))] pub async fn restart(&self, shard_id: ShardId) { - info!("Restarting shard {}", shard_id); + info!("Restarting shard {shard_id}"); self.shutdown(shard_id, 4000).await; - - let shard_total = self.shard_total.load(Ordering::Relaxed); - - self.boot([shard_id, ShardId(shard_total)]); + self.boot(shard_id); } /// Returns the [`ShardId`]s of the shards that have been instantiated and currently have a @@ -324,12 +298,18 @@ impl ShardManager { drop(self.return_value_tx.lock().await.unbounded_send(Ok(()))); } - #[instrument(skip(self))] - fn boot(&self, shard_info: [ShardId; 2]) { - info!("Telling shard queuer to start shard {}", shard_info[0]); + fn set_shard_total(&self, shard_total: NonZeroU16) { + info!("Setting shard total to {shard_total}"); - let msg = ShardQueuerMessage::Start(shard_info[0], shard_info[1]); + let msg = ShardQueuerMessage::SetShardTotal(shard_total); + drop(self.shard_queuer.unbounded_send(msg)); + } + #[instrument(skip(self))] + fn boot(&self, shard_id: ShardId) { + info!("Telling shard queuer to start shard {shard_id}"); + + let msg = ShardQueuerMessage::Start(shard_id); drop(self.shard_queuer.unbounded_send(msg)); } @@ -351,10 +331,10 @@ impl ShardManager { } } - pub async fn restart_shard(&self, id: ShardId) { - self.restart(id).await; - if let Err(e) = self.shard_shutdown_send.unbounded_send(id) { - tracing::warn!("failed to notify about finished shutdown: {}", e); + pub async fn restart_shard(&self, shard_id: ShardId) { + self.restart(shard_id).await; + if let Err(e) = self.shard_shutdown_send.unbounded_send(shard_id) { + tracing::warn!("failed to notify about finished shutdown: {e}"); } } @@ -389,12 +369,10 @@ pub struct ShardManagerOptions { pub raw_event_handlers: Vec>, #[cfg(feature = "framework")] pub framework: Arc>>, - pub shard_index: u32, - pub shard_init: u32, - pub shard_total: u32, #[cfg(feature = "voice")] pub voice_manager: Option>, - pub ws_url: Arc>, + pub ws_url: Arc, + pub shard_total: NonZeroU16, #[cfg(feature = "cache")] pub cache: Arc, pub http: Arc, diff --git a/src/gateway/bridge/shard_messenger.rs b/src/gateway/bridge/shard_messenger.rs index 685cc7eb0ed..efd1b6c9133 100644 --- a/src/gateway/bridge/shard_messenger.rs +++ b/src/gateway/bridge/shard_messenger.rs @@ -58,24 +58,17 @@ impl ShardMessenger { /// parameter: /// /// ```rust,no_run - /// # use tokio::sync::Mutex; - /// # use serenity::model::gateway::{GatewayIntents, ShardInfo}; - /// # use serenity::model::id::ShardId; /// # use serenity::gateway::{ChunkGuildFilter, Shard}; - /// # use std::sync::Arc; - /// # - /// # async fn run() -> Result<(), Box> { - /// # let mutex = Arc::new(Mutex::new("".to_string())); - /// # - /// # let shard_info = ShardInfo { - /// # id: ShardId(0), - /// # total: 1, - /// # }; - /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?; - /// # + /// # async fn run(mut shard: Shard) -> Result<(), Box> { /// use serenity::model::id::GuildId; /// - /// shard.chunk_guild(GuildId::new(81384788765712384), Some(2000), false, ChunkGuildFilter::None, None); + /// shard.chunk_guild( + /// GuildId::new(81384788765712384), + /// Some(2000), + /// false, + /// ChunkGuildFilter::None, + /// None, + /// ); /// # Ok(()) /// # } /// ``` @@ -84,22 +77,8 @@ impl ShardMessenger { /// and a nonce of `"request"`: /// /// ```rust,no_run - /// # use tokio::sync::Mutex; - /// # use serenity::model::gateway::{GatewayIntents, ShardInfo}; - /// # use serenity::model::id::ShardId; /// # use serenity::gateway::{ChunkGuildFilter, Shard}; - /// # use std::sync::Arc; - /// # - /// # async fn run() -> Result<(), Box> { - /// # let mutex = Arc::new(Mutex::new("".to_string())); - /// # - /// # let shard_info = ShardInfo { - /// # id: ShardId(0), - /// # total: 1, - /// # }; - /// # - /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?;; - /// # + /// # async fn run(mut shard: Shard) -> Result<(), Box> { /// use serenity::model::id::GuildId; /// /// shard.chunk_guild( @@ -138,21 +117,8 @@ impl ShardMessenger { /// Setting the current activity to playing `"Heroes of the Storm"`: /// /// ```rust,no_run - /// # use tokio::sync::Mutex; - /// # use serenity::gateway::{Shard}; - /// # use serenity::model::id::ShardId; - /// # use serenity::model::gateway::{GatewayIntents, ShardInfo}; - /// # use std::sync::Arc; - /// # - /// # async fn run() -> Result<(), Box> { - /// # let mutex = Arc::new(Mutex::new("".to_string())); - /// # - /// # let shard_info = ShardInfo { - /// # id: ShardId(0), - /// # total: 1, - /// # }; - /// # - /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?; + /// # use serenity::gateway::Shard; + /// # async fn run(mut shard: Shard) -> Result<(), Box> { /// use serenity::gateway::ActivityData; /// /// shard.set_activity(Some(ActivityData::playing("Heroes of the Storm"))); @@ -172,20 +138,8 @@ impl ShardMessenger { /// Set the current user as playing `"Heroes of the Storm"` and being online: /// /// ```rust,ignore - /// # use tokio::sync::Mutex; /// # use serenity::gateway::Shard; - /// # use std::sync::Arc; - /// # - /// # async fn run() -> Result<(), Box> { - /// # let mutex = Arc::new(Mutex::new("".to_string())); - /// # - /// # let shard_info = ShardInfo { - /// # id: 0, - /// # total: 1, - /// # }; - /// # - /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, None).await?; - /// # + /// # async fn run(shard: Shard) -> Result<(), Box> { /// use serenity::gateway::ActivityData; /// use serenity::model::user::OnlineStatus; /// @@ -214,21 +168,8 @@ impl ShardMessenger { /// Setting the current online status for the shard to [`DoNotDisturb`]. /// /// ```rust,no_run - /// # use tokio::sync::Mutex; - /// # use serenity::gateway::{Shard}; - /// # use serenity::model::id::ShardId; - /// # use serenity::model::gateway::{GatewayIntents, ShardInfo}; - /// # use std::sync::Arc; - /// # - /// # async fn run() -> Result<(), Box> { - /// # let mutex = Arc::new(Mutex::new("".to_string())); - /// # let shard_info = ShardInfo { - /// # id: ShardId(0), - /// # total: 1, - /// # }; - /// # - /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?; - /// # + /// # use serenity::gateway::Shard; + /// # async fn run(mut shard: Shard) -> Result<(), Box> { /// use serenity::model::user::OnlineStatus; /// /// shard.set_status(OnlineStatus::DoNotDisturb); diff --git a/src/gateway/bridge/shard_queuer.rs b/src/gateway/bridge/shard_queuer.rs index 2c0571a8fa5..f1dd6b80f9f 100644 --- a/src/gateway/bridge/shard_queuer.rs +++ b/src/gateway/bridge/shard_queuer.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, VecDeque}; +use std::num::NonZeroU16; use std::sync::Arc; #[cfg(feature = "framework")] use std::sync::OnceLock; @@ -63,7 +64,7 @@ pub struct ShardQueuer { /// The shards that are queued for booting. /// /// This will typically be filled with previously failed boots. - pub queue: VecDeque, + pub queue: VecDeque, /// A copy of the map of shard runners. pub runners: Arc>>, /// A receiver channel for the shard queuer to be told to start shards. @@ -72,7 +73,9 @@ pub struct ShardQueuer { #[cfg(feature = "voice")] pub voice_manager: Option>, /// A copy of the URL to use to connect to the gateway. - pub ws_url: Arc>, + pub ws_url: Arc, + /// The total amount of shards to start. + pub shard_total: NonZeroU16, #[cfg(feature = "cache")] pub cache: Arc, pub http: Arc, @@ -116,14 +119,16 @@ impl ShardQueuer { debug!("[Shard Queuer] Received to shutdown shard {} with {}.", shard.0, code); self.shutdown(shard, code).await; }, - Ok(Some(ShardQueuerMessage::Start(id, total))) => { - debug!("[Shard Queuer] Received to start shard {} of {}.", id.0, total.0); - self.checked_start(id, total.0).await; + Ok(Some(ShardQueuerMessage::Start(shard_id))) => { + self.checked_start(shard_id).await; + }, + Ok(Some(ShardQueuerMessage::SetShardTotal(shard_total))) => { + self.shard_total = shard_total; }, Ok(None) => break, Err(_) => { if let Some(shard) = self.queue.pop_front() { - self.checked_start(shard.id, shard.total).await; + self.checked_start(shard).await; } }, } @@ -148,28 +153,26 @@ impl ShardQueuer { } #[instrument(skip(self))] - async fn checked_start(&mut self, id: ShardId, total: u32) { - debug!("[Shard Queuer] Checked start for shard {} out of {}", id, total); - self.check_last_start().await; + async fn checked_start(&mut self, shard_id: ShardId) { + debug!("[Shard Queuer] Checked start for shard {shard_id}"); - if let Err(why) = self.start(id, total).await { - warn!("[Shard Queuer] Err starting shard {}: {:?}", id, why); - info!("[Shard Queuer] Re-queueing start of shard {}", id); + self.check_last_start().await; + if let Err(why) = self.start(shard_id).await { + warn!("[Shard Queuer] Err starting shard {shard_id}: {why:?}"); + info!("[Shard Queuer] Re-queueing start of shard {shard_id}"); - self.queue.push_back(ShardInfo::new(id, total)); + self.queue.push_back(shard_id); } self.last_start = Some(Instant::now()); } #[instrument(skip(self))] - async fn start(&mut self, id: ShardId, total: u32) -> Result<()> { - let shard_info = ShardInfo::new(id, total); - + async fn start(&mut self, shard_id: ShardId) -> Result<()> { let mut shard = Shard::new( Arc::clone(&self.ws_url), self.http.token(), - shard_info, + ShardInfo::new(shard_id, self.shard_total), self.intents, self.presence.clone(), ) @@ -204,7 +207,7 @@ impl ShardQueuer { debug!("[ShardRunner {:?}] Stopping", runner.shard.shard_info()); }); - self.runners.lock().await.insert(id, runner_info); + self.runners.lock().await.insert(shard_id, runner_info); Ok(()) } diff --git a/src/gateway/bridge/shard_runner.rs b/src/gateway/bridge/shard_runner.rs index a8b5ace432b..703be65c729 100644 --- a/src/gateway/bridge/shard_runner.rs +++ b/src/gateway/bridge/shard_runner.rs @@ -331,7 +331,9 @@ impl ShardRunner { }, Event::VoiceServerUpdate(event) => { if let Some(guild_id) = event.guild_id { - voice_manager.server_update(guild_id, &event.endpoint, &event.token).await; + voice_manager + .server_update(guild_id, event.endpoint.as_deref(), &event.token) + .await; } }, Event::VoiceStateUpdate(event) => { diff --git a/src/gateway/bridge/voice.rs b/src/gateway/bridge/voice.rs index 7f03113070f..8f3bc6ce7c2 100644 --- a/src/gateway/bridge/voice.rs +++ b/src/gateway/bridge/voice.rs @@ -1,3 +1,5 @@ +use std::num::NonZeroU16; + use async_trait::async_trait; use futures::channel::mpsc::UnboundedSender as Sender; @@ -14,7 +16,7 @@ pub trait VoiceGatewayManager: Send + Sync { /// Performs initial setup at the start of a connection to Discord. /// /// This will only occur once, and provides the bot's ID and shard count. - async fn initialise(&self, shard_count: u32, user_id: UserId); + async fn initialise(&self, shard_count: NonZeroU16, user_id: UserId); /// Handler fired in response to a [`Ready`] event. /// @@ -22,19 +24,19 @@ pub trait VoiceGatewayManager: Send + Sync { /// active shard. /// /// [`Ready`]: crate::model::event::Event - async fn register_shard(&self, shard_id: u32, sender: Sender); + async fn register_shard(&self, shard_id: u16, sender: Sender); /// Handler fired in response to a disconnect, reconnection, or rebalance. /// /// This event invalidates the last sender associated with `shard_id`. Unless the bot is fully /// disconnecting, this is often followed by a call to [`Self::register_shard`]. Users may wish /// to buffer manually any gateway messages sent between these calls. - async fn deregister_shard(&self, shard_id: u32); + async fn deregister_shard(&self, shard_id: u16); /// Handler for VOICE_SERVER_UPDATE messages. /// /// These contain the endpoint and token needed to form a voice connection session. - async fn server_update(&self, guild_id: GuildId, endpoint: &Option, token: &str); + async fn server_update(&self, guild_id: GuildId, endpoint: Option<&str>, token: &str); /// Handler for VOICE_STATE_UPDATE messages. /// diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index 19ab0942461..3fbdf49d1e4 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use std::time::{Duration as StdDuration, Instant}; -use tokio::sync::Mutex; use tokio_tungstenite::tungstenite::error::Error as TungsteniteError; use tokio_tungstenite::tungstenite::protocol::frame::CloseFrame; use tracing::{debug, error, info, instrument, trace, warn}; @@ -74,7 +73,7 @@ pub struct Shard { // a decent amount of time. pub started: Instant, pub token: String, - ws_url: Arc>, + ws_url: Arc, pub intents: GatewayIntents, } @@ -88,6 +87,7 @@ impl Shard { /// Instantiating a new Shard manually for a bot with no shards, and then listening for events: /// /// ```rust,no_run + /// use std::num::NonZeroU16; /// use std::sync::Arc; /// /// use serenity::gateway::Shard; @@ -102,11 +102,11 @@ impl Shard { /// let token = std::env::var("DISCORD_BOT_TOKEN")?; /// let shard_info = ShardInfo { /// id: ShardId(0), - /// total: 1, + /// total: NonZeroU16::MIN, /// }; /// /// // retrieve the gateway response, which contains the URL to connect to - /// let gateway = Arc::new(Mutex::new(http.get_gateway().await?.url)); + /// let gateway = Arc::from(http.get_gateway().await?.url); /// let shard = Shard::new(gateway, &token, shard_info, GatewayIntents::all(), None).await?; /// /// // at this point, you can create a `loop`, and receive events and match @@ -120,14 +120,13 @@ impl Shard { /// On Error, will return either [`Error::Gateway`], [`Error::Tungstenite`] or a Rustls/native /// TLS error. pub async fn new( - ws_url: Arc>, + ws_url: Arc, token: &str, shard_info: ShardInfo, intents: GatewayIntents, presence: Option, ) -> Result { - let url = ws_url.lock().await.clone(); - let client = connect(&url).await?; + let client = connect(&ws_url).await?; let presence = presence.unwrap_or_default(); let last_heartbeat_sent = None; @@ -596,24 +595,19 @@ impl Shard { /// specifying a query parameter: /// /// ```rust,no_run - /// # use tokio::sync::Mutex; /// # use serenity::gateway::{ChunkGuildFilter, Shard}; - /// # use serenity::model::gateway::{GatewayIntents, ShardInfo}; - /// # use serenity::model::id::ShardId; - /// # use std::sync::Arc; - /// # - /// # async fn run() -> Result<(), Box> { - /// # let mutex = Arc::new(Mutex::new("".to_string())); - /// # let shard_info = ShardInfo { - /// # id: ShardId(0), - /// # total: 1, - /// # }; - /// # - /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?; - /// # + /// # async fn run(mut shard: Shard) -> Result<(), Box> { /// use serenity::model::id::GuildId; /// - /// shard.chunk_guild(GuildId::new(81384788765712384), Some(2000), false, ChunkGuildFilter::None, None).await?; + /// shard + /// .chunk_guild( + /// GuildId::new(81384788765712384), + /// Some(2000), + /// false, + /// ChunkGuildFilter::None, + /// None, + /// ) + /// .await?; /// # Ok(()) /// # } /// ``` @@ -622,22 +616,8 @@ impl Shard { /// `"do"` and a nonce of `"request"`: /// /// ```rust,no_run - /// # use tokio::sync::Mutex; - /// # use serenity::model::gateway::{GatewayIntents, ShardInfo}; /// # use serenity::gateway::{ChunkGuildFilter, Shard}; - /// # use serenity::model::id::ShardId; - /// # use std::error::Error; - /// # use std::sync::Arc; - /// # - /// # async fn run() -> Result<(), Box> { - /// # let mutex = Arc::new(Mutex::new("".to_string())); - /// # - /// # let shard_info = ShardInfo { - /// # id: ShardId(0), - /// # total: 1, - /// # }; - /// # let mut shard = Shard::new(mutex.clone(), "", shard_info, GatewayIntents::all(), None).await?; - /// # + /// # async fn run(mut shard: Shard) -> Result<(), Box> { /// use serenity::model::id::GuildId; /// /// shard @@ -703,8 +683,7 @@ impl Shard { // Hello is received. self.stage = ConnectionStage::Connecting; self.started = Instant::now(); - let url = &self.ws_url.lock().await.clone(); - let client = connect(url).await?; + let client = connect(&self.ws_url).await?; self.stage = ConnectionStage::Handshake; Ok(client) diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 877b5fc948f..cfc8e9998c0 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -22,7 +22,7 @@ pub struct BotGateway { /// The gateway to connect to. pub url: String, /// The number of shards that is recommended to be used by the current bot user. - pub shards: u32, + pub shards: NonZeroU16, /// Information describing how many gateway sessions you can initiate within a ratelimit /// period. pub session_start_limit: SessionStartLimit, @@ -372,12 +372,12 @@ pub struct SessionStartLimit { #[derive(Clone, Copy, Debug)] pub struct ShardInfo { pub id: ShardId, - pub total: u32, + pub total: NonZeroU16, } impl ShardInfo { #[must_use] - pub(crate) fn new(id: ShardId, total: u32) -> Self { + pub(crate) fn new(id: ShardId, total: NonZeroU16) -> Self { Self { id, total, @@ -387,7 +387,7 @@ impl ShardInfo { impl<'de> serde::Deserialize<'de> for ShardInfo { fn deserialize>(deserializer: D) -> StdResult { - <(u32, u32)>::deserialize(deserializer).map(|(id, total)| ShardInfo { + <(u16, NonZeroU16)>::deserialize(deserializer).map(|(id, total)| ShardInfo { id: ShardId(id), total, }) diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs index 697c4bd1d3c..60d9754d596 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -1384,8 +1384,8 @@ impl GuildId { #[cfg(all(feature = "cache", feature = "utils"))] #[inline] #[must_use] - pub fn shard_id(self, cache: impl AsRef) -> u32 { - crate::utils::shard_id(self, cache.as_ref().shard_count()) + pub fn shard_id(self, cache: impl AsRef) -> u16 { + crate::utils::shard_id(self, cache.as_ref().shard_count().get()) } /// Returns the Id of the shard associated with the guild. @@ -1410,7 +1410,7 @@ impl GuildId { #[cfg(all(feature = "utils", not(feature = "cache")))] #[inline] #[must_use] - pub fn shard_id(self, shard_count: u32) -> u32 { + pub fn shard_id(self, shard_count: u16) -> u16 { crate::utils::shard_id(self, shard_count) } diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 9779136f991..bfe345c556d 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -2244,7 +2244,7 @@ impl Guild { /// [`utils::shard_id`]: crate::utils::shard_id #[cfg(all(feature = "cache", feature = "utils"))] #[inline] - pub fn shard_id(&self, cache: impl AsRef) -> u32 { + pub fn shard_id(&self, cache: impl AsRef) -> u16 { self.id.shard_id(&cache) } @@ -2269,7 +2269,7 @@ impl Guild { #[cfg(all(feature = "utils", not(feature = "cache")))] #[inline] #[must_use] - pub fn shard_id(&self, shard_count: u32) -> u32 { + pub fn shard_id(&self, shard_count: u16) -> u16 { self.id.shard_id(shard_count) } diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index fac1277010a..68c6507a76c 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -1375,7 +1375,7 @@ impl PartialGuild { #[cfg(all(feature = "cache", feature = "utils"))] #[inline] #[must_use] - pub fn shard_id(&self, cache: impl AsRef) -> u32 { + pub fn shard_id(&self, cache: impl AsRef) -> u16 { self.id.shard_id(cache) } @@ -1400,7 +1400,7 @@ impl PartialGuild { #[cfg(all(feature = "utils", not(feature = "cache")))] #[inline] #[must_use] - pub fn shard_id(&self, shard_count: u32) -> u32 { + pub fn shard_id(&self, shard_count: u16) -> u16 { self.id.shard_id(shard_count) } diff --git a/src/model/id.rs b/src/model/id.rs index f1d965b89fa..0032f5d7c86 100644 --- a/src/model/id.rs +++ b/src/model/id.rs @@ -213,15 +213,15 @@ id_u64! { /// and therefore cannot be [`Serialize`]d or [`Deserialize`]d. #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct ShardId(pub u32); +pub struct ShardId(pub u16); impl ShardId { - /// Retrieves the value as a [`u32`]. + /// Retrieves the value as a [`u16`]. /// /// This is not a [`u64`] as [`ShardId`]s are not a discord concept and are simply used for /// internal type safety. #[must_use] - pub fn get(self) -> u32 { + pub fn get(self) -> u16 { self.0 } } diff --git a/src/model/invite.rs b/src/model/invite.rs index 841777fd5e4..bced0fbc7c6 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -236,7 +236,7 @@ impl InviteGuild { #[cfg(all(feature = "cache", feature = "utils"))] #[inline] #[must_use] - pub fn shard_id(&self, cache: impl AsRef) -> u32 { + pub fn shard_id(&self, cache: impl AsRef) -> u16 { self.id.shard_id(&cache) } @@ -261,7 +261,7 @@ impl InviteGuild { #[cfg(all(feature = "utils", not(feature = "cache")))] #[inline] #[must_use] - pub fn shard_id(&self, shard_count: u32) -> u32 { + pub fn shard_id(&self, shard_count: u16) -> u16 { self.id.shard_id(shard_count) } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 367c28bd9f8..3950f6fe270 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -22,6 +22,7 @@ pub use content_safe::*; pub use formatted_timestamp::*; #[cfg(feature = "collector")] pub use quick_modal::*; +use tracing::warn; use url::Url; pub use self::custom_message::CustomMessage; @@ -485,8 +486,16 @@ pub(crate) fn user_perms(cache: impl AsRef, channel_id: ChannelId) -> Res /// ``` #[inline] #[must_use] -pub fn shard_id(guild_id: GuildId, shard_count: u32) -> u32 { - ((guild_id.get() >> 22) % (shard_count as u64)) as u32 +pub fn shard_id(guild_id: GuildId, shard_count: u16) -> u16 { + let shard_count = check_shard_total(shard_count); + ((guild_id.get() >> 22) % (shard_count.get() as u64)) as u16 +} + +pub(crate) fn check_shard_total(total_shards: u16) -> NonZeroU16 { + NonZeroU16::new(total_shards).unwrap_or_else(|| { + warn!("Invalid shard total provided ({total_shards}), defaulting to 1"); + NonZeroU16::MIN + }) } #[cfg(test)] From 0c1aea40e1561112559e0b41516855044be03d75 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Sat, 9 Dec 2023 22:23:49 +0000 Subject: [PATCH 006/159] Put `Message::thread` behind a `Box` (#2658) This trades a heap allocation for messages sent along with thread creation for `Message`'s inline size dropping from 1176 bytes to 760 bytes, --- src/model/channel/message.rs | 2 +- src/model/event.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 7ea029c87d6..d4c65f0540b 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -113,7 +113,7 @@ pub struct Message { /// [`Interaction`]: crate::model::application::Interaction pub interaction_metadata: Option>, /// The thread that was started from this message, includes thread member object. - pub thread: Option, + pub thread: Option>, /// The components of this message #[serde(default)] pub components: Vec, diff --git a/src/model/event.rs b/src/model/event.rs index 5c8e019ab87..159c8f1323e 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -530,7 +530,7 @@ pub struct MessageUpdateEvent { pub interaction: Option>>, pub interaction_metadata: Option>>, #[serde(default, deserialize_with = "deserialize_some")] - pub thread: Option>, + pub thread: Option>>, pub components: Option>, pub sticker_items: Option>, pub position: Option>, From 5e84751400cb285b614a3e2037e3d765400f8f33 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Sun, 10 Dec 2023 22:36:23 +0000 Subject: [PATCH 007/159] Replace `Vec` and `String` with `FixedArray` and `FixedString` for all models (#2656) This shrinks type sizes by a lot; however, it makes the user experience slightly different: - `FixedString` must be converted to String with `.into()` or `.into_string()` before it can be pushed to, but dereferences to `&str` as is. - `FixedArray` must be converted to `Vec` with `.into()` or `.into_vec()` before it can be pushed to, but dereferences to `&[T]` as is. The crate of these types is currently a Git dependency, but this is fine for the `next` branch. It needs some basic testing, which Serenity is perfect for, before a release will be made to crates.io. --- Cargo.toml | 5 +- examples/e04_message_builder/src/main.rs | 2 +- src/builder/create_command.rs | 30 ++++---- src/builder/create_components.rs | 20 +++--- src/builder/create_embed.rs | 26 +++---- src/builder/create_forum_tag.rs | 7 +- src/builder/create_interaction_response.rs | 2 +- src/builder/create_scheduled_event.rs | 2 +- src/builder/edit_guild_welcome_screen.rs | 4 +- src/builder/edit_role.rs | 13 ++-- src/builder/edit_scheduled_event.rs | 2 +- src/cache/event.rs | 31 ++++++--- src/client/dispatch.rs | 9 +-- src/client/event_handler.rs | 1 + src/collector.rs | 7 +- src/framework/standard/parse/mod.rs | 2 +- src/gateway/mod.rs | 19 +++-- src/gateway/shard.rs | 6 +- src/internal/prelude.rs | 2 + src/model/application/command.rs | 20 +++--- src/model/application/command_interaction.rs | 62 +++++++++-------- src/model/application/component.rs | 36 +++++----- .../application/component_interaction.rs | 18 ++--- src/model/application/interaction.rs | 2 +- src/model/application/mod.rs | 29 ++++---- src/model/application/modal_interaction.rs | 10 +-- src/model/application/ping_interaction.rs | 3 +- src/model/channel/attachment.rs | 13 ++-- src/model/channel/channel_id.rs | 3 +- src/model/channel/embed.rs | 51 +++++++------- src/model/channel/guild_channel.rs | 17 +++-- src/model/channel/message.rs | 38 +++++----- src/model/channel/mod.rs | 13 ++-- src/model/channel/partial_channel.rs | 3 +- src/model/channel/private_channel.rs | 5 +- src/model/channel/reaction.rs | 20 +++--- src/model/connection.rs | 9 +-- src/model/event.rs | 52 +++++++------- src/model/gateway.rs | 51 +++++++------- src/model/guild/audit_log/change.rs | 5 +- src/model/guild/audit_log/mod.rs | 29 ++++---- src/model/guild/automod.rs | 19 ++--- src/model/guild/emoji.rs | 5 +- src/model/guild/guild_id.rs | 2 +- src/model/guild/guild_preview.rs | 11 +-- src/model/guild/integration.rs | 12 ++-- src/model/guild/member.rs | 9 ++- src/model/guild/mod.rs | 55 +++++++-------- src/model/guild/partial_guild.rs | 15 ++-- src/model/guild/role.rs | 5 +- src/model/guild/scheduled_event.rs | 7 +- src/model/guild/welcome_screen.rs | 15 ++-- src/model/invite.rs | 19 +++-- src/model/misc.rs | 69 ++++++++++--------- src/model/sticker.rs | 15 ++-- src/model/user.rs | 20 +++--- src/model/utils.rs | 20 ++++-- src/model/voice.rs | 7 +- src/model/webhook.rs | 7 +- src/utils/argument_convert/user.rs | 2 +- src/utils/content_safe.rs | 10 +-- src/utils/custom_message.rs | 2 +- src/utils/message_builder.rs | 4 +- src/utils/mod.rs | 6 +- src/utils/quick_modal.rs | 7 +- tests/test_reaction.rs | 8 +-- 66 files changed, 545 insertions(+), 485 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 709f9d74ed1..1f995b2f489 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ base64 = { version = "0.22.0" } secrecy = { version = "0.8.0", features = ["serde"] } arrayvec = { version = "0.7.4", features = ["serde"] } serde_cow = { version = "0.1.0" } +small-fixed-array = { git = "https://github.com/GnomedDev/small-fixed-array", features = ["serde", "log_using_tracing"] } # Optional dependencies fxhash = { version = "0.2.1", optional = true } simd-json = { version = "0.13.4", optional = true } @@ -49,7 +50,7 @@ mime_guess = { version = "2.0.4", optional = true } dashmap = { version = "5.5.3", features = ["serde"], optional = true } parking_lot = { version = "0.12.1", optional = true } ed25519-dalek = { version = "2.0.0", optional = true } -typesize = { version = "0.1.2", optional = true, features = ["url", "time", "serde_json", "secrecy", "dashmap", "parking_lot", "details"] } +typesize = { version = "0.1.4", optional = true, features = ["url", "time", "serde_json", "secrecy", "dashmap", "parking_lot", "details"] } # serde feature only allows for serialisation, # Serenity workspace crates command_attr = { version = "0.5.3", path = "./command_attr", optional = true } @@ -125,6 +126,8 @@ simd_json = ["simd-json", "typesize?/simd_json"] # Enables temporary caching in functions that retrieve data via the HTTP API. temp_cache = ["cache", "mini-moka", "typesize?/mini_moka"] +typesize = ["dep:typesize", "small-fixed-array/typesize"] + # Removed feature (https://github.com/serenity-rs/serenity/pull/2246) absolute_ratelimits = [] diff --git a/examples/e04_message_builder/src/main.rs b/examples/e04_message_builder/src/main.rs index e5fd8ce3b0c..0f3005cc966 100644 --- a/examples/e04_message_builder/src/main.rs +++ b/examples/e04_message_builder/src/main.rs @@ -26,7 +26,7 @@ impl EventHandler for Handler { // emojis, and more. let response = MessageBuilder::new() .push("User ") - .push_bold_safe(&msg.author.name) + .push_bold_safe(msg.author.name.into_string()) .push(" used the 'ping' command in the ") .mention(&channel) .push(" channel") diff --git a/src/builder/create_command.rs b/src/builder/create_command.rs index c32795f8d39..38341359dcc 100644 --- a/src/builder/create_command.rs +++ b/src/builder/create_command.rs @@ -26,9 +26,9 @@ impl CreateCommandOption { ) -> Self { Self(CommandOption { kind, - name: name.into(), + name: name.into().into(), name_localizations: None, - description: description.into(), + description: description.into().into(), description_localizations: None, required: false, autocomplete: false, @@ -37,7 +37,7 @@ impl CreateCommandOption { min_length: None, max_length: None, - channel_types: Vec::new(), + channel_types: FixedArray::default(), choices: Vec::new(), options: Vec::new(), }) @@ -53,7 +53,7 @@ impl CreateCommandOption { /// /// **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(); + self.0.name = name.into().into(); self } @@ -77,7 +77,7 @@ impl CreateCommandOption { /// /// **Note**: Must be between 1 and 100 characters. pub fn description(mut self, description: impl Into) -> Self { - self.0.description = description.into(); + self.0.description = description.into().into(); self } /// Specifies a localized description of the option. @@ -114,7 +114,7 @@ impl CreateCommandOption { /// characters. Value must be between -2^53 and 2^53. pub fn add_int_choice(self, name: impl Into, value: i32) -> Self { self.add_choice(CommandOptionChoice { - name: name.into(), + name: name.into().into(), value: Value::from(value), name_localizations: None, }) @@ -128,7 +128,7 @@ impl CreateCommandOption { locales: impl IntoIterator, impl Into)>, ) -> Self { self.add_choice(CommandOptionChoice { - name: name.into(), + name: name.into().into(), value: Value::from(value), name_localizations: Some( locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(), @@ -142,7 +142,7 @@ impl CreateCommandOption { /// 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(), + name: name.into().into(), value: Value::String(value.into()), name_localizations: None, }) @@ -156,7 +156,7 @@ impl CreateCommandOption { locales: impl IntoIterator, impl Into)>, ) -> Self { self.add_choice(CommandOptionChoice { - name: name.into(), + name: name.into().into(), value: Value::String(value.into()), name_localizations: Some( locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(), @@ -170,7 +170,7 @@ impl CreateCommandOption { /// 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(), + name: name.into().into(), value: Value::from(value), name_localizations: None, }) @@ -184,7 +184,7 @@ impl CreateCommandOption { locales: impl IntoIterator, impl Into)>, ) -> Self { self.add_choice(CommandOptionChoice { - name: name.into(), + name: name.into().into(), value: Value::from(value), name_localizations: Some( locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(), @@ -241,7 +241,7 @@ impl CreateCommandOption { /// /// [`Channel`]: crate::model::application::CommandOptionType::Channel pub fn channel_types(mut self, channel_types: Vec) -> Self { - self.0.channel_types = channel_types; + self.0.channel_types = channel_types.into(); self } @@ -300,7 +300,7 @@ impl CreateCommandOption { #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateCommand { - name: String, + name: FixedString, name_localizations: HashMap, #[serde(skip_serializing_if = "Option::is_none")] description: Option, @@ -328,7 +328,7 @@ impl CreateCommand { Self { kind: None, - name: name.into(), + name: name.into().into(), name_localizations: HashMap::new(), description: None, description_localizations: HashMap::new(), @@ -351,7 +351,7 @@ impl CreateCommand { /// 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(); + self.name = name.into().into(); self } diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index 4d9858e2a0f..8c339a8d79d 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -45,7 +45,7 @@ impl CreateButton { Self(Button { kind: ComponentType::Button, data: ButtonKind::Link { - url: url.into(), + url: url.into().into(), }, label: None, emoji: None, @@ -75,7 +75,7 @@ impl CreateButton { kind: ComponentType::Button, data: ButtonKind::NonLink { style: ButtonStyle::Primary, - custom_id: custom_id.into(), + custom_id: custom_id.into().into(), }, label: None, emoji: None, @@ -92,7 +92,7 @@ impl CreateButton { custom_id, .. } = &mut self.0.data { - *custom_id = id.into(); + *custom_id = id.into().into(); } self } @@ -112,7 +112,7 @@ impl CreateButton { /// Sets label of the button. pub fn label(mut self, label: impl Into) -> Self { - self.0.label = Some(label.into()); + self.0.label = Some(label.into().into()); self } @@ -366,8 +366,8 @@ impl CreateInputText { ) -> Self { Self(InputText { style: Some(style), - label: Some(label.into()), - custom_id: custom_id.into(), + label: Some(label.into().into()), + custom_id: custom_id.into().into(), placeholder: None, min_length: None, @@ -387,20 +387,20 @@ impl CreateInputText { /// Sets the label of this input text. Replaces the current value as set in [`Self::new`]. pub fn label(mut self, label: impl Into) -> Self { - self.0.label = Some(label.into()); + self.0.label = Some(label.into().into()); self } /// Sets the custom id of the input text, a developer-defined identifier. Replaces the current /// value as set in [`Self::new`]. pub fn custom_id(mut self, id: impl Into) -> Self { - self.0.custom_id = id.into(); + self.0.custom_id = id.into().into(); self } /// Sets the placeholder of this input text. pub fn placeholder(mut self, label: impl Into) -> Self { - self.0.placeholder = Some(label.into()); + self.0.placeholder = Some(label.into().into()); self } @@ -418,7 +418,7 @@ impl CreateInputText { /// Sets the value of this input text. pub fn value(mut self, value: impl Into) -> Self { - self.0.value = Some(value.into()); + self.0.value = Some(value.into().into()); self } diff --git a/src/builder/create_embed.rs b/src/builder/create_embed.rs index 5861f46d22a..7ba2443bd52 100644 --- a/src/builder/create_embed.rs +++ b/src/builder/create_embed.rs @@ -59,7 +59,7 @@ impl CreateEmbed { /// **Note**: This can't be longer than 4096 characters. #[inline] pub fn description(mut self, description: impl Into) -> Self { - self.0.description = Some(description.into()); + self.0.description = Some(description.into().into()); self } @@ -107,7 +107,7 @@ impl CreateEmbed { #[inline] pub fn image(mut self, url: impl Into) -> Self { self.0.image = Some(EmbedImage { - url: url.into(), + url: url.into().into(), proxy_url: None, height: None, width: None, @@ -119,7 +119,7 @@ impl CreateEmbed { #[inline] pub fn thumbnail(mut self, url: impl Into) -> Self { self.0.thumbnail = Some(EmbedThumbnail { - url: url.into(), + url: url.into().into(), proxy_url: None, height: None, width: None, @@ -150,14 +150,14 @@ impl CreateEmbed { /// Set the title of the embed. #[inline] pub fn title(mut self, title: impl Into) -> Self { - self.0.title = Some(title.into()); + self.0.title = Some(title.into().into()); self } /// Set the URL to direct to when clicking on the title. #[inline] pub fn url(mut self, url: impl Into) -> Self { - self.0.url = Some(url.into()); + self.0.url = Some(url.into().into()); self } @@ -213,7 +213,7 @@ impl Default for CreateEmbed { description: None, thumbnail: None, timestamp: None, - kind: Some("rich".into()), + kind: Some("rich".to_string().into()), author: None, colour: None, footer: None, @@ -241,7 +241,7 @@ impl CreateEmbedAuthor { /// Creates an author object with the given name, leaving all other fields empty. pub fn new(name: impl Into) -> Self { Self(EmbedAuthor { - name: name.into(), + name: name.into().into(), icon_url: None, url: None, // Has no builder method because I think this field is only relevant when receiving (?) @@ -251,19 +251,19 @@ impl CreateEmbedAuthor { /// Set the author's name, replacing the current value as set in [`Self::new`]. pub fn name(mut self, name: impl Into) -> Self { - self.0.name = name.into(); + self.0.name = name.into().into(); self } /// Set the URL of the author's icon. pub fn icon_url(mut self, icon_url: impl Into) -> Self { - self.0.icon_url = Some(icon_url.into()); + self.0.icon_url = Some(icon_url.into().into()); self } /// Set the author's URL. pub fn url(mut self, url: impl Into) -> Self { - self.0.url = Some(url.into()); + self.0.url = Some(url.into().into()); self } } @@ -299,7 +299,7 @@ impl CreateEmbedFooter { /// Creates a new footer object with the given text, leaving all other fields empty. pub fn new(text: impl Into) -> Self { Self(EmbedFooter { - text: text.into(), + text: text.into().into(), icon_url: None, // Has no builder method because I think this field is only relevant when receiving (?) proxy_icon_url: None, @@ -308,7 +308,7 @@ impl CreateEmbedFooter { /// Set the footer's text, replacing the current value as set in [`Self::new`]. pub fn text(mut self, text: impl Into) -> Self { - self.0.text = text.into(); + self.0.text = text.into().into(); self } @@ -316,7 +316,7 @@ impl CreateEmbedFooter { /// /// Refer [`CreateEmbed::image`] for rules on naming local attachments. pub fn icon_url(mut self, icon_url: impl Into) -> Self { - self.0.icon_url = Some(icon_url.into()); + self.0.icon_url = Some(icon_url.into().into()); self } } diff --git a/src/builder/create_forum_tag.rs b/src/builder/create_forum_tag.rs index 31db70451d5..d616b3e2481 100644 --- a/src/builder/create_forum_tag.rs +++ b/src/builder/create_forum_tag.rs @@ -1,3 +1,4 @@ +use crate::internal::prelude::*; use crate::model::prelude::*; /// [Discord docs](https://discord.com/developers/docs/resources/channel#forum-tag-object-forum-tag-structure) @@ -6,16 +7,16 @@ use crate::model::prelude::*; #[must_use] #[derive(Clone, Debug, Serialize)] pub struct CreateForumTag { - name: String, + name: FixedString, moderated: bool, emoji_id: Option, - emoji_name: Option, + emoji_name: Option, } impl CreateForumTag { pub fn new(name: impl Into) -> Self { Self { - name: name.into(), + name: name.into().into(), moderated: false, emoji_id: None, emoji_name: None, diff --git a/src/builder/create_interaction_response.rs b/src/builder/create_interaction_response.rs index 5e77281d06c..38a979661d5 100644 --- a/src/builder/create_interaction_response.rs +++ b/src/builder/create_interaction_response.rs @@ -328,7 +328,7 @@ pub struct AutocompleteChoice(CommandOptionChoice); impl AutocompleteChoice { pub fn new(name: impl Into, value: impl Into) -> Self { Self(CommandOptionChoice { - name: name.into(), + name: name.into().into(), name_localizations: None, value: value.into(), }) diff --git a/src/builder/create_scheduled_event.rs b/src/builder/create_scheduled_event.rs index b2f83eda3a9..4cd3db68d39 100644 --- a/src/builder/create_scheduled_event.rs +++ b/src/builder/create_scheduled_event.rs @@ -104,7 +104,7 @@ impl<'a> CreateScheduledEvent<'a> { /// [`External`]: ScheduledEventType::External pub fn location(mut self, location: impl Into) -> Self { self.entity_metadata = Some(ScheduledEventMetadata { - location: Some(location.into()), + location: Some(location.into().into()), }); self } diff --git a/src/builder/edit_guild_welcome_screen.rs b/src/builder/edit_guild_welcome_screen.rs index df652c512fc..d0d635d226a 100644 --- a/src/builder/edit_guild_welcome_screen.rs +++ b/src/builder/edit_guild_welcome_screen.rs @@ -94,8 +94,8 @@ impl CreateGuildWelcomeChannel { pub fn new(channel_id: ChannelId, description: String) -> Self { Self(GuildWelcomeChannel { channel_id, - description, emoji: None, + description: description.into(), }) } @@ -107,7 +107,7 @@ impl CreateGuildWelcomeChannel { /// The description shown for the channel. pub fn description(mut self, description: impl Into) -> Self { - self.0.description = description.into(); + self.0.description = description.into().into(); self } diff --git a/src/builder/edit_role.rs b/src/builder/edit_role.rs index 56b5d322942..080e816da3e 100644 --- a/src/builder/edit_role.rs +++ b/src/builder/edit_role.rs @@ -3,7 +3,6 @@ use super::Builder; use super::CreateAttachment; #[cfg(feature = "http")] use crate::http::CacheHttp; -#[cfg(feature = "http")] use crate::internal::prelude::*; use crate::model::prelude::*; @@ -47,7 +46,7 @@ use crate::model::prelude::*; #[must_use] pub struct EditRole<'a> { #[serde(skip_serializing_if = "Option::is_none")] - name: Option, + name: Option, #[serde(skip_serializing_if = "Option::is_none")] permissions: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -56,9 +55,9 @@ pub struct EditRole<'a> { #[serde(skip_serializing_if = "Option::is_none")] hoist: Option, #[serde(skip_serializing_if = "Option::is_none")] - icon: Option>, + icon: Option>, #[serde(skip_serializing_if = "Option::is_none")] - unicode_emoji: Option>, + unicode_emoji: Option>, #[serde(skip_serializing_if = "Option::is_none")] mentionable: Option, @@ -112,7 +111,7 @@ impl<'a> EditRole<'a> { /// Set the role's name. pub fn name(mut self, name: impl Into) -> Self { - self.name = Some(name.into()); + self.name = Some(name.into().into()); self } @@ -131,14 +130,14 @@ impl<'a> EditRole<'a> { /// Set the role icon to a unicode emoji. pub fn unicode_emoji(mut self, unicode_emoji: Option) -> Self { - self.unicode_emoji = Some(unicode_emoji); + self.unicode_emoji = Some(unicode_emoji.map(Into::into)); self.icon = Some(None); self } /// Set the role icon to a custom image. pub fn icon(mut self, icon: Option<&CreateAttachment>) -> Self { - self.icon = Some(icon.map(CreateAttachment::to_base64)); + self.icon = Some(icon.map(CreateAttachment::to_base64).map(Into::into)); self.unicode_emoji = Some(None); self } diff --git a/src/builder/edit_scheduled_event.rs b/src/builder/edit_scheduled_event.rs index d97a2401ef9..a8b0b214f04 100644 --- a/src/builder/edit_scheduled_event.rs +++ b/src/builder/edit_scheduled_event.rs @@ -148,7 +148,7 @@ impl<'a> EditScheduledEvent<'a> { /// [`External`]: ScheduledEventType::External pub fn location(mut self, location: impl Into) -> Self { self.entity_metadata = Some(Some(ScheduledEventMetadata { - location: Some(location.into()), + location: Some(location.into().into()), })); self } diff --git a/src/cache/event.rs b/src/cache/event.rs index 4ca869663ac..e671de3506e 100644 --- a/src/cache/event.rs +++ b/src/cache/event.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::num::NonZeroU16; use super::{Cache, CacheUpdate}; +use crate::internal::prelude::*; use crate::model::channel::{GuildChannel, Message}; use crate::model::event::{ ChannelCreateEvent, @@ -447,7 +448,7 @@ impl CacheUpdate for PresenceUpdateEvent { mute: false, nick: None, user, - roles: vec![], + roles: FixedArray::default(), pending: false, premium_since: None, permissions: None, @@ -468,9 +469,7 @@ impl CacheUpdate for ReadyEvent { type Output = (); fn update(&mut self, cache: &Cache) -> Option<()> { - let ready = self.ready.clone(); - - for unavailable in ready.guilds { + for unavailable in &self.ready.guilds { cache.guilds.remove(&unavailable.id); cache.unavailable_guilds.insert(unavailable.id, ()); } @@ -502,7 +501,7 @@ impl CacheUpdate for ReadyEvent { cached_shard_data.total = shard_data.total; cached_shard_data.connected.insert(shard_data.id); } - *cache.user.write() = ready.user; + cache.user.write().clone_from(&self.ready.user); None } @@ -518,7 +517,11 @@ impl CacheUpdate for ThreadCreateEvent { if let Some(i) = g.threads.iter().position(|e| e.id == thread_id) { Some(std::mem::replace(&mut g.threads[i], self.thread.clone())) } else { - g.threads.push(self.thread.clone()); + // This is a rare enough occurence to realloc. + let mut threads = std::mem::take(&mut g.threads).into_vec(); + threads.push(self.thread.clone()); + g.threads = threads.into(); + None } }) @@ -535,7 +538,11 @@ impl CacheUpdate for ThreadUpdateEvent { if let Some(i) = g.threads.iter().position(|e| e.id == thread_id) { Some(std::mem::replace(&mut g.threads[i], self.thread.clone())) } else { - g.threads.push(self.thread.clone()); + // This is a rare enough occurence to realloc. + let mut threads = std::mem::take(&mut g.threads).into_vec(); + threads.push(self.thread.clone()); + g.threads = threads.into(); + None } }) @@ -549,7 +556,13 @@ impl CacheUpdate for ThreadDeleteEvent { let (guild_id, thread_id) = (self.thread.guild_id, self.thread.id); cache.guilds.get_mut(&guild_id).and_then(|mut g| { - g.threads.iter().position(|e| e.id == thread_id).map(|i| g.threads.remove(i)) + g.threads.iter().position(|e| e.id == thread_id).map(|i| { + let mut threads = std::mem::take(&mut g.threads).into_vec(); + let thread = threads.remove(i); + g.threads = threads.into(); + + thread + }) }) } } @@ -590,7 +603,7 @@ impl CacheUpdate for VoiceStateUpdateEvent { } impl CacheUpdate for VoiceChannelStatusUpdateEvent { - type Output = String; + type Output = FixedString; fn update(&mut self, cache: &Cache) -> Option { let mut guild = cache.guilds.get_mut(&self.guild_id)?; diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index 7ec22ba64f2..2a866a5edd0 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -9,6 +9,7 @@ use super::{Context, FullEvent}; use crate::cache::{Cache, CacheUpdate}; #[cfg(feature = "framework")] use crate::framework::Framework; +use crate::internal::prelude::*; use crate::internal::tokio::spawn_named; use crate::model::channel::ChannelType; use crate::model::event::Event; @@ -299,7 +300,7 @@ fn update_cache_with_event( }, Event::MessageDeleteBulk(event) => FullEvent::MessageDeleteBulk { channel_id: event.channel_id, - multiple_deleted_messages_ids: event.ids, + multiple_deleted_messages_ids: event.ids.into_vec(), guild_id: event.guild_id, }, Event::MessageDelete(event) => FullEvent::MessageDelete { @@ -319,7 +320,7 @@ fn update_cache_with_event( }, #[allow(deprecated)] Event::PresencesReplace(event) => FullEvent::PresenceReplace { - presences: event.presences, + presences: event.presences.into_vec(), }, Event::PresenceUpdate(mut event) => { update_cache!(cache, event); @@ -391,11 +392,11 @@ fn update_cache_with_event( } }, Event::VoiceChannelStatusUpdate(mut event) => { - let old = if_cache!(event.update(cache)); + let old = if_cache!(event.update(cache).map(FixedString::into_string)); FullEvent::VoiceChannelStatusUpdate { old, - status: event.status, + status: event.status.map(FixedString::into_string), id: event.id, guild_id: event.guild_id, } diff --git a/src/client/event_handler.rs b/src/client/event_handler.rs index c06f172d98b..b813bba44f3 100644 --- a/src/client/event_handler.rs +++ b/src/client/event_handler.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "cache")] use std::num::NonZeroU16; use async_trait::async_trait; diff --git a/src/collector.rs b/src/collector.rs index 232e50e6c38..4cc4b1d4f93 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -2,6 +2,7 @@ use futures::future::pending; use futures::{Stream, StreamExt as _}; use crate::gateway::{CollectorCallback, ShardMessenger}; +use crate::internal::prelude::*; use crate::model::prelude::*; /// Fundamental collector function. All collector types in this module are just wrappers around @@ -85,7 +86,7 @@ macro_rules! make_specific_collector { } $( - #[doc = concat!("Filters [`", stringify!($item_type), "`]'s by a specific [`", stringify!($filter_type), "`].")] + #[doc = concat!("Filters [`", stringify!($item_type), "`]'s by a specific [`type@", stringify!($filter_type), "`].")] pub fn $filter_name(mut self, $filter_name: $filter_type) -> Self { self.$filter_name = Some($filter_name); self @@ -158,7 +159,7 @@ make_specific_collector!( channel_id: ChannelId => interaction.channel_id == *channel_id, guild_id: GuildId => interaction.guild_id.map_or(true, |x| x == *guild_id), message_id: MessageId => interaction.message.id == *message_id, - custom_ids: Vec => custom_ids.contains(&interaction.data.custom_id), + custom_ids: FixedArray => custom_ids.contains(&interaction.data.custom_id), ); make_specific_collector!( ModalInteractionCollector, ModalInteraction, @@ -169,7 +170,7 @@ make_specific_collector!( channel_id: ChannelId => interaction.channel_id == *channel_id, guild_id: GuildId => interaction.guild_id.map_or(true, |g| g == *guild_id), message_id: MessageId => interaction.message.as_ref().map_or(true, |m| m.id == *message_id), - custom_ids: Vec => custom_ids.contains(&interaction.data.custom_id), + custom_ids: Vec => custom_ids.contains(&interaction.data.custom_id), ); make_specific_collector!( ReactionCollector, Reaction, diff --git a/src/framework/standard/parse/mod.rs b/src/framework/standard/parse/mod.rs index 158ebee9038..bd02a80963a 100644 --- a/src/framework/standard/parse/mod.rs +++ b/src/framework/standard/parse/mod.rs @@ -55,7 +55,7 @@ fn permissions_in( } if let Some(channel) = guild.and_then(|guild| guild.channels.get(&channel_id).cloned()) { - let mut data = Vec::with_capacity(member.roles.len()); + let mut data = Vec::with_capacity(member.roles.len() as usize); for overwrite in &channel.permission_overwrites { if let PermissionOverwriteType::Role(role) = overwrite.kind { diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 3b31c752877..c54af922b16 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -54,7 +54,6 @@ pub use self::bridge::*; pub use self::error::Error as GatewayError; pub use self::shard::Shard; pub use self::ws::WsClient; -#[cfg(feature = "http")] use crate::internal::prelude::*; use crate::model::gateway::{Activity, ActivityType}; use crate::model::id::UserId; @@ -73,12 +72,12 @@ pub struct PresenceData { #[derive(Clone, Debug, Serialize)] pub struct ActivityData { /// The name of the activity - pub name: String, + pub name: FixedString, /// The type of the activity #[serde(rename = "type")] pub kind: ActivityType, /// The state of the activity, if the type is [`ActivityType::Custom`] - pub state: Option, + pub state: Option, /// The url of the activity, if the type is [`ActivityType::Streaming`] pub url: Option, } @@ -88,7 +87,7 @@ impl ActivityData { #[must_use] pub fn playing(name: impl Into) -> Self { Self { - name: name.into(), + name: name.into().into(), kind: ActivityType::Playing, state: None, url: None, @@ -103,7 +102,7 @@ impl ActivityData { #[cfg(feature = "http")] pub fn streaming(name: impl Into, url: impl IntoUrl) -> Result { Ok(Self { - name: name.into(), + name: name.into().into(), kind: ActivityType::Streaming, state: None, url: Some(url.into_url()?), @@ -114,7 +113,7 @@ impl ActivityData { #[must_use] pub fn listening(name: impl Into) -> Self { Self { - name: name.into(), + name: name.into().into(), kind: ActivityType::Listening, state: None, url: None, @@ -125,7 +124,7 @@ impl ActivityData { #[must_use] pub fn watching(name: impl Into) -> Self { Self { - name: name.into(), + name: name.into().into(), kind: ActivityType::Watching, state: None, url: None, @@ -136,7 +135,7 @@ impl ActivityData { #[must_use] pub fn competing(name: impl Into) -> Self { Self { - name: name.into(), + name: name.into().into(), kind: ActivityType::Competing, state: None, url: None, @@ -149,9 +148,9 @@ impl ActivityData { Self { // discord seems to require a name for custom activities // even though it's not displayed - name: "~".to_string(), + name: "~".to_string().into(), kind: ActivityType::Custom, - state: Some(state.into()), + state: Some(state.into().into()), url: None, } } diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index 3fbdf49d1e4..3904893d75f 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -65,7 +65,7 @@ pub struct Shard { // This must be set to `true` in `Shard::handle_event`'s `Ok(GatewayEvent::HeartbeatAck)` arm. last_heartbeat_acknowledged: bool, seq: u64, - session_id: Option, + session_id: Option, shard_info: ShardInfo, stage: ConnectionStage, /// Instant of when the shard was started. @@ -235,8 +235,8 @@ impl Shard { } #[inline] - pub fn session_id(&self) -> Option<&String> { - self.session_id.as_ref() + pub fn session_id(&self) -> Option<&str> { + self.session_id.as_deref() } #[inline] diff --git a/src/internal/prelude.rs b/src/internal/prelude.rs index df676dad5cb..7a1e846f11d 100644 --- a/src/internal/prelude.rs +++ b/src/internal/prelude.rs @@ -4,5 +4,7 @@ pub use std::result::Result as StdResult; +pub use small_fixed_array::{FixedArray, FixedString}; + pub use crate::error::{Error, Result}; pub use crate::json::{JsonMap, Value}; diff --git a/src/model/application/command.rs b/src/model/application/command.rs index 6c8525a5cab..eb9e2780460 100644 --- a/src/model/application/command.rs +++ b/src/model/application/command.rs @@ -39,13 +39,13 @@ pub struct Command { /// **Note**: It may only be present if it is received through the gateway. pub guild_id: Option, /// The command name. - pub name: String, + pub name: FixedString, /// The localized command name of the selected locale. /// /// If the name is localized, either this field or [`Self::name_localizations`] is set, /// depending on which endpoint this data was retrieved from /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)). - pub name_localized: Option, + pub name_localized: Option>, /// All localized command names. /// /// If the name is localized, either this field or [`Self::name_localized`] is set, depending @@ -53,13 +53,13 @@ pub struct Command { /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)). pub name_localizations: Option>, /// The command description. - pub description: String, + pub description: FixedString, /// The localized command description of the selected locale. /// /// If the description is localized, either this field or [`Self::description_localizations`] /// is set, depending on which endpoint this data was retrieved from /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)). - pub description_localized: Option, + pub description_localized: Option>, /// All localized command descriptions. /// /// If the description is localized, either this field or [`Self::description_localized`] is @@ -68,7 +68,7 @@ pub struct Command { pub description_localizations: Option>, /// The parameters for the command. #[serde(default)] - pub options: Vec, + pub options: FixedArray, /// The default permissions required to execute the command. pub default_member_permissions: Option, /// Indicates whether the command is available in DMs with the app, only for globally-scoped @@ -278,12 +278,12 @@ pub struct CommandOption { #[serde(rename = "type")] pub kind: CommandOptionType, /// The option name. - pub name: String, + pub name: FixedString, /// Localizations of the option name with locale as the key #[serde(skip_serializing_if = "Option::is_none")] pub name_localizations: Option>, /// The option description. - pub description: String, + pub description: FixedString, /// Localizations of the option description with locale as the key #[serde(skip_serializing_if = "Option::is_none")] pub description_localizations: Option>, @@ -310,7 +310,7 @@ pub struct CommandOption { /// /// [`Channel`]: CommandOptionType::Channel #[serde(default)] - pub channel_types: Vec, + pub channel_types: FixedArray, /// Minimum permitted value for Integer or Number options #[serde(default)] pub min_value: Option, @@ -359,7 +359,7 @@ enum_number! { #[non_exhaustive] pub struct CommandOptionChoice { /// The choice name. - pub name: String, + pub name: FixedString, /// Localizations of the choice name, with locale as key #[serde(skip_serializing_if = "Option::is_none")] pub name_localizations: Option>, @@ -381,7 +381,7 @@ pub struct CommandPermissions { /// The id of the guild. pub guild_id: GuildId, /// The permissions for the command in the guild. - pub permissions: Vec, + pub permissions: FixedArray, } /// The [`CommandPermission`] data. diff --git a/src/model/application/command_interaction.rs b/src/model/application/command_interaction.rs index 690a697f7eb..e63731222fc 100644 --- a/src/model/application/command_interaction.rs +++ b/src/model/application/command_interaction.rs @@ -71,16 +71,16 @@ pub struct CommandInteraction { #[serde(default)] pub user: User, /// A continuation token for responding to the interaction. - pub token: String, + pub token: FixedString, /// Always `1`. pub version: u8, /// Permissions the app or bot has within the channel the interaction was sent from. // TODO(next): This is now always serialized. pub app_permissions: Option, /// The selected language of the invoking user. - pub locale: String, + pub locale: FixedString, /// The guild's preferred locale. - pub guild_locale: Option, + pub guild_locale: Option, /// For monetized applications, any entitlements of the invoking user. pub entitlements: Vec, /// The owners of the applications that authorized the interaction, such as a guild or user. @@ -283,7 +283,7 @@ pub struct CommandData { /// The Id of the invoked command. pub id: CommandId, /// The name of the invoked command. - pub name: String, + pub name: FixedString, /// The application command type of the triggered application command. #[serde(rename = "type")] pub kind: CommandType, @@ -291,7 +291,7 @@ pub struct CommandData { #[serde(default)] pub resolved: CommandDataResolved, #[serde(default)] - pub options: Vec, + pub options: FixedArray, /// The Id of the guild the command is registered to. #[serde(skip_serializing_if = "Option::is_none")] pub guild_id: Option, @@ -526,7 +526,7 @@ pub struct CommandDataResolved { #[non_exhaustive] pub struct CommandDataOption { /// The name of the parameter. - pub name: String, + pub name: FixedString, /// The given value. pub value: CommandDataOptionValue, } @@ -540,7 +540,7 @@ impl CommandDataOption { #[derive(Deserialize, Serialize)] struct RawCommandDataOption { - name: String, + name: FixedString, #[serde(rename = "type")] kind: CommandOptionType, #[serde(skip_serializing_if = "Option::is_none")] @@ -650,13 +650,13 @@ impl Serialize for CommandDataOption { #[derive(Clone, Debug, PartialEq)] #[non_exhaustive] pub enum CommandDataOptionValue { - Autocomplete { kind: CommandOptionType, value: String }, + Autocomplete { kind: CommandOptionType, value: FixedString }, Boolean(bool), Integer(i64), Number(f64), - String(String), - SubCommand(Vec), - SubCommandGroup(Vec), + String(FixedString), + SubCommand(FixedArray), + SubCommandGroup(FixedArray), Attachment(AttachmentId), Channel(ChannelId), Mentionable(GenericId), @@ -818,14 +818,20 @@ mod tests { #[test] fn nested_options() { let value = CommandDataOption { - name: "subcommand_group".into(), - value: CommandDataOptionValue::SubCommandGroup(vec![CommandDataOption { - name: "subcommand".into(), - value: CommandDataOptionValue::SubCommand(vec![CommandDataOption { - name: "channel".into(), - value: CommandDataOptionValue::Channel(ChannelId::new(3)), - }]), - }]), + name: "subcommand_group".to_string().into(), + value: CommandDataOptionValue::SubCommandGroup( + vec![CommandDataOption { + name: "subcommand".to_string().into(), + value: CommandDataOptionValue::SubCommand( + vec![CommandDataOption { + name: "channel".to_string().into(), + value: CommandDataOptionValue::Channel(ChannelId::new(3)), + }] + .into(), + ), + }] + .into(), + ), }; assert_json( @@ -846,30 +852,30 @@ mod tests { fn mixed_options() { let value = vec![ CommandDataOption { - name: "boolean".into(), + name: "boolean".to_string().into(), value: CommandDataOptionValue::Boolean(true), }, CommandDataOption { - name: "integer".into(), + name: "integer".to_string().into(), value: CommandDataOptionValue::Integer(1), }, CommandDataOption { - name: "number".into(), + name: "number".to_string().into(), value: CommandDataOptionValue::Number(2.0), }, CommandDataOption { - name: "string".into(), - value: CommandDataOptionValue::String("foobar".into()), + name: "string".to_string().into(), + value: CommandDataOptionValue::String("foobar".to_string().into()), }, CommandDataOption { - name: "empty_subcommand".into(), - value: CommandDataOptionValue::SubCommand(vec![]), + name: "empty_subcommand".to_string().into(), + value: CommandDataOptionValue::SubCommand(FixedArray::default()), }, CommandDataOption { - name: "autocomplete".into(), + name: "autocomplete".to_string().into(), value: CommandDataOptionValue::Autocomplete { kind: CommandOptionType::Integer, - value: "not an integer".into(), + value: "not an integer".to_string().into(), }, }, ]; diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 997a5c5c0b6..c290c90cf52 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -37,7 +37,7 @@ pub struct ActionRow { pub kind: ComponentType, /// The components of this ActionRow. #[serde(default)] - pub components: Vec, + pub components: FixedArray, } /// A component which can be inside of an [`ActionRow`]. @@ -104,9 +104,9 @@ impl From for ActionRowComponent { #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum ButtonKind { - Link { url: String }, + Link { url: FixedString }, Premium { sku_id: SkuId }, - NonLink { custom_id: String, style: ButtonStyle }, + NonLink { custom_id: FixedString, style: ButtonStyle }, } impl Serialize for ButtonKind { @@ -171,7 +171,7 @@ pub struct Button { pub data: ButtonKind, /// The text which appears on the button. #[serde(skip_serializing_if = "Option::is_none")] - pub label: Option, + pub label: Option, /// The emoji of this button, if there is one. #[serde(skip_serializing_if = "Option::is_none")] pub emoji: Option, @@ -209,17 +209,17 @@ pub struct SelectMenu { #[serde(rename = "type")] pub kind: ComponentType, /// An identifier defined by the developer for the select menu. - pub custom_id: Option, + pub custom_id: Option, /// The options of this select menu. /// /// Required for [`ComponentType::StringSelect`] and unavailable for all others. #[serde(default)] - pub options: Vec, + pub options: FixedArray, /// List of channel types to include in the [`ComponentType::ChannelSelect`]. #[serde(default)] - pub channel_types: Vec, + pub channel_types: FixedArray, /// The placeholder shown when nothing is selected. - pub placeholder: Option, + pub placeholder: Option, /// The minimum number of selections allowed. pub min_values: Option, /// The maximum number of selections allowed. @@ -237,11 +237,11 @@ pub struct SelectMenu { #[non_exhaustive] pub struct SelectMenuOption { /// The text displayed on this option. - pub label: String, + pub label: FixedString, /// The value to be sent for this option. - pub value: String, + pub value: FixedString, /// The description shown for this option. - pub description: Option, + pub description: Option, /// The emoji displayed on this option. pub emoji: Option, /// Render this option as the default selection. @@ -260,7 +260,7 @@ pub struct InputText { #[serde(rename = "type")] pub kind: ComponentType, /// Developer-defined identifier for the input; max 100 characters - pub custom_id: String, + pub custom_id: FixedString, /// The [`InputTextStyle`]. Required when sending modal data. /// /// Discord docs are wrong here; it says the field is always sent in modal submit interactions @@ -272,7 +272,7 @@ pub struct InputText { /// Discord docs are wrong here; it says the field is always sent in modal submit interactions /// but it's not. It's only required when _sending_ modal data to Discord. /// - pub label: Option, + pub label: Option>, /// Minimum input length for a text input; min 0, max 4000 #[serde(skip_serializing_if = "Option::is_none")] pub min_length: Option, @@ -286,10 +286,10 @@ pub struct InputText { /// /// When receiving: The input from the user (always Some) #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, + pub value: Option>, /// Custom placeholder text if the input is empty; max 100 characters #[serde(skip_serializing_if = "Option::is_none")] - pub placeholder: Option, + pub placeholder: Option>, } enum_number! { @@ -317,10 +317,10 @@ mod tests { let mut button = Button { kind: ComponentType::Button, data: ButtonKind::NonLink { - custom_id: "hello".into(), + custom_id: "hello".to_string().into(), style: ButtonStyle::Danger, }, - label: Some("a".into()), + label: Some("a".to_string().into()), emoji: None, disabled: false, }; @@ -330,7 +330,7 @@ mod tests { ); button.data = ButtonKind::Link { - url: "https://google.com".into(), + url: "https://google.com".to_string().into(), }; assert_json( &button, diff --git a/src/model/application/component_interaction.rs b/src/model/application/component_interaction.rs index 01ea532d6ba..241a83b1968 100644 --- a/src/model/application/component_interaction.rs +++ b/src/model/application/component_interaction.rs @@ -49,7 +49,7 @@ pub struct ComponentInteraction { #[serde(default)] pub user: User, /// A continuation token for responding to the interaction. - pub token: String, + pub token: FixedString, /// Always `1`. pub version: u8, /// The message this interaction was triggered by, if it is a component. @@ -57,9 +57,9 @@ pub struct ComponentInteraction { /// Permissions the app or bot has within the channel the interaction was sent from. pub app_permissions: Option, /// The selected language of the invoking user. - pub locale: String, + pub locale: FixedString, /// The guild's preferred locale. - pub guild_locale: Option, + pub guild_locale: Option, /// For monetized applications, any entitlements of the invoking user. pub entitlements: Vec, /// The owners of the applications that authorized the interaction, such as a guild or user. @@ -252,11 +252,11 @@ impl Serialize for ComponentInteraction { #[derive(Clone, Debug)] pub enum ComponentInteractionDataKind { Button, - StringSelect { values: Vec }, - UserSelect { values: Vec }, - RoleSelect { values: Vec }, - MentionableSelect { values: Vec }, - ChannelSelect { values: Vec }, + StringSelect { values: FixedArray }, + UserSelect { values: FixedArray }, + RoleSelect { values: FixedArray }, + MentionableSelect { values: FixedArray }, + ChannelSelect { values: FixedArray }, Unknown(u8), } @@ -339,7 +339,7 @@ impl Serialize for ComponentInteractionDataKind { #[non_exhaustive] pub struct ComponentInteractionData { /// The custom id of the component. - pub custom_id: String, + pub custom_id: FixedString, /// Type and type-specific data of this component interaction. #[serde(flatten)] pub kind: ComponentInteractionDataKind, diff --git a/src/model/application/interaction.rs b/src/model/application/interaction.rs index 0cd0b69b68d..fa4adad36fe 100644 --- a/src/model/application/interaction.rs +++ b/src/model/application/interaction.rs @@ -407,7 +407,7 @@ pub struct MessageInteraction { /// The name of the [`Command`]. /// /// [`Command`]: crate::model::application::Command - pub name: String, + pub name: FixedString, /// The user who invoked the interaction. pub user: User, /// The member who invoked the interaction in the guild. diff --git a/src/model/application/mod.rs b/src/model/application/mod.rs index 2ec7099d255..d7504bb8e0e 100644 --- a/src/model/application/mod.rs +++ b/src/model/application/mod.rs @@ -24,6 +24,7 @@ use super::id::{ApplicationId, GenericId, GuildId, SkuId, UserId}; use super::misc::ImageHash; use super::user::User; use super::Permissions; +use crate::internal::prelude::*; /// Partial information about the given application. /// @@ -45,29 +46,29 @@ pub struct PartialCurrentApplicationInfo { #[non_exhaustive] pub struct CurrentApplicationInfo { pub id: ApplicationId, - pub name: String, + pub name: FixedString, pub icon: Option, - pub description: String, + pub description: FixedString, #[serde(default)] - pub rpc_origins: Vec, + pub rpc_origins: FixedArray, pub bot_public: bool, pub bot_require_code_grant: bool, #[serde(default)] - pub terms_of_service_url: Option, + pub terms_of_service_url: Option, #[serde(default)] - pub privacy_policy_url: Option, + pub privacy_policy_url: Option, pub owner: Option, // omitted `summary` because it deprecated - pub verify_key: String, + pub verify_key: FixedString, pub team: Option, #[serde(default)] pub guild_id: Option, #[serde(default)] pub primary_sku_id: Option, #[serde(default)] - pub slug: Option, + pub slug: Option, #[serde(default)] - pub cover_image: Option, + pub cover_image: Option, #[serde(default)] pub flags: Option, #[serde(default)] @@ -75,10 +76,10 @@ pub struct CurrentApplicationInfo { #[serde(default)] pub install_params: Option, #[serde(default)] - pub custom_install_url: Option, + pub custom_install_url: Option, /// The application's role connection verification entry point, which when configured will /// render the app as a verification method in the guild role verification configuration. - pub role_connections_verification_url: Option, + pub role_connections_verification_url: Option, #[serde(default)] pub integration_types_config: HashMap, pub approximate_guild_count: Option, @@ -152,9 +153,9 @@ pub struct Team { /// The snowflake ID of the team. pub id: GenericId, /// The name of the team. - pub name: String, + pub name: FixedString, /// The members of the team - pub members: Vec, + pub members: FixedArray, /// The user id of the team owner. pub owner_user_id: UserId, } @@ -171,7 +172,7 @@ pub struct TeamMember { /// /// NOTE: Will always be "*" for now. #[deprecated = "This field is not sent by the API anymore"] - pub permissions: Vec, + pub permissions: FixedArray, /// The ID of the team they are a member of. pub team_id: GenericId, /// The user type of the team member. @@ -276,7 +277,7 @@ bitflags! { #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct InstallParams { - pub scopes: Vec, + pub scopes: FixedArray, pub permissions: Permissions, } diff --git a/src/model/application/modal_interaction.rs b/src/model/application/modal_interaction.rs index fcb04db26b1..e5091706363 100644 --- a/src/model/application/modal_interaction.rs +++ b/src/model/application/modal_interaction.rs @@ -43,7 +43,7 @@ pub struct ModalInteraction { #[serde(default)] pub user: User, /// A continuation token for responding to the interaction. - pub token: String, + pub token: FixedString, /// Always `1`. pub version: u8, /// The message this interaction was triggered by @@ -55,9 +55,9 @@ pub struct ModalInteraction { /// Permissions the app or bot has within the channel the interaction was sent from. pub app_permissions: Option, /// The selected language of the invoking user. - pub locale: String, + pub locale: FixedString, /// The guild's preferred locale. - pub guild_locale: Option, + pub guild_locale: Option, /// For monetized applications, any entitlements of the invoking user. pub entitlements: Vec, } @@ -219,7 +219,7 @@ impl Serialize for ModalInteraction { #[non_exhaustive] pub struct ModalInteractionData { /// The custom id of the modal - pub custom_id: String, + pub custom_id: FixedString, /// The components. - pub components: Vec, + pub components: FixedArray, } diff --git a/src/model/application/ping_interaction.rs b/src/model/application/ping_interaction.rs index 269f377a0c8..c8bd3b14930 100644 --- a/src/model/application/ping_interaction.rs +++ b/src/model/application/ping_interaction.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; +use crate::internal::prelude::*; use crate::model::id::{ApplicationId, InteractionId}; /// A ping interaction, which can only be received through an endpoint url. @@ -14,7 +15,7 @@ pub struct PingInteraction { /// Id of the application this interaction is for. pub application_id: ApplicationId, /// A continuation token for responding to the interaction. - pub token: String, + pub token: FixedString, /// Always `1`. pub version: u8, } diff --git a/src/model/channel/attachment.rs b/src/model/channel/attachment.rs index 95cff064697..535ac8b8251 100644 --- a/src/model/channel/attachment.rs +++ b/src/model/channel/attachment.rs @@ -2,7 +2,6 @@ use reqwest::Client as ReqwestClient; use serde_cow::CowStr; -#[cfg(feature = "model")] use crate::internal::prelude::*; use crate::model::prelude::*; use crate::model::utils::is_false; @@ -37,23 +36,23 @@ pub struct Attachment { pub id: AttachmentId, /// The filename of the file that was uploaded. This is equivalent to what the uploader had /// their file named. - pub filename: String, + pub filename: FixedString, /// Description for the file (max 1024 characters). - pub description: Option, + pub description: Option>, /// If the attachment is an image, then the height of the image is provided. pub height: Option, /// The proxy URL. - pub proxy_url: String, + pub proxy_url: FixedString, /// The size of the file in bytes. pub size: u32, /// The URL of the uploaded attachment. - pub url: String, + pub url: FixedString, /// If the attachment is an image, then the width of the image is provided. pub width: Option, /// The attachment's [media type]. /// /// [media type]: https://en.wikipedia.org/wiki/Media_type - pub content_type: Option, + pub content_type: Option, /// Whether this attachment is ephemeral. /// /// Ephemeral attachments will automatically be removed after a set period of time. @@ -152,7 +151,7 @@ impl Attachment { /// [`Message`]: super::Message pub async fn download(&self) -> Result> { let reqwest = ReqwestClient::new(); - let bytes = reqwest.get(&self.url).send().await?.bytes().await?; + let bytes = reqwest.get(&*self.url).send().await?.bytes().await?; Ok(bytes.to_vec()) } } diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs index ed73c00ace4..1de3657194d 100644 --- a/src/model/channel/channel_id.rs +++ b/src/model/channel/channel_id.rs @@ -28,6 +28,7 @@ use crate::collector::{MessageCollector, ReactionCollector}; use crate::gateway::ShardMessenger; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http, Typing}; +use crate::internal::prelude::*; #[cfg(feature = "model")] use crate::json::json; use crate::model::prelude::*; @@ -531,7 +532,7 @@ impl ChannelId { /// # Errors /// /// Same as [`Self::to_channel()`]. - pub async fn name(self, cache_http: impl CacheHttp) -> Result { + pub async fn name(self, cache_http: impl CacheHttp) -> Result> { let channel = self.to_channel(cache_http).await?; Ok(match channel { diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs index 1fcf9962dda..23b924d8b2c 100644 --- a/src/model/channel/embed.rs +++ b/src/model/channel/embed.rs @@ -1,3 +1,4 @@ +use crate::internal::prelude::*; use crate::model::{Colour, Timestamp}; /// Represents a rich embed which allows using richer markdown, multiple fields and more. This was @@ -17,20 +18,20 @@ use crate::model::{Colour, Timestamp}; pub struct Embed { /// The title of the embed. #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, + pub title: Option>, /// The type of the embed. For embeds not generated by Discord's backend, this will always be /// "rich". #[serde(rename = "type")] #[serde(skip_serializing_if = "Option::is_none")] - pub kind: Option, + pub kind: Option>, /// The description of the embed. /// /// The maximum value for this field is 2048 unicode codepoints. #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, + pub description: Option>, /// The URL of the embed. #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, + pub url: Option, /// Timestamp information. #[serde(skip_serializing_if = "Option::is_none")] pub timestamp: Option, @@ -77,18 +78,18 @@ pub struct Embed { #[non_exhaustive] pub struct EmbedAuthor { /// The name of the author. - pub name: String, + pub name: FixedString, /// The URL of the author. #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, + pub url: Option, /// The URL of the author icon. /// /// This only supports HTTP(S) and attachments. #[serde(skip_serializing_if = "Option::is_none")] - pub icon_url: Option, + pub icon_url: Option, /// A proxied URL of the author icon. #[serde(skip_serializing_if = "Option::is_none")] - pub proxy_icon_url: Option, + pub proxy_icon_url: Option, } /// A field object in an embed. @@ -101,11 +102,11 @@ pub struct EmbedField { /// The name of the field. /// /// The maximum length of this field is 512 unicode codepoints. - pub name: String, + pub name: FixedString, /// The value of the field. /// /// The maximum length of this field is 1024 unicode codepoints. - pub value: String, + pub value: FixedString, /// Indicator of whether the field should display as inline. #[serde(default)] pub inline: bool, @@ -121,10 +122,14 @@ impl EmbedField { T: Into, U: Into, { - Self::new_(name.into(), value.into(), inline) + Self::new_(name.into().into(), value.into().into(), inline) } - pub(crate) const fn new_(name: String, value: String, inline: bool) -> Self { + pub(crate) const fn new_( + name: FixedString, + value: FixedString, + inline: bool, + ) -> Self { Self { name, value, @@ -141,15 +146,15 @@ impl EmbedField { #[non_exhaustive] pub struct EmbedFooter { /// The associated text with the footer. - pub text: String, + pub text: FixedString, /// The URL of the footer icon. /// /// This only supports HTTP(S) and attachments. #[serde(skip_serializing_if = "Option::is_none")] - pub icon_url: Option, + pub icon_url: Option, /// A proxied URL of the footer icon. #[serde(skip_serializing_if = "Option::is_none")] - pub proxy_icon_url: Option, + pub proxy_icon_url: Option, } /// An image object in an embed. @@ -162,9 +167,9 @@ pub struct EmbedImage { /// Source URL of the image. /// /// This only supports HTTP(S) and attachments. - pub url: String, + pub url: FixedString, /// A proxied URL of the image. - pub proxy_url: Option, + pub proxy_url: Option, /// The height of the image. pub height: Option, /// The width of the image. @@ -179,9 +184,9 @@ pub struct EmbedImage { #[non_exhaustive] pub struct EmbedProvider { /// The name of the provider. - pub name: Option, + pub name: Option, /// The URL of the provider. - pub url: Option, + pub url: Option, } /// The dimensions and URL of an embed thumbnail. @@ -194,9 +199,9 @@ pub struct EmbedThumbnail { /// The source URL of the thumbnail. /// /// This only supports HTTP(S) and attachments. - pub url: String, + pub url: FixedString, /// A proxied URL of the thumbnail. - pub proxy_url: Option, + pub proxy_url: Option, /// The height of the thumbnail in pixels. pub height: Option, /// The width of the thumbnail in pixels. @@ -211,9 +216,9 @@ pub struct EmbedThumbnail { #[non_exhaustive] pub struct EmbedVideo { /// The source URL of the video. - pub url: String, + pub url: FixedString, /// A proxied URL of the thumbnail. - pub proxy_url: Option, + pub proxy_url: Option, /// The height of the video in pixels. pub height: Option, /// The width of the video in pixels. diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index 1caaa8fa690..250bf5fac23 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -27,7 +27,6 @@ use crate::collector::{MessageCollector, ReactionCollector}; use crate::gateway::ShardMessenger; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http, Typing}; -#[cfg(all(feature = "cache", feature = "model"))] use crate::internal::prelude::*; use crate::model::prelude::*; @@ -74,11 +73,11 @@ pub struct GuildChannel { /// /// **Note**: This is only available for text channels. pub last_pin_timestamp: Option, - /// The name of the channel. - pub name: String, + /// The name of the channel. (1-100 characters) + pub name: FixedString, /// Permission overwrites for [`Member`]s and for [`Role`]s. #[serde(default)] - pub permission_overwrites: Vec, + pub permission_overwrites: FixedArray, /// The position of the channel. /// /// The default text channel will _almost always_ have a position of `0`. @@ -87,7 +86,7 @@ pub struct GuildChannel { /// The topic of the channel. /// /// **Note**: This is only available for text, forum and stage channels. - pub topic: Option, + pub topic: Option>, /// The maximum number of members allowed in the channel. /// /// **Note**: This is only available for voice channels. @@ -106,7 +105,7 @@ pub struct GuildChannel { /// /// **Note**: This is only available for voice and stage channels. [`None`] for voice and stage /// channels means automatic region selection. - pub rtc_region: Option, + pub rtc_region: Option>, /// The video quality mode for a voice channel. pub video_quality_mode: Option, /// An approximate count of messages in the thread. @@ -145,12 +144,12 @@ pub struct GuildChannel { /// /// **Note**: This is only available in forum channels. #[serde(default)] - pub available_tags: Vec, + pub available_tags: FixedArray, /// The set of applied tags. /// /// **Note**: This is only available in a thread in a forum. #[serde(default)] - pub applied_tags: Vec, + pub applied_tags: FixedArray, /// The emoji to show in the add reaction button /// /// **Note**: This is only available in a forum. @@ -163,7 +162,7 @@ pub struct GuildChannel { /// The status of a voice channel. /// /// **Note**: This is only available in voice channels. - pub status: Option, + pub status: Option>, /// The default sort order type used to order posts /// /// **Note**: This is only available in a forum. diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index d4c65f0540b..7d3169cf4bd 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -21,6 +21,7 @@ use crate::constants; use crate::gateway::ShardMessenger; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http}; +use crate::internal::prelude::*; use crate::model::prelude::*; use crate::model::utils::{discord_colours, StrOrInt}; #[cfg(all(feature = "model", feature = "cache"))] @@ -41,7 +42,7 @@ pub struct Message { /// The user that sent the message. pub author: User, /// The content of the message. - pub content: String, + pub content: FixedString, /// Initial message creation timestamp, calculated from its Id. pub timestamp: Timestamp, /// The timestamp of the last time the message was updated, if it was. @@ -53,9 +54,9 @@ pub struct Message { /// Indicator of whether the message mentions everyone. pub mention_everyone: bool, /// Array of users mentioned in the message. - pub mentions: Vec, + pub mentions: FixedArray, /// Array of [`Role`]s' Ids mentioned in the message. - pub mention_roles: Vec, + pub mention_roles: FixedArray, /// Channels specifically mentioned in this message. /// /// **Note**: Not all channel mentions in a message will appear in [`Self::mention_channels`]. @@ -73,15 +74,15 @@ pub struct Message { /// [Refer to Discord's documentation for more information][discord-docs]. /// /// [discord-docs]: https://discord.com/developers/docs/resources/channel#message-object - #[serde(default = "Vec::new")] - pub mention_channels: Vec, + #[serde(default)] + pub mention_channels: FixedArray, /// An vector of the files attached to a message. - pub attachments: Vec, + pub attachments: FixedArray, /// Array of embeds sent with the message. - pub embeds: Vec, + pub embeds: FixedArray, /// Array of reactions performed on the message. #[serde(default)] - pub reactions: Vec, + pub reactions: FixedArray, /// Non-repeating number used for ensuring message order. #[serde(default)] pub nonce: Option, @@ -116,10 +117,10 @@ pub struct Message { pub thread: Option>, /// The components of this message #[serde(default)] - pub components: Vec, + pub components: FixedArray, /// Array of message sticker item objects. #[serde(default)] - pub sticker_items: Vec, + pub sticker_items: FixedArray, /// A generally increasing integer (there may be gaps or duplicates) that represents the /// approximate position of the message in a thread, it can be used to estimate the relative /// position of the message in a thread in company with total_message_sent on parent thread. @@ -408,7 +409,7 @@ impl Message { /// names and everyone/here mentions cancelled. #[cfg(feature = "cache")] pub fn content_safe(&self, cache: impl AsRef) -> String { - let mut result = self.content.clone(); + let mut result = self.content.to_string(); // First replace all user mentions. for u in &self.mentions { @@ -1099,11 +1100,11 @@ pub struct MessageApplication { /// ID of the embed's image asset. pub cover_image: Option, /// Application's description. - pub description: String, + pub description: FixedString, /// ID of the application's icon. pub icon: Option, /// Name of the application. - pub name: String, + pub name: FixedString, } /// Rich Presence activity information. @@ -1117,7 +1118,7 @@ pub struct MessageActivity { #[serde(rename = "type")] pub kind: MessageActivityKind, /// `party_id` from a Rich Presence event. - pub party_id: Option, + pub party_id: Option, } enum_number! { @@ -1226,7 +1227,7 @@ pub struct ChannelMention { #[serde(rename = "type")] pub kind: ChannelType, /// The name of the channel - pub name: String, + pub name: FixedString, } bitflags! { @@ -1328,7 +1329,7 @@ pub struct RoleSubscriptionData { /// The id of the sku and listing that the user is subscribed to. pub role_subscription_listing_id: SkuId, /// The name of the tier that the user is subscribed to. - pub tier_name: String, + pub tier_name: FixedString, /// The cumulative number of months that the user has been subscribed for. pub total_months_subscribed: u16, /// Whether this notification is for a renewal rather than a new purchase. @@ -1468,6 +1469,7 @@ mod tests { use std::collections::HashMap; use dashmap::DashMap; + use small_fixed_array::FixedArray; use super::{ Guild, @@ -1494,11 +1496,11 @@ mod tests { // Channel with the message, with SEND_MESSAGES on. let channel = GuildChannel { - permission_overwrites: vec![PermissionOverwrite { + permission_overwrites: FixedArray::from_vec_trunc(vec![PermissionOverwrite { allow: Permissions::SEND_MESSAGES, deny: Permissions::default(), kind: PermissionOverwriteType::Member(author.id), - }], + }]), ..Default::default() }; let channel_id = channel.id; diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 821a5b60c15..519b3a7568e 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -24,6 +24,7 @@ pub use self::private_channel::*; pub use self::reaction::*; #[cfg(feature = "model")] use crate::http::CacheHttp; +use crate::internal::prelude::*; use crate::json::*; use crate::model::prelude::*; use crate::model::utils::is_false; @@ -423,7 +424,7 @@ pub struct StageInstance { /// The Id of the associated stage channel. pub channel_id: ChannelId, /// The topic of the stage instance. - pub topic: String, + pub topic: FixedString, /// The privacy level of the Stage instance. pub privacy_level: StageInstancePrivacyLevel, /// Whether or not Stage Discovery is disabled (deprecated). @@ -472,9 +473,9 @@ pub struct ThreadMetadata { #[non_exhaustive] pub struct ThreadsData { /// The threads channels. - pub threads: Vec, + pub threads: FixedArray, /// A thread member for each returned thread the current user has joined. - pub members: Vec, + pub members: FixedArray, /// Whether there are potentially more threads that could be returned on a subsequent call. #[serde(default)] pub has_more: bool, @@ -491,13 +492,13 @@ pub enum ForumEmoji { /// The id of a guild's custom emoji. Id(EmojiId), /// The unicode character of the emoji. - Name(String), + Name(FixedString), } #[derive(Deserialize)] struct RawForumEmoji { emoji_id: Option, - emoji_name: Option, + emoji_name: Option, } impl serde::Serialize for ForumEmoji { @@ -544,7 +545,7 @@ pub struct ForumTag { /// The id of the tag. pub id: ForumTagId, /// The name of the tag (0-20 characters). - pub name: String, + pub name: FixedString, /// Whether this tag can only be added to or removed from threads by a member with the /// MANAGE_THREADS permission. pub moderated: bool, diff --git a/src/model/channel/partial_channel.rs b/src/model/channel/partial_channel.rs index 663eac6d052..1e544c9d115 100644 --- a/src/model/channel/partial_channel.rs +++ b/src/model/channel/partial_channel.rs @@ -1,3 +1,4 @@ +use crate::internal::prelude::*; use crate::model::channel::{ChannelType, ThreadMetadata}; use crate::model::id::{ChannelId, WebhookId}; use crate::model::Permissions; @@ -13,7 +14,7 @@ pub struct PartialChannel { /// The channel Id. pub id: ChannelId, /// The channel name. - pub name: Option, + pub name: Option, /// The channel type. #[serde(rename = "type")] pub kind: ChannelType, diff --git a/src/model/channel/private_channel.rs b/src/model/channel/private_channel.rs index 6eb441a41d0..cf18664a5c5 100644 --- a/src/model/channel/private_channel.rs +++ b/src/model/channel/private_channel.rs @@ -8,6 +8,7 @@ use crate::builder::{CreateAttachment, CreateMessage, EditMessage, GetMessages}; use crate::http::CacheHttp; #[cfg(feature = "model")] use crate::http::{Http, Typing}; +use crate::internal::prelude::*; use crate::model::prelude::*; use crate::model::utils::single_recipient; @@ -199,8 +200,8 @@ impl PrivateChannel { /// Returns "DM with $username#discriminator". #[must_use] - pub fn name(&self) -> String { - format!("DM with {}", self.recipient.tag()) + pub fn name(&self) -> FixedString { + format!("DM with {}", self.recipient.tag()).into() } /// Gets the list of [`User`]s who have reacted to a [`Message`] with a certain [`Emoji`]. diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs index 1cc02c92df8..293cef013ee 100644 --- a/src/model/channel/reaction.rs +++ b/src/model/channel/reaction.rs @@ -295,10 +295,10 @@ pub enum ReactionType { id: EmojiId, /// The name of the custom emoji. This is primarily used for decoration and distinguishing /// the emoji client-side. - name: Option, + name: Option, }, /// A reaction with a twemoji. - Unicode(String), + Unicode(FixedString), } // Manual impl needed to decide enum variant by presence of `id` @@ -309,7 +309,7 @@ impl<'de> Deserialize<'de> for ReactionType { #[serde(default)] animated: bool, id: Option, - name: Option, + name: Option, } let emoji = PartialEmoji::deserialize(deserializer)?; Ok(match (emoji.id, emoji.name) { @@ -383,7 +383,7 @@ impl ReactionType { #[must_use] pub fn unicode_eq(&self, other: &str) -> bool { if let ReactionType::Unicode(unicode) = &self { - unicode == other + &**unicode == other } else { // Always return false if not a unicode reaction false @@ -425,7 +425,7 @@ impl From for ReactionType { /// # fn main() {} /// ``` fn from(ch: char) -> ReactionType { - ReactionType::Unicode(ch.to_string()) + ReactionType::Unicode(ch.to_string().into()) } } @@ -444,7 +444,7 @@ impl From for ReactionType { ReactionType::Custom { animated: false, id: emoji_id, - name: Some("emoji".to_string()), + name: Some("emoji".to_string().into()), } } } @@ -479,7 +479,7 @@ impl TryFrom for ReactionType { } if !emoji_string.starts_with('<') { - return Ok(ReactionType::Unicode(emoji_string)); + return Ok(ReactionType::Unicode(emoji_string.into())); } ReactionType::try_from(&emoji_string[..]) } @@ -519,7 +519,7 @@ impl TryFrom<&str> for ReactionType { /// let reaction2 = ReactionType::Custom { /// animated: false, /// id: EmojiId::new(600404340292059257), - /// name: Some("customemoji".to_string()), + /// name: Some("customemoji".to_string().into()), /// }; /// /// assert_eq!(reaction, reaction2); @@ -532,7 +532,7 @@ impl TryFrom<&str> for ReactionType { } if !emoji_str.starts_with('<') { - return Ok(ReactionType::Unicode(emoji_str.to_string())); + return Ok(ReactionType::Unicode(emoji_str.to_string().into())); } if !emoji_str.ends_with('>') { @@ -544,7 +544,7 @@ impl TryFrom<&str> for ReactionType { let mut split_iter = emoji_str.split(':'); let animated = split_iter.next().ok_or(ReactionConversionError)? == "a"; - let name = split_iter.next().ok_or(ReactionConversionError)?.to_string().into(); + let name = Some(split_iter.next().ok_or(ReactionConversionError)?.to_string().into()); let id = split_iter.next().and_then(|s| s.parse().ok()).ok_or(ReactionConversionError)?; Ok(ReactionType::Custom { diff --git a/src/model/connection.rs b/src/model/connection.rs index 7968fccdd50..338e4c03727 100644 --- a/src/model/connection.rs +++ b/src/model/connection.rs @@ -1,6 +1,7 @@ //! Models for user connections. use super::prelude::*; +use crate::internal::prelude::*; /// Information about a connection between the current user and a third party service. /// @@ -9,20 +10,20 @@ use super::prelude::*; #[non_exhaustive] pub struct Connection { /// The ID of the account on the other side of this connection. - pub id: String, + pub id: FixedString, /// The username of the account on the other side of this connection. - pub name: String, + pub name: FixedString, /// The service that this connection represents (e.g. twitch, youtube) /// /// [Discord docs](https://discord.com/developers/docs/resources/user#connection-object-services). #[serde(rename = "type")] - pub kind: String, + pub kind: FixedString, /// Whether this connection has been revoked and is no longer valid. #[serde(default)] pub revoked: bool, /// A list of partial guild [`Integration`]s that use this connection. #[serde(default)] - pub integrations: Vec, + pub integrations: FixedArray, /// Whether this connection has been verified and the user has proven they own the account. pub verified: bool, /// Whether friend sync is enabled for this connection. diff --git a/src/model/event.rs b/src/model/event.rs index 159c8f1323e..ac5ad8e189d 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -19,6 +19,8 @@ use crate::model::utils::{ remove_from_map_opt, stickers, }; +use crate::constants::Opcode; +use crate::internal::prelude::*; /// Requires no gateway intents. /// @@ -247,9 +249,9 @@ pub struct GuildMemberRemoveEvent { #[non_exhaustive] pub struct GuildMemberUpdateEvent { pub guild_id: GuildId, - pub nick: Option, + pub nick: Option>, pub joined_at: Timestamp, - pub roles: Vec, + pub roles: FixedArray, pub user: User, pub premium_since: Option, #[serde(default)] @@ -283,12 +285,12 @@ pub struct GuildMembersChunkEvent { /// When passing an invalid ID to [`crate::gateway::ShardRunnerMessage::ChunkGuild`], it will /// be returned here. #[serde(default)] - pub not_found: Vec, + pub not_found: FixedArray, /// When passing true to [`crate::gateway::ShardRunnerMessage::ChunkGuild`], presences of the /// returned members will be here. pub presences: Option>, /// Nonce used in the [`crate::gateway::ShardRunnerMessage::ChunkGuild`] request. - pub nonce: Option, + pub nonce: Option, } // Manual impl needed to insert guild_id fields in Member @@ -390,7 +392,7 @@ pub struct InviteCreateEvent { /// Channel the invite is for. pub channel_id: ChannelId, /// Unique invite [code](Invite::code). - pub code: String, + pub code: FixedString, /// Time at which the invite was created. pub created_at: Timestamp, /// Guild of the invite. @@ -422,7 +424,7 @@ pub struct InviteCreateEvent { pub struct InviteDeleteEvent { pub channel_id: ChannelId, pub guild_id: Option, - pub code: String, + pub code: FixedString, } /// Requires [`GatewayIntents::GUILDS`]. @@ -457,7 +459,7 @@ pub struct MessageCreateEvent { pub struct MessageDeleteBulkEvent { pub guild_id: Option, pub channel_id: ChannelId, - pub ids: Vec, + pub ids: FixedArray, } /// Requires [`GatewayIntents::GUILD_MESSAGES`] or [`GatewayIntents::DIRECT_MESSAGES`]. @@ -498,17 +500,17 @@ pub struct MessageUpdateEvent { pub id: MessageId, pub channel_id: ChannelId, pub author: Option, - pub content: Option, + pub content: Option>, pub timestamp: Option, pub edited_timestamp: Option, pub tts: Option, pub mention_everyone: Option, - pub mentions: Option>, - pub mention_roles: Option>, - pub mention_channels: Option>, - pub attachments: Option>, - pub embeds: Option>, - pub reactions: Option>, + pub mentions: Option>, + pub mention_roles: Option>, + pub mention_channels: Option>, + pub attachments: Option>, + pub embeds: Option>, + pub reactions: Option>, pub pinned: Option, #[serde(default, deserialize_with = "deserialize_some")] pub webhook_id: Option>, @@ -531,8 +533,8 @@ pub struct MessageUpdateEvent { pub interaction_metadata: Option>>, #[serde(default, deserialize_with = "deserialize_some")] pub thread: Option>>, - pub components: Option>, - pub sticker_items: Option>, + pub components: Option>, + pub sticker_items: Option>, pub position: Option>, pub role_subscription_data: Option>, pub guild_id: Option, @@ -638,7 +640,7 @@ pub struct PresenceUpdateEvent { #[serde(transparent)] #[non_exhaustive] pub struct PresencesReplaceEvent { - pub presences: Vec, + pub presences: FixedArray, } /// Requires [`GatewayIntents::GUILD_MESSAGE_REACTIONS`] or @@ -738,7 +740,7 @@ pub struct TypingStartEvent { #[non_exhaustive] pub struct UnknownEvent { #[serde(rename = "t")] - pub kind: String, + pub kind: FixedString, #[serde(rename = "d")] pub value: Value, } @@ -763,9 +765,9 @@ pub struct UserUpdateEvent { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct VoiceServerUpdateEvent { - pub token: String, + pub token: FixedString, pub guild_id: Option, - pub endpoint: Option, + pub endpoint: Option, } /// Requires [`GatewayIntents::GUILD_VOICE_STATES`]. @@ -786,7 +788,7 @@ pub struct VoiceStateUpdateEvent { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct VoiceChannelStatusUpdateEvent { - pub status: Option, + pub status: Option>, pub id: ChannelId, pub guild_id: GuildId, } @@ -927,10 +929,10 @@ pub struct ThreadListSyncEvent { /// well, so you know to clear that data. pub channel_ids: Option>, /// All active threads in the given channels that the current user can access. - pub threads: Vec, + pub threads: FixedArray, /// All thread member objects from the synced threads for the current user, indicating which /// threads the current user has been added to - pub members: Vec, + pub members: FixedArray, } /// Requires [`GatewayIntents::GUILDS`], and, to receive this event for other users, @@ -963,10 +965,10 @@ pub struct ThreadMembersUpdateEvent { pub member_count: i16, /// The users who were added to the thread. #[serde(default)] - pub added_members: Vec, + pub added_members: FixedArray, /// The ids of the users who were removed from the thread. #[serde(default)] - pub removed_member_ids: Vec, + pub removed_member_ids: FixedArray, } /// Requires [`GatewayIntents::GUILD_SCHEDULED_EVENTS`]. diff --git a/src/model/gateway.rs b/src/model/gateway.rs index cfc8e9998c0..8431531b6f0 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -7,6 +7,7 @@ use url::Url; use super::prelude::*; use super::utils::*; +use crate::internal::prelude::*; /// A representation of the data retrieved from the bot gateway endpoint. /// @@ -20,7 +21,7 @@ use super::utils::*; #[non_exhaustive] pub struct BotGateway { /// The gateway to connect to. - pub url: String, + pub url: FixedString, /// The number of shards that is recommended to be used by the current bot user. pub shards: NonZeroU16, /// Information describing how many gateway sessions you can initiate within a ratelimit @@ -42,7 +43,7 @@ pub struct Activity { /// Images for the presence and their texts. pub assets: Option, /// What the user is doing. - pub details: Option, + pub details: Option, /// Activity flags describing what the payload includes. pub flags: Option, /// Whether or not the activity is an instanced game session. @@ -51,13 +52,13 @@ pub struct Activity { #[serde(rename = "type")] pub kind: ActivityType, /// The name of the activity. - pub name: String, + pub name: FixedString, /// Information about the user's current party. pub party: Option, /// Secrets for Rich Presence joining and spectating. pub secrets: Option, /// The user's current party status. - pub state: Option, + pub state: Option, /// Emoji currently used in custom status pub emoji: Option, /// Unix timestamps for the start and/or end times of the activity. @@ -65,18 +66,18 @@ pub struct Activity { /// The sync ID of the activity. Mainly used by the Spotify activity type which uses this /// parameter to store the track ID. #[cfg(feature = "unstable_discord_api")] - pub sync_id: Option, + pub sync_id: Option, /// The session ID of the activity. Reserved for specific activity types, such as the Activity /// that is transmitted when a user is listening to Spotify. #[cfg(feature = "unstable_discord_api")] - pub session_id: Option, + pub session_id: Option, /// The Stream URL if [`Self::kind`] is [`ActivityType::Streaming`]. pub url: Option, /// The buttons of this activity. /// /// **Note**: There can only be up to 2 buttons. #[serde(default, deserialize_with = "deserialize_buttons")] - pub buttons: Vec, + pub buttons: FixedArray, /// Unix timestamp (in milliseconds) of when the activity was added to the user's session pub created_at: u64, } @@ -87,12 +88,12 @@ pub struct Activity { #[non_exhaustive] pub struct ActivityButton { /// The text shown on the button. - pub label: String, + pub label: FixedString, /// The url opened when clicking the button. /// /// **Note**: Bots cannot access activity button URL. #[serde(default)] - pub url: String, + pub url: FixedString, } /// The assets for an activity. @@ -103,13 +104,13 @@ pub struct ActivityButton { #[non_exhaustive] pub struct ActivityAssets { /// The ID for a large asset of the activity, usually a snowflake. - pub large_image: Option, + pub large_image: Option, /// Text displayed when hovering over the large image of the activity. - pub large_text: Option, + pub large_text: Option, /// The ID for a small asset of the activity, usually a snowflake. - pub small_image: Option, + pub small_image: Option, /// Text displayed when hovering over the small image of the activity. - pub small_text: Option, + pub small_text: Option, } bitflags! { @@ -148,7 +149,7 @@ bitflags! { #[non_exhaustive] pub struct ActivityParty { /// The ID of the party. - pub id: Option, + pub id: Option, /// Used to show the party's current and maximum size. pub size: Option<[u32; 2]>, } @@ -161,12 +162,12 @@ pub struct ActivityParty { #[non_exhaustive] pub struct ActivitySecrets { /// The secret for joining a party. - pub join: Option, + pub join: Option, /// The secret for a specific instanced match. #[serde(rename = "match")] - pub match_: Option, + pub match_: Option, /// The secret for spectating an activity. - pub spectate: Option, + pub spectate: Option, } /// Representation of an emoji used in a custom status @@ -177,7 +178,7 @@ pub struct ActivitySecrets { #[non_exhaustive] pub struct ActivityEmoji { /// The name of the emoji. - pub name: String, + pub name: FixedString, /// The id of the emoji. pub id: Option, /// Whether this emoji is animated. @@ -217,7 +218,7 @@ enum_number! { #[non_exhaustive] pub struct Gateway { /// The gateway to connect to. - pub url: String, + pub url: FixedString, } /// Information detailing the current active status of a [`User`]. @@ -248,10 +249,10 @@ pub struct PresenceUser { pub bot: Option, #[serde(default, skip_serializing_if = "Option::is_none", with = "discriminator")] pub discriminator: Option, - pub email: Option, + pub email: Option, pub mfa_enabled: Option, #[serde(rename = "username")] - pub name: Option, + pub name: Option>, pub verified: Option, pub public_flags: Option, } @@ -323,7 +324,7 @@ pub struct Presence { pub status: OnlineStatus, /// [`User`]'s current activities. #[serde(default)] - pub activities: Vec, + pub activities: FixedArray, /// The devices a user are currently active on, if available. pub client_status: Option, } @@ -341,11 +342,11 @@ pub struct Ready { /// Information about the user including email pub user: CurrentUser, /// Guilds the user is in - pub guilds: Vec, + pub guilds: FixedArray, /// Used for resuming connections - pub session_id: String, + pub session_id: FixedString, /// Gateway URL for resuming connections - pub resume_gateway_url: String, + pub resume_gateway_url: FixedString, /// Shard information associated with this session, if sent when identifying pub shard: Option, /// Contains id and flags diff --git a/src/model/guild/audit_log/change.rs b/src/model/guild/audit_log/change.rs index f5a57ed747e..e091e68ae00 100644 --- a/src/model/guild/audit_log/change.rs +++ b/src/model/guild/audit_log/change.rs @@ -1,3 +1,4 @@ +use crate::internal::prelude::*; use crate::json::Value; use crate::model::channel::PermissionOverwrite; use crate::model::guild::automod::{Action, EventType, TriggerMetadata, TriggerType}; @@ -20,7 +21,7 @@ use crate::model::{Permissions, Timestamp}; #[non_exhaustive] pub struct AffectedRole { pub id: RoleId, - pub name: String, + pub name: FixedString, } #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] @@ -89,7 +90,7 @@ macro_rules! generate_change { /// Unknown key was changed. Other { - name: String, + name: FixedString, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "old_value")] old_value: Option, diff --git a/src/model/guild/audit_log/mod.rs b/src/model/guild/audit_log/mod.rs index c3cb7454cc7..832fb583d09 100644 --- a/src/model/guild/audit_log/mod.rs +++ b/src/model/guild/audit_log/mod.rs @@ -10,6 +10,7 @@ mod utils; pub use change::{AffectedRole, Change, EntityType}; use utils::{optional_string, users, webhooks}; +use crate::internal::prelude::*; use crate::model::prelude::*; /// Determines the action that was done on a target. @@ -295,20 +296,20 @@ pub enum VoiceChannelStatusAction { pub struct AuditLogs { /// List of audit log entries, sorted from most to least recent. #[serde(rename = "audit_log_entries")] - pub entries: Vec, + pub entries: FixedArray, /// List of auto moderation rules referenced in the audit log. - pub auto_moderation_rules: Vec, + pub auto_moderation_rules: FixedArray, /// List of application commands referenced in the audit log. - pub application_commands: Vec, + pub application_commands: FixedArray, /// List of guild scheduled events referenced in the audit log. - pub guild_scheduled_events: Vec, + pub guild_scheduled_events: FixedArray, /// List of partial integration objects. - pub integrations: Vec, + pub integrations: FixedArray, /// List of threads referenced in the audit log. /// /// Threads referenced in THREAD_CREATE and THREAD_UPDATE events are included in the threads /// map since archived threads might not be kept in memory by clients. - pub threads: Vec, + pub threads: FixedArray, /// List of users referenced in the audit log. #[serde(with = "users")] pub users: HashMap, @@ -325,9 +326,9 @@ pub struct AuditLogs { #[non_exhaustive] pub struct PartialIntegration { pub id: IntegrationId, - pub name: String, + pub name: FixedString, #[serde(rename = "type")] - pub kind: String, + pub kind: FixedString, pub account: IntegrationAccount, pub application: Option, } @@ -343,7 +344,7 @@ pub struct AuditLogEntry { #[serde(rename = "action_type")] pub action: Action, /// What was the reasoning by doing an action on a target? If there was one. - pub reason: Option, + pub reason: Option, /// The user that did this action on a target. pub user_id: UserId, /// What changes were made. @@ -361,9 +362,9 @@ pub struct AuditLogEntry { #[non_exhaustive] pub struct Options { /// Name of the Auto Moderation rule that was triggered. - pub auto_moderation_rule_name: Option, + pub auto_moderation_rule_name: Option, /// Trigger type of the Auto Moderation rule that was triggered. - pub auto_moderation_rule_trigger_type: Option, + pub auto_moderation_rule_trigger_type: Option, /// ID of the app whose permissions were targeted. pub application_id: Option, /// Number of days after which inactive members were kicked. @@ -383,16 +384,16 @@ pub struct Options { pub id: Option, /// Type of overwritten entity ("member" or "role"). #[serde(default, rename = "type")] - pub kind: Option, + pub kind: Option, /// Message that was pinned or unpinned. #[serde(default)] pub message_id: Option, /// Name of the role if type is "role" #[serde(default)] - pub role_name: Option, + pub role_name: Option, /// The status of a voice channel when set. #[serde(default)] - pub status: Option, + pub status: Option, } #[cfg(test)] diff --git a/src/model/guild/automod.rs b/src/model/guild/automod.rs index 7c169a8ca35..7c2ab6f722e 100644 --- a/src/model/guild/automod.rs +++ b/src/model/guild/automod.rs @@ -8,6 +8,7 @@ use serde::de::{Deserializer, Error}; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; +use crate::internal::prelude::*; use crate::model::id::*; /// Configured auto moderation rule. @@ -23,7 +24,7 @@ pub struct Rule { /// ID of the guild this rule belongs to. pub guild_id: GuildId, /// Name of the rule. - pub name: String, + pub name: FixedString, /// ID of the user which created the rule. pub creator_id: UserId, /// Event context in which the rule should be checked. @@ -32,17 +33,17 @@ pub struct Rule { #[serde(flatten)] pub trigger: Trigger, /// Actions which will execute when the rule is triggered. - pub actions: Vec, + pub actions: FixedArray, /// Whether the rule is enabled. pub enabled: bool, /// Roles that should not be affected by the rule. /// /// Maximum of 20. - pub exempt_roles: Vec, + pub exempt_roles: FixedArray, /// Channels that should not be affected by the rule. /// /// Maximum of 50. - pub exempt_channels: Vec, + pub exempt_channels: FixedArray, } /// Indicates in what event context a rule should be checked. @@ -330,7 +331,7 @@ pub enum Action { /// Additional explanation that will be shown to members whenever their message is blocked /// /// Maximum of 150 characters - custom_message: Option, + custom_message: Option>, }, /// Logs user content to a specified channel. Alert(ChannelId), @@ -381,15 +382,15 @@ pub struct ActionExecution { /// Requires [`GatewayIntents::MESSAGE_CONTENT`] to receive non-empty values. /// /// [`GatewayIntents::MESSAGE_CONTENT`]: crate::model::gateway::GatewayIntents::MESSAGE_CONTENT - pub content: String, + pub content: FixedString, /// Word or phrase configured in the rule that triggered the rule. - pub matched_keyword: Option, + pub matched_keyword: Option, /// Substring in content that triggered the rule. /// /// Requires [`GatewayIntents::MESSAGE_CONTENT`] to receive non-empty values. /// /// [`GatewayIntents::MESSAGE_CONTENT`]: crate::model::gateway::GatewayIntents::MESSAGE_CONTENT - pub matched_content: Option, + pub matched_content: Option, } /// Helper struct for the (de)serialization of `Action`. @@ -400,7 +401,7 @@ struct RawActionMetadata { #[serde(skip_serializing_if = "Option::is_none")] duration_seconds: Option, #[serde(skip_serializing_if = "Option::is_none")] - custom_message: Option, + custom_message: Option>, } /// Helper struct for the (de)serialization of `Action`. diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs index a9572c41a20..6e896694c0f 100644 --- a/src/model/guild/emoji.rs +++ b/src/model/guild/emoji.rs @@ -1,5 +1,6 @@ use std::fmt; +use crate::internal::prelude::*; use crate::model::id::{EmojiId, RoleId}; use crate::model::user::User; use crate::model::utils::default_true; @@ -23,7 +24,7 @@ pub struct Emoji { pub id: EmojiId, /// The name of the emoji. It must be at least 2 characters long and can only contain /// alphanumeric characters and underscores. - pub name: String, + pub name: FixedString, /// Whether the emoji is managed via an [`Integration`] service. /// /// [`Integration`]: super::Integration @@ -37,7 +38,7 @@ pub struct Emoji { /// /// [`Role`]: super::Role #[serde(default)] - pub roles: Vec, + pub roles: FixedArray, /// The user who created the emoji. pub user: Option, } diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs index 60d9754d596..9e5202a0c9c 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -1200,7 +1200,7 @@ impl GuildId { #[cfg(feature = "cache")] #[must_use] pub fn name(self, cache: impl AsRef) -> Option { - self.to_guild_cached(cache.as_ref()).map(|g| g.name.clone()) + self.to_guild_cached(cache.as_ref()).map(|g| g.name.to_string()) } /// Disconnects a member from a voice channel in the guild. diff --git a/src/model/guild/guild_preview.rs b/src/model/guild/guild_preview.rs index 75aedcbb736..5514311848e 100644 --- a/src/model/guild/guild_preview.rs +++ b/src/model/guild/guild_preview.rs @@ -1,3 +1,4 @@ +use crate::internal::prelude::*; use crate::model::guild::Emoji; use crate::model::id::GuildId; use crate::model::misc::ImageHash; @@ -14,7 +15,7 @@ pub struct GuildPreview { /// The guild Id. pub id: GuildId, /// The guild name. - pub name: String, + pub name: FixedString, /// The guild icon hash if it has one. pub icon: Option, /// The guild splash hash if it has one. @@ -22,17 +23,17 @@ pub struct GuildPreview { /// The guild discovery splash hash it it has one. pub discovery_splash: Option, /// The custom guild emojis. - pub emojis: Vec, + pub emojis: FixedArray, /// The guild features. See [`Guild::features`] /// /// [`Guild::features`]: super::Guild::features - pub features: Vec, + pub features: FixedArray, /// Approximate number of members in this guild. pub approximate_member_count: u64, /// Approximate number of online members in this guild. pub approximate_presence_count: u64, /// The description for the guild, if the guild has the `DISCOVERABLE` feature. - pub description: Option, + pub description: Option, /// Custom guild stickers. - pub stickers: Vec, + pub stickers: FixedArray, } diff --git a/src/model/guild/integration.rs b/src/model/guild/integration.rs index 2c1c6e29540..0710a23c344 100644 --- a/src/model/guild/integration.rs +++ b/src/model/guild/integration.rs @@ -10,9 +10,9 @@ use crate::model::prelude::*; #[non_exhaustive] pub struct Integration { pub id: IntegrationId, - pub name: String, + pub name: FixedString, #[serde(rename = "type")] - pub kind: String, + pub kind: FixedString, pub enabled: bool, pub syncing: Option, pub role_id: Option, @@ -60,8 +60,8 @@ impl From for IntegrationId { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct IntegrationAccount { - pub id: String, - pub name: String, + pub id: FixedString, + pub name: FixedString, } /// Integration application object. @@ -72,8 +72,8 @@ pub struct IntegrationAccount { #[non_exhaustive] pub struct IntegrationApplication { pub id: ApplicationId, - pub name: String, + pub name: FixedString, pub icon: Option, - pub description: String, + pub description: FixedString, pub bot: Option, } diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs index d9726496eca..b2fb6b91f44 100644 --- a/src/model/guild/member.rs +++ b/src/model/guild/member.rs @@ -8,7 +8,6 @@ use crate::builder::EditMember; use crate::cache::Cache; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http}; -#[cfg(all(feature = "cache", feature = "model"))] use crate::internal::prelude::*; use crate::model::prelude::*; #[cfg(feature = "model")] @@ -27,11 +26,11 @@ pub struct Member { /// The member's nickname, if present. /// /// Can't be longer than 32 characters. - pub nick: Option, + pub nick: Option>, /// The guild avatar hash pub avatar: Option, /// Vector of Ids of [`Role`]s given to the member. - pub roles: Vec, + pub roles: FixedArray, /// Timestamp representing the date when the member joined. pub joined_at: Option, /// Timestamp representing the date since the member is boosting the guild. @@ -576,9 +575,9 @@ pub struct PartialMember { /// The member's nickname, if present. /// /// Can't be longer than 32 characters. - pub nick: Option, + pub nick: Option>, /// Vector of Ids of [`Role`]s given to the member. - pub roles: Vec, + pub roles: FixedArray, /// Indicator that the member hasn't accepted the rules of the guild yet. #[serde(default)] pub pending: bool, diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index bfe345c556d..1820ae85df3 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -58,6 +58,7 @@ use crate::constants::LARGE_THRESHOLD; use crate::gateway::ShardMessenger; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http, UserPagination}; +use crate::internal::prelude::*; #[cfg(feature = "model")] use crate::json::json; use crate::model::prelude::*; @@ -70,7 +71,7 @@ use crate::model::utils::*; #[non_exhaustive] pub struct Ban { /// The reason given for this ban. - pub reason: Option, + pub reason: Option, /// The user that was banned. pub user: User, } @@ -111,7 +112,7 @@ pub struct Guild { /// This is equivalent to the Id of the default role (`@everyone`). pub id: GuildId, /// The name of the guild. - pub name: String, + pub name: FixedString, /// The hash of the icon used by the guild. /// /// In the client, this appears on the guild list on the left-hand side. @@ -183,7 +184,7 @@ pub struct Guild { /// /// /// [`discord documentation`]: https://discord.com/developers/docs/resources/guild#guild-object-guild-features - pub features: Vec, + pub features: FixedArray, /// Indicator of whether the guild requires multi-factor authentication for [`Role`]s or /// [`User`]s with moderation permissions. pub mfa_level: MfaLevel, @@ -204,18 +205,18 @@ pub struct Guild { /// The maximum number of members for the guild. pub max_members: Option, /// The vanity url code for the guild, if it has one. - pub vanity_url_code: Option, + pub vanity_url_code: Option, /// The server's description, if it has one. - pub description: Option, + pub description: Option, /// The guild's banner, if it has one. - pub banner: Option, + pub banner: Option, /// The server's premium boosting level. pub premium_tier: PremiumTier, /// The total number of users currently boosting this server. pub premium_subscription_count: Option, /// The preferred locale of this guild only set if guild has the "DISCOVERABLE" feature, /// defaults to en-US. - pub preferred_locale: String, + pub preferred_locale: FixedString, /// The id of the channel where admins and moderators of Community guilds receive notices from /// Discord. /// @@ -277,17 +278,17 @@ pub struct Guild { /// A thread is guaranteed (for errors, not for panics) to be cached if a `MESSAGE_CREATE` /// event is fired in said thread, however an `INTERACTION_CREATE` may not have a private /// thread in cache. - pub threads: Vec, + pub threads: FixedArray, /// A mapping of [`User`]s' Ids to their current presences. /// /// **Note**: This will be empty unless the "guild presences" privileged intent is enabled. #[serde(with = "presences")] pub presences: HashMap, /// The stage instances in this guild. - pub stage_instances: Vec, + pub stage_instances: FixedArray, /// The stage instances in this guild. #[serde(rename = "guild_scheduled_events")] - pub scheduled_events: Vec, + pub scheduled_events: FixedArray, } #[cfg(feature = "model")] @@ -454,7 +455,7 @@ impl Guild { ) -> Option { let name = name.as_ref(); - self.channels.values().find(|c| c.name == name).map(|c| c.id) + self.channels.values().find(|c| &*c.name == name).map(|c| c.id) } /// Ban a [`User`] from the guild, deleting a number of days' worth of messages (`dmd`) between @@ -1701,14 +1702,14 @@ impl Guild { }; for member in self.members.values() { - if member.user.name == username + if &*member.user.name == username && discrim.map_or(true, |d| member.user.discriminator == d) { return Some(member); } } - self.members.values().find(|member| member.nick.as_ref().is_some_and(|nick| nick == name)) + self.members.values().find(|member| member.nick.as_deref().is_some_and(|nick| nick == name)) } /// Retrieves all [`Member`] that start with a given [`String`]. @@ -1730,7 +1731,7 @@ impl Guild { prefix: &str, case_sensitive: bool, sorted: bool, - ) -> Vec<(&Member, String)> { + ) -> Vec<(&Member, &str)> { fn starts_with(name: &str, prefix: &str, case_sensitive: bool) -> bool { if case_sensitive { name.starts_with(prefix) @@ -1746,19 +1747,19 @@ impl Guild { let username = &member.user.name; if starts_with(username, prefix, case_sensitive) { - Some((member, username.clone())) + Some((member, username.as_str())) } else { match &member.nick { Some(nick) => starts_with(nick, prefix, case_sensitive) - .then(|| (member, nick.clone())), + .then(|| (member, nick.as_str())), None => None, } } }) - .collect::>(); + .collect::>(); if sorted { - members.sort_by(|a, b| closest_to_origin(prefix, &a.1[..], &b.1[..])); + members.sort_by(|a, b| closest_to_origin(prefix, a.1, b.1)); } members @@ -1803,11 +1804,11 @@ impl Guild { let username = &member.user.name; if contains(username, substring, case_sensitive) { - Some((member, username.clone())) + Some((member, username.clone().into())) } else { match &member.nick { Some(nick) => contains(nick, substring, case_sensitive) - .then(|| (member, nick.clone())), + .then(|| (member, nick.clone().into())), None => None, } } @@ -1853,7 +1854,7 @@ impl Guild { .values() .filter_map(|member| { let name = &member.user.name; - contains(name, substring, case_sensitive).then(|| (member, name.clone())) + contains(name, substring, case_sensitive).then(|| (member, name.clone().into())) }) .collect::>(); @@ -1898,7 +1899,7 @@ impl Guild { .values() .filter_map(|member| { let nick = member.nick.as_ref().unwrap_or(&member.user.name); - contains(nick, substring, case_sensitive).then(|| (member, nick.clone())) + contains(nick, substring, case_sensitive).then(|| (member, nick.clone().into())) }) .collect::>(); @@ -2414,7 +2415,7 @@ impl Guild { /// ``` #[must_use] pub fn role_by_name(&self, role_name: &str) -> Option<&Role> { - self.roles.values().find(|role| role_name == role.name) + self.roles.values().find(|role| role_name == &*role.name) } /// Returns a builder which can be awaited to obtain a message or stream of messages in this @@ -2608,7 +2609,7 @@ pub struct GuildInfo { /// Can be used to calculate creation date. pub id: GuildId, /// The name of the guild. - pub name: String, + pub name: FixedString, /// The hash of the icon of the guild. /// /// This can be used to generate a URL to the guild's icon image. @@ -2618,7 +2619,7 @@ pub struct GuildInfo { /// The permissions that the current user has. pub permissions: Permissions, /// See [`Guild::features`]. - pub features: Vec, + pub features: FixedArray, } #[cfg(feature = "model")] @@ -2787,9 +2788,9 @@ mod test { fn gen_member() -> Member { Member { - nick: Some("aaaa".to_string()), + nick: Some("aaaa".to_string().into()), user: User { - name: "test".into(), + name: "test".to_string().into(), discriminator: NonZeroU16::new(1432), ..User::default() }, diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index 68c6507a76c..8c922e502de 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -22,6 +22,7 @@ use crate::collector::{MessageCollector, ReactionCollector}; use crate::gateway::ShardMessenger; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http, UserPagination}; +use crate::internal::prelude::*; use crate::model::prelude::*; #[cfg(feature = "model")] use crate::model::utils::icon_url; @@ -43,7 +44,7 @@ pub struct PartialGuild { /// This is equivalent to the Id of the default role (`@everyone`). pub id: GuildId, /// The name of the guild. - pub name: String, + pub name: FixedString, /// The hash of the icon used by the guild. /// /// In the client, this appears on the guild list on the left-hand side. @@ -115,7 +116,7 @@ pub struct PartialGuild { /// /// /// [`discord documentation`]: https://discord.com/developers/docs/resources/guild#guild-object-guild-features - pub features: Vec, + pub features: FixedArray, /// Indicator of whether the guild requires multi-factor authentication for [`Role`]s or /// [`User`]s with moderation permissions. pub mfa_level: MfaLevel, @@ -136,18 +137,18 @@ pub struct PartialGuild { /// The maximum number of members for the guild. pub max_members: Option, /// The vanity url code for the guild, if it has one. - pub vanity_url_code: Option, + pub vanity_url_code: Option, /// The server's description, if it has one. - pub description: Option, + pub description: Option, /// The guild's banner, if it has one. - pub banner: Option, + pub banner: Option, /// The server's premium boosting level. pub premium_tier: PremiumTier, /// The total number of users currently boosting this server. pub premium_subscription_count: Option, /// The preferred locale of this guild only set if guild has the "DISCOVERABLE" feature, /// defaults to en-US. - pub preferred_locale: String, + pub preferred_locale: FixedString, /// The id of the channel where admins and moderators of Community guilds receive notices from /// Discord. /// @@ -1505,7 +1506,7 @@ impl PartialGuild { #[inline] #[must_use] pub fn role_by_name(&self, role_name: &str) -> Option<&Role> { - self.roles.values().find(|role| role_name == role.name) + self.roles.values().find(|role| role_name == &*role.name) } /// Returns a builder which can be awaited to obtain a message or stream of messages in this diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs index 3b8528738f9..39aa7d373e0 100644 --- a/src/model/guild/role.rs +++ b/src/model/guild/role.rs @@ -5,7 +5,6 @@ use std::fmt; use crate::builder::EditRole; #[cfg(feature = "model")] use crate::http::Http; -#[cfg(all(feature = "cache", feature = "model"))] use crate::internal::prelude::*; use crate::model::prelude::*; use crate::model::utils::is_false; @@ -48,7 +47,7 @@ pub struct Role { #[serde(default)] pub mentionable: bool, /// The name of the role. - pub name: String, + pub name: FixedString, /// A set of permissions that the role has been assigned. /// /// See the [`permissions`] module for more information. @@ -71,7 +70,7 @@ pub struct Role { /// Role icon image hash. pub icon: Option, /// Role unicoded image. - pub unicode_emoji: Option, + pub unicode_emoji: Option, } #[cfg(feature = "model")] diff --git a/src/model/guild/scheduled_event.rs b/src/model/guild/scheduled_event.rs index 19a5499c4f2..ac5994ac20e 100644 --- a/src/model/guild/scheduled_event.rs +++ b/src/model/guild/scheduled_event.rs @@ -1,3 +1,4 @@ +use crate::internal::prelude::*; use crate::model::prelude::*; /// Information about a guild scheduled event. @@ -18,9 +19,9 @@ pub struct ScheduledEvent { /// Only `None` for events created before October 25th, 2021. pub creator_id: Option, /// The name of the scheduled event. - pub name: String, + pub name: FixedString, /// The description of the scheduled event, if any. - pub description: Option, + pub description: Option, /// The event's starting time. #[serde(rename = "scheduled_start_time")] pub start_time: Timestamp, @@ -88,7 +89,7 @@ enum_number! { #[non_exhaustive] pub struct ScheduledEventMetadata { #[serde(default)] - pub location: Option, + pub location: Option, } /// [Discord docs](https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-user-object). diff --git a/src/model/guild/welcome_screen.rs b/src/model/guild/welcome_screen.rs index f809065d2a0..29d98ffd286 100644 --- a/src/model/guild/welcome_screen.rs +++ b/src/model/guild/welcome_screen.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::internal::prelude::*; use crate::model::id::{ChannelId, EmojiId}; /// Information relating to a guild's welcome screen. @@ -10,11 +11,11 @@ use crate::model::id::{ChannelId, EmojiId}; #[non_exhaustive] pub struct GuildWelcomeScreen { /// The server description shown in the welcome screen. - pub description: Option, + pub description: Option, /// The channels shown in the welcome screen. /// /// **Note**: There can only be only up to 5 channels. - pub welcome_channels: Vec, + pub welcome_channels: FixedArray, } /// A channel shown in the [`GuildWelcomeScreen`]. @@ -27,7 +28,7 @@ pub struct GuildWelcomeChannel { /// The channel Id. pub channel_id: ChannelId, /// The description shown for the channel. - pub description: String, + pub description: FixedString, /// The emoji shown, if there is one. pub emoji: Option, } @@ -38,9 +39,9 @@ impl<'de> Deserialize<'de> for GuildWelcomeChannel { #[derive(Deserialize)] struct Helper { channel_id: ChannelId, - description: String, + description: FixedString, emoji_id: Option, - emoji_name: Option, + emoji_name: Option, } let Helper { channel_id, @@ -95,7 +96,7 @@ impl Serialize for GuildWelcomeChannel { #[non_exhaustive] pub enum GuildWelcomeChannelEmoji { /// A custom emoji. - Custom { id: EmojiId, name: String }, + Custom { id: EmojiId, name: FixedString }, /// A unicode emoji. - Unicode(String), + Unicode(FixedString), } diff --git a/src/model/invite.rs b/src/model/invite.rs index bced0fbc7c6..3c66cfcc5e5 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -7,7 +7,6 @@ use crate::builder::CreateInvite; use crate::cache::Cache; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http}; -#[cfg(feature = "model")] use crate::internal::prelude::*; /// Information about an invite code. @@ -26,7 +25,7 @@ pub struct Invite { /// These include [invisible][`OnlineStatus::Invisible`] members. pub approximate_presence_count: Option, /// The unique code for the invite. - pub code: String, + pub code: FixedString, /// A representation of the minimal amount of information needed about the [`GuildChannel`] /// being invited to. pub channel: InviteChannel, @@ -199,7 +198,7 @@ impl Invite { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InviteChannel { pub id: ChannelId, - pub name: String, + pub name: FixedString, #[serde(rename = "type")] pub kind: ChannelType, } @@ -211,14 +210,14 @@ pub struct InviteChannel { #[non_exhaustive] pub struct InviteGuild { pub id: GuildId, - pub name: String, + pub name: FixedString, pub splash: Option, pub banner: Option, - pub description: Option, + pub description: Option, pub icon: Option, - pub features: Vec, + pub features: FixedArray, pub verification_level: VerificationLevel, - pub vanity_url_code: Option, + pub vanity_url_code: Option, pub nsfw_level: NsfwLevel, pub premium_subscription_count: Option, } @@ -281,7 +280,7 @@ pub struct RichInvite { /// invited to. pub channel: InviteChannel, /// The unique code for the invite. - pub code: String, + pub code: FixedString, /// When the invite was created. pub created_at: Timestamp, /// A representation of the minimal amount of information needed about the [`Guild`] being @@ -393,13 +392,13 @@ impl RichInvite { #[non_exhaustive] pub struct InviteStageInstance { /// The members speaking in the Stage - pub members: Vec, + pub members: FixedArray, /// The number of users in the Stage pub participant_count: u64, /// The number of users speaking in the Stage pub speaker_count: u64, /// The topic of the Stage instance (1-120 characters) - pub topic: String, + pub topic: FixedString, } enum_number! { diff --git a/src/model/misc.rs b/src/model/misc.rs index e5d6d8c2a78..3b0214551be 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -11,6 +11,7 @@ use std::str::FromStr; use arrayvec::ArrayString; use super::prelude::*; +use crate::internal::prelude::*; #[cfg(all(feature = "model", any(feature = "cache", feature = "utils")))] use crate::utils; @@ -170,7 +171,7 @@ pub struct EmojiIdentifier { pub id: EmojiId, /// The name of the emoji. It must be at least 2 characters long and can only contain /// alphanumeric characters and underscores. - pub name: String, + pub name: FixedString, } #[cfg(all(feature = "model", feature = "utils"))] @@ -204,7 +205,7 @@ impl fmt::Display for EmojiIdentifier { #[derive(Debug)] #[cfg(all(feature = "model", feature = "utils"))] pub struct EmojiIdentifierParseError { - parsed_string: String, + parsed_string: FixedString, } #[cfg(all(feature = "model", feature = "utils"))] @@ -223,7 +224,7 @@ impl FromStr for EmojiIdentifier { fn from_str(s: &str) -> StdResult { utils::parse_emoji(s).ok_or_else(|| EmojiIdentifierParseError { - parsed_string: s.to_owned(), + parsed_string: s.to_owned().into(), }) } } @@ -236,17 +237,17 @@ impl FromStr for EmojiIdentifier { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct Incident { - pub created_at: String, - pub id: String, - pub impact: String, - pub incident_updates: Vec, - pub monitoring_at: Option, - pub name: String, - pub page_id: String, - pub resolved_at: Option, - pub shortlink: String, - pub status: String, - pub updated_at: String, + pub created_at: FixedString, + pub id: FixedString, + pub impact: FixedString, + pub incident_updates: FixedArray, + pub monitoring_at: Option, + pub name: FixedString, + pub page_id: FixedString, + pub resolved_at: Option, + pub shortlink: FixedString, + pub status: FixedString, + pub updated_at: FixedString, } /// An update to an incident from the Discord status page. @@ -257,13 +258,13 @@ pub struct Incident { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct IncidentUpdate { - pub body: String, - pub created_at: String, - pub display_at: String, - pub id: String, - pub incident_id: String, - pub status: String, - pub updated_at: String, + pub body: FixedString, + pub created_at: FixedString, + pub display_at: FixedString, + pub id: FixedString, + pub incident_id: FixedString, + pub status: FixedString, + pub updated_at: FixedString, } /// A Discord status maintenance message. This can be either for active maintenances or for @@ -273,19 +274,19 @@ pub struct IncidentUpdate { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct Maintenance { - pub created_at: String, - pub id: String, - pub impact: String, - pub incident_updates: Vec, - pub monitoring_at: Option, - pub name: String, - pub page_id: String, - pub resolved_at: Option, - pub scheduled_for: String, - pub scheduled_until: String, - pub shortlink: String, - pub status: String, - pub updated_at: String, + pub created_at: FixedString, + pub id: FixedString, + pub impact: FixedString, + pub incident_updates: FixedArray, + pub monitoring_at: Option, + pub name: FixedString, + pub page_id: FixedString, + pub resolved_at: Option, + pub scheduled_for: FixedString, + pub scheduled_until: FixedString, + pub shortlink: FixedString, + pub status: FixedString, + pub updated_at: FixedString, } #[cfg(test)] diff --git a/src/model/sticker.rs b/src/model/sticker.rs index 5b0dd9963ea..cefbb23159b 100644 --- a/src/model/sticker.rs +++ b/src/model/sticker.rs @@ -2,7 +2,6 @@ use crate::builder::EditSticker; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http}; -#[cfg(feature = "model")] use crate::internal::prelude::*; use crate::model::prelude::*; use crate::model::utils::comma_separated_string; @@ -43,7 +42,7 @@ pub struct StickerItem { /// The unique ID given to this sticker. pub id: StickerId, /// The name of the sticker. - pub name: String, + pub name: FixedString, /// The type of sticker format. pub format_type: StickerFormatType, } @@ -82,15 +81,15 @@ pub struct StickerPack { /// The unique ID given to this sticker sticker pack. pub id: StickerPackId, /// The stickers in the pack - pub stickers: Vec, + pub stickers: FixedArray, /// The name of the sticker pack - pub name: String, + pub name: FixedString, /// The unique ID given to the pack's SKU. pub sku_id: SkuId, /// ID of a sticker in the pack which is shown as the pack's icon. pub cover_sticker_id: Option, /// Description of the sticker pack. - pub description: String, + pub description: FixedString, /// The unique ID given to the sticker pack's banner image. pub banner_asset_id: StickerPackBannerId, } @@ -126,13 +125,13 @@ pub struct Sticker { /// The unique ID of the pack the sticker is from. pub pack_id: Option, /// The name of the sticker. - pub name: String, + pub name: FixedString, /// Description of the sticker - pub description: Option, + pub description: Option, /// For guild stickers, the Discord name of a unicode emoji representing the sticker's /// expression. For standard stickers, a list of related expressions. #[serde(with = "comma_separated_string")] - pub tags: Vec, + pub tags: FixedArray, /// The type of sticker. #[serde(rename = "type")] pub kind: StickerType, diff --git a/src/model/user.rs b/src/model/user.rs index 4b16d35c750..8ce7887ad7e 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -19,7 +19,6 @@ use crate::collector::{MessageCollector, ReactionCollector}; use crate::gateway::ShardMessenger; #[cfg(feature = "model")] use crate::http::CacheHttp; -#[cfg(feature = "model")] use crate::internal::prelude::*; #[cfg(feature = "model")] use crate::json::json; @@ -234,7 +233,7 @@ pub struct User { /// change if the username+discriminator pair becomes non-unique. Unless the account has /// migrated to a next generation username, which does not have a discriminant. #[serde(rename = "username")] - pub name: String, + pub name: FixedString, /// The account's discriminator to differentiate the user from others with /// the same [`Self::name`]. The name+discriminator pair is always unique. /// If the discriminator is not present, then this is a next generation username @@ -243,7 +242,7 @@ pub struct User { pub discriminator: Option, /// The account's display name, if it is set. /// For bots this is the application name. - pub global_name: Option, + pub global_name: Option>, /// Optional avatar hash. pub avatar: Option, /// Indicator of whether the user is a bot. @@ -267,7 +266,7 @@ pub struct User { #[serde(rename = "accent_color")] pub accent_colour: Option, /// The user's chosen language option - pub locale: Option, + pub locale: Option, /// Whether the email on this account has been verified /// /// Requires [`Scope::Email`] @@ -275,7 +274,7 @@ pub struct User { /// The user's email /// /// Requires [`Scope::Email`] - pub email: Option, + pub email: Option, /// The flags on a user's account #[serde(default)] pub flags: UserPublicFlags, @@ -583,14 +582,19 @@ impl User { if let Some(cache) = cache_http.cache() { if let Some(guild) = guild_id.to_guild_cached(cache) { if let Some(member) = guild.members.get(&self.id) { - return member.nick.clone(); + return member.nick.clone().map(Into::into); } } } } // At this point we're guaranteed to do an API call. - guild_id.member(cache_http, &self.id).await.ok().and_then(|member| member.nick) + guild_id + .member(cache_http, &self.id) + .await + .ok() + .and_then(|member| member.nick) + .map(Into::into) } /// Returns a builder which can be awaited to obtain a message or stream of messages sent by @@ -878,7 +882,7 @@ mod test { id: UserId::new(210), avatar: Some(ImageHash::from_str("fb211703bcc04ee612c88d494df0272f").unwrap()), discriminator: NonZeroU16::new(1432), - name: "test".to_string(), + name: "test".to_string().into(), ..Default::default() }; diff --git a/src/model/utils.rs b/src/model/utils.rs index b1a1cb535ad..0786f057aa5 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -3,11 +3,13 @@ use std::hash::Hash; use std::marker::PhantomData; use std::num::NonZeroU64; +use arrayvec::ArrayVec; use serde::de::Error as DeError; use serde::ser::{Serialize, SerializeSeq, Serializer}; use serde_cow::CowStr; use super::prelude::*; +use crate::internal::prelude::*; pub fn default_true() -> bool { true @@ -230,13 +232,13 @@ pub mod presences { pub fn deserialize_buttons<'de, D: Deserializer<'de>>( deserializer: D, -) -> StdResult, D::Error> { - Vec::deserialize(deserializer).map(|labels| { +) -> StdResult, D::Error> { + ArrayVec::<_, 2>::deserialize(deserializer).map(|labels| { labels .into_iter() .map(|l| ActivityButton { label: l, - url: String::new(), + url: FixedString::default(), }) .collect() }) @@ -285,17 +287,23 @@ pub mod comma_separated_string { use serde::{Deserialize, Deserializer, Serializer}; use serde_cow::CowStr; + use crate::internal::prelude::*; + pub fn deserialize<'de, D: Deserializer<'de>>( deserializer: D, - ) -> Result, D::Error> { + ) -> Result, D::Error> { let str_sequence = CowStr::deserialize(deserializer)?.0; - let vec = str_sequence.split(", ").map(str::to_owned).collect(); + let vec = str_sequence.split(", ").map(str::to_owned).map(FixedString::from).collect(); Ok(vec) } #[allow(clippy::ptr_arg)] - pub fn serialize(vec: &Vec, serializer: S) -> Result { + pub fn serialize( + vec: &FixedArray, + serializer: S, + ) -> Result { + let vec: Vec = vec.iter().map(FixedString::clone).map(String::from).collect(); serializer.serialize_str(&vec.join(", ")) } } diff --git a/src/model/voice.rs b/src/model/voice.rs index 9912c945f17..a219702ade3 100644 --- a/src/model/voice.rs +++ b/src/model/voice.rs @@ -3,6 +3,7 @@ use serde::de::{Deserialize, Deserializer}; use serde::Serialize; +use crate::internal::prelude::*; use crate::model::guild::Member; use crate::model::id::{ChannelId, GuildId, UserId}; use crate::model::Timestamp; @@ -18,9 +19,9 @@ pub struct VoiceRegion { /// Whether it is a deprecated voice region, which you should avoid using. pub deprecated: bool, /// The internal Id of the voice region. - pub id: String, + pub id: FixedString, /// A recognizable name of the location of the voice region. - pub name: String, + pub name: FixedString, /// Whether the voice region is optimal for use by the current user. pub optimal: bool, } @@ -42,7 +43,7 @@ pub struct VoiceState { pub self_mute: bool, pub self_stream: Option, pub self_video: bool, - pub session_id: String, + pub session_id: FixedString, pub suppress: bool, pub user_id: UserId, /// When unsuppressed, non-bot users will have this set to the current time. Bot users will be diff --git a/src/model/webhook.rs b/src/model/webhook.rs index b8bbcd005da..ac3150b74fc 100644 --- a/src/model/webhook.rs +++ b/src/model/webhook.rs @@ -11,7 +11,6 @@ use crate::builder::{Builder, EditWebhook, EditWebhookMessage, ExecuteWebhook}; use crate::cache::{Cache, GuildChannelRef, GuildRef}; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http}; -#[cfg(feature = "model")] use crate::internal::prelude::*; use crate::model::prelude::*; @@ -74,7 +73,7 @@ pub struct Webhook { /// The default name of the webhook. /// /// This can be temporarily overridden via [`ExecuteWebhook::username`]. - pub name: Option, + pub name: Option>, /// The default avatar. /// /// This can be temporarily overridden via [`ExecuteWebhook::avatar_url`]. @@ -103,7 +102,7 @@ pub struct WebhookGuild { /// The unique Id identifying the guild. pub id: GuildId, /// The name of the guild. - pub name: String, + pub name: FixedString, /// The hash of the icon used by the guild. /// /// In the client, this appears on the guild list on the left-hand side. @@ -165,7 +164,7 @@ pub struct WebhookChannel { /// The unique Id of the channel. pub id: ChannelId, /// The name of the channel. - pub name: String, + pub name: FixedString, } #[cfg(feature = "model")] diff --git a/src/utils/argument_convert/user.rs b/src/utils/argument_convert/user.rs index fb9a00073a6..24fb459fb86 100644 --- a/src/utils/argument_convert/user.rs +++ b/src/utils/argument_convert/user.rs @@ -43,7 +43,7 @@ fn lookup_by_global_cache(ctx: impl CacheHttp, s: &str) -> Option { let lookup_by_name = || { users.iter().find_map(|m| { let user = m.value(); - (user.name == s).then(|| user.clone()) + (&*user.name == s).then(|| user.clone()) }) }; diff --git a/src/utils/content_safe.rs b/src/utils/content_safe.rs index 43ce0e23fd1..818eab287ae 100644 --- a/src/utils/content_safe.rs +++ b/src/utils/content_safe.rs @@ -280,13 +280,13 @@ mod tests { fn test_content_safe() { let user = User { id: UserId::new(100000000000000000), - name: "Crab".to_string(), + name: "Crab".to_string().into(), ..Default::default() }; let outside_cache_user = User { id: UserId::new(100000000000000001), - name: "Boat".to_string(), + name: "Boat".to_string().into(), ..Default::default() }; @@ -296,19 +296,19 @@ mod tests { }; let member = Member { - nick: Some("Ferris".to_string()), + nick: Some("Ferris".to_string().into()), ..Default::default() }; let role = Role { id: RoleId::new(333333333333333333), - name: "ferris-club-member".to_string(), + name: "ferris-club-member".to_string().into(), ..Default::default() }; let channel = GuildChannel { id: ChannelId::new(111880193700067777), - name: "general".to_string(), + name: "general".to_string().into(), ..Default::default() }; diff --git a/src/utils/custom_message.rs b/src/utils/custom_message.rs index 1e100c06bfd..e447c262d1d 100644 --- a/src/utils/custom_message.rs +++ b/src/utils/custom_message.rs @@ -66,7 +66,7 @@ impl CustomMessage { /// If not used, the default value is an empty string (`String::default()`). #[inline] pub fn content(&mut self, s: impl Into) -> &mut Self { - self.msg.content = s.into(); + self.msg.content = s.into().into(); self } diff --git a/src/utils/message_builder.rs b/src/utils/message_builder.rs index 23c15a17063..858303680b9 100644 --- a/src/utils/message_builder.rs +++ b/src/utils/message_builder.rs @@ -1200,10 +1200,10 @@ mod test { animated: false, available: true, id: EmojiId::new(32), - name: "Rohrkatze".to_string(), + name: "Rohrkatze".to_string().into(), managed: false, require_colons: true, - roles: vec![], + roles: vec![].into(), user: None, }) .build(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3950f6fe270..e681ad0f3d7 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -246,7 +246,7 @@ pub fn parse_channel_mention(mention: &str) -> Option { /// let emoji = parse_emoji("<:smugAnimeFace:302516740095606785>").unwrap(); /// assert_eq!(emoji.animated, false); /// assert_eq!(emoji.id, EmojiId::new(302516740095606785)); -/// assert_eq!(emoji.name, "smugAnimeFace".to_string()); +/// assert_eq!(&*emoji.name, "smugAnimeFace"); /// ``` /// /// Asserting that an invalid emoji usage returns [`None`]: @@ -291,9 +291,9 @@ pub fn parse_emoji(mention: impl AsRef) -> Option { } id.parse().ok().map(|id| EmojiIdentifier { + name: name.into(), animated, id, - name, }) } else { None @@ -532,7 +532,7 @@ mod test { #[test] fn test_emoji_parser() { let emoji = parse_emoji("<:name:12345>").unwrap(); - assert_eq!(emoji.name, "name"); + assert_eq!(&*emoji.name, "name"); assert_eq!(emoji.id, 12_345); } diff --git a/src/utils/quick_modal.rs b/src/utils/quick_modal.rs index 5d9b6f1bcd0..d417b1e39d7 100644 --- a/src/utils/quick_modal.rs +++ b/src/utils/quick_modal.rs @@ -7,12 +7,13 @@ use crate::builder::{ }; use crate::client::Context; use crate::collector::ModalInteractionCollector; +use crate::internal::prelude::*; use crate::model::prelude::*; #[cfg(feature = "collector")] pub struct QuickModalResponse { pub interaction: ModalInteraction, - pub inputs: Vec, + pub inputs: FixedArray>, } /// Convenience builder to create a modal, wait for the user to submit and parse the response. @@ -27,7 +28,7 @@ pub struct QuickModalResponse { /// .paragraph_field("Hobbies and interests"); /// let response = interaction.quick_modal(ctx, modal).await?; /// let inputs = response.unwrap().inputs; -/// let (first_name, last_name, hobbies) = (&inputs[0], &inputs[1], &inputs[2]); +/// let (first_name, last_name, hobbies) = (&inputs[0_usize], &inputs[1_usize], &inputs[2_usize]); /// # Ok(()) /// # } /// ``` @@ -105,7 +106,7 @@ impl CreateQuickModal { builder.execute(ctx, (interaction_id, token)).await?; let collector = - ModalInteractionCollector::new(&ctx.shard).custom_ids(vec![modal_custom_id]); + ModalInteractionCollector::new(&ctx.shard).custom_ids(vec![modal_custom_id.into()]); let collector = match self.timeout { Some(timeout) => collector.timeout(timeout), diff --git a/tests/test_reaction.rs b/tests/test_reaction.rs index 135deb172f6..cafd1e2c01b 100644 --- a/tests/test_reaction.rs +++ b/tests/test_reaction.rs @@ -10,7 +10,7 @@ fn str_to_reaction_type() { let reaction2 = ReactionType::Custom { animated: false, id: EmojiId::new(600404340292059257), - name: Some("customemoji".to_string()), + name: Some("customemoji".to_string().into()), }; assert_eq!(reaction, reaction2); } @@ -22,7 +22,7 @@ fn str_to_reaction_type_animated() { let reaction2 = ReactionType::Custom { animated: true, id: EmojiId::new(600409340292059257), - name: Some("customemoji2".to_string()), + name: Some("customemoji2".to_string().into()), }; assert_eq!(reaction, reaction2); } @@ -34,7 +34,7 @@ fn string_to_reaction_type() { let reaction2 = ReactionType::Custom { animated: false, id: EmojiId::new(600404340292059257), - name: Some("customemoji".to_string()), + name: Some("customemoji".to_string().into()), }; assert_eq!(reaction, reaction2); } @@ -105,7 +105,7 @@ fn json_to_reaction_type() { let value = serde_json::from_str(s).unwrap(); assert!(matches!(value, ReactionType::Unicode(_))); if let ReactionType::Unicode(value) = value { - assert_eq!(value, "foo"); + assert_eq!(&*value, "foo"); } let s = r#"{"name": null}"#; From 1a961acfc9f663a7830dd96d92caf934965c0883 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Thu, 14 Dec 2023 22:40:08 +0000 Subject: [PATCH 008/159] Fix truncation when using `FixedString` where multibyte characters push over the limit (#2660) --- src/model/application/command.rs | 4 ++-- src/model/application/component.rs | 4 ++-- src/model/channel/channel_id.rs | 4 ++-- src/model/channel/guild_channel.rs | 2 +- src/model/channel/private_channel.rs | 4 ++-- src/model/guild/automod.rs | 4 ++-- src/model/invite.rs | 2 +- src/model/webhook.rs | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/model/application/command.rs b/src/model/application/command.rs index eb9e2780460..b85628ce7af 100644 --- a/src/model/application/command.rs +++ b/src/model/application/command.rs @@ -53,13 +53,13 @@ pub struct Command { /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)). pub name_localizations: Option>, /// The command description. - pub description: FixedString, + pub description: FixedString, /// The localized command description of the selected locale. /// /// If the description is localized, either this field or [`Self::description_localizations`] /// is set, depending on which endpoint this data was retrieved from /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)). - pub description_localized: Option>, + pub description_localized: Option>, /// All localized command descriptions. /// /// If the description is localized, either this field or [`Self::description_localized`] is diff --git a/src/model/application/component.rs b/src/model/application/component.rs index c290c90cf52..467cd7032e7 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -260,7 +260,7 @@ pub struct InputText { #[serde(rename = "type")] pub kind: ComponentType, /// Developer-defined identifier for the input; max 100 characters - pub custom_id: FixedString, + pub custom_id: FixedString, /// The [`InputTextStyle`]. Required when sending modal data. /// /// Discord docs are wrong here; it says the field is always sent in modal submit interactions @@ -289,7 +289,7 @@ pub struct InputText { pub value: Option>, /// Custom placeholder text if the input is empty; max 100 characters #[serde(skip_serializing_if = "Option::is_none")] - pub placeholder: Option>, + pub placeholder: Option>, } enum_number! { diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs index 1de3657194d..851945ab631 100644 --- a/src/model/channel/channel_id.rs +++ b/src/model/channel/channel_id.rs @@ -532,11 +532,11 @@ impl ChannelId { /// # Errors /// /// Same as [`Self::to_channel()`]. - pub async fn name(self, cache_http: impl CacheHttp) -> Result> { + pub async fn name(self, cache_http: impl CacheHttp) -> Result { let channel = self.to_channel(cache_http).await?; Ok(match channel { - Channel::Guild(channel) => channel.name, + Channel::Guild(channel) => channel.name.into(), Channel::Private(channel) => channel.name(), }) } diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index 250bf5fac23..4cf0395902b 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -74,7 +74,7 @@ pub struct GuildChannel { /// **Note**: This is only available for text channels. pub last_pin_timestamp: Option, /// The name of the channel. (1-100 characters) - pub name: FixedString, + pub name: FixedString, /// Permission overwrites for [`Member`]s and for [`Role`]s. #[serde(default)] pub permission_overwrites: FixedArray, diff --git a/src/model/channel/private_channel.rs b/src/model/channel/private_channel.rs index cf18664a5c5..c053f1d9efc 100644 --- a/src/model/channel/private_channel.rs +++ b/src/model/channel/private_channel.rs @@ -200,8 +200,8 @@ impl PrivateChannel { /// Returns "DM with $username#discriminator". #[must_use] - pub fn name(&self) -> FixedString { - format!("DM with {}", self.recipient.tag()).into() + pub fn name(&self) -> String { + format!("DM with {}", self.recipient.tag()) } /// Gets the list of [`User`]s who have reacted to a [`Message`] with a certain [`Emoji`]. diff --git a/src/model/guild/automod.rs b/src/model/guild/automod.rs index 7c2ab6f722e..1892d6af38b 100644 --- a/src/model/guild/automod.rs +++ b/src/model/guild/automod.rs @@ -331,7 +331,7 @@ pub enum Action { /// Additional explanation that will be shown to members whenever their message is blocked /// /// Maximum of 150 characters - custom_message: Option>, + custom_message: Option>, }, /// Logs user content to a specified channel. Alert(ChannelId), @@ -401,7 +401,7 @@ struct RawActionMetadata { #[serde(skip_serializing_if = "Option::is_none")] duration_seconds: Option, #[serde(skip_serializing_if = "Option::is_none")] - custom_message: Option>, + custom_message: Option>, } /// Helper struct for the (de)serialization of `Action`. diff --git a/src/model/invite.rs b/src/model/invite.rs index 3c66cfcc5e5..75fe9294f62 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -398,7 +398,7 @@ pub struct InviteStageInstance { /// The number of users speaking in the Stage pub speaker_count: u64, /// The topic of the Stage instance (1-120 characters) - pub topic: FixedString, + pub topic: FixedString, } enum_number! { diff --git a/src/model/webhook.rs b/src/model/webhook.rs index ac3150b74fc..4f5d01948b8 100644 --- a/src/model/webhook.rs +++ b/src/model/webhook.rs @@ -102,7 +102,7 @@ pub struct WebhookGuild { /// The unique Id identifying the guild. pub id: GuildId, /// The name of the guild. - pub name: FixedString, + pub name: FixedString, /// The hash of the icon used by the guild. /// /// In the client, this appears on the guild list on the left-hand side. @@ -164,7 +164,7 @@ pub struct WebhookChannel { /// The unique Id of the channel. pub id: ChannelId, /// The name of the channel. - pub name: FixedString, + pub name: FixedString, } #[cfg(feature = "model")] From 07007338dccb32679eae5897e5b851e4135e28f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 22 Dec 2023 18:17:13 +0100 Subject: [PATCH 009/159] Encode timestamps in RFC3339 (#2665) Fixes #2664 --- src/http/client.rs | 6 +++--- src/model/channel/channel_id.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/http/client.rs b/src/http/client.rs index 2ab32fc43c5..4c1b5367d67 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -3005,7 +3005,7 @@ impl Http { pub async fn get_channel_archived_public_threads( &self, channel_id: ChannelId, - before: Option, + before: Option, limit: Option, ) -> Result { let mut params = ArrayVec::<_, 2>::new(); @@ -3033,7 +3033,7 @@ impl Http { pub async fn get_channel_archived_private_threads( &self, channel_id: ChannelId, - before: Option, + before: Option, limit: Option, ) -> Result { let mut params = ArrayVec::<_, 2>::new(); @@ -3061,7 +3061,7 @@ impl Http { pub async fn get_channel_joined_archived_private_threads( &self, channel_id: ChannelId, - before: Option, + before: Option, limit: Option, ) -> Result { let mut params = ArrayVec::<_, 2>::new(); diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs index 851945ab631..be6e776c20b 100644 --- a/src/model/channel/channel_id.rs +++ b/src/model/channel/channel_id.rs @@ -1033,7 +1033,7 @@ impl ChannelId { pub async fn get_archived_private_threads( self, http: impl AsRef, - before: Option, + before: Option, limit: Option, ) -> Result { http.as_ref().get_channel_archived_private_threads(self, before, limit).await @@ -1047,7 +1047,7 @@ impl ChannelId { pub async fn get_archived_public_threads( self, http: impl AsRef, - before: Option, + before: Option, limit: Option, ) -> Result { http.as_ref().get_channel_archived_public_threads(self, before, limit).await @@ -1061,7 +1061,7 @@ impl ChannelId { pub async fn get_joined_archived_private_threads( self, http: impl AsRef, - before: Option, + before: Option, limit: Option, ) -> Result { http.as_ref().get_channel_joined_archived_private_threads(self, before, limit).await From 3305c46d4b878bea66e0d0ecd00d0d305b9cbb42 Mon Sep 17 00:00:00 2001 From: Aneesh Kadiyala <143342960+ARandomDev99@users.noreply.github.com> Date: Fri, 22 Dec 2023 23:07:47 +0530 Subject: [PATCH 010/159] Switch to `i64` in methods associated with `CreateCommandOption` (#2668) This commit: - switches from `u64` to `i64` in `CreateCommandOption::min_int_value` and `CreateCommandOption::max_int_value` to accommodate negative integers in Discord's integer range (between -2^53 and 2^53). Values outside this range will cause Discord's API to return an error. - switches from `i32` to `i64` in `CreateCommandOption::add_int_choice` and `CreateCommandOption::add_int_choice_localized` to accommodate Discord's complete integer range (between -2^53 and 2^53). Values outside this range will cause Discord's API to return an error. --- src/builder/create_command.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/builder/create_command.rs b/src/builder/create_command.rs index 38341359dcc..39d8c9dd31a 100644 --- a/src/builder/create_command.rs +++ b/src/builder/create_command.rs @@ -112,7 +112,7 @@ 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: i32) -> Self { + pub fn add_int_choice(self, name: impl Into, value: i64) -> Self { self.add_choice(CommandOptionChoice { name: name.into().into(), value: Value::from(value), @@ -124,7 +124,7 @@ impl CreateCommandOption { pub fn add_int_choice_localized( self, name: impl Into, - value: i32, + value: i64, locales: impl IntoIterator, impl Into)>, ) -> Self { self.add_choice(CommandOptionChoice { @@ -246,13 +246,13 @@ impl CreateCommandOption { } /// Sets the minimum permitted value for this integer option - pub fn min_int_value(mut self, value: u64) -> Self { + pub fn min_int_value(mut self, value: i64) -> Self { self.0.min_value = Some(value.into()); self } /// Sets the maximum permitted value for this integer option - pub fn max_int_value(mut self, value: u64) -> Self { + pub fn max_int_value(mut self, value: i64) -> Self { self.0.max_value = Some(value.into()); self } From 4ee1f9e67b1385b1a41d3f12eeb33250fa403a32 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Thu, 28 Dec 2023 01:17:32 +0000 Subject: [PATCH 011/159] Get rid of the duplicate users cache (#2662) This cache was just duplicating information already present in `Guild::members` and therefore should be removed. This saves around 700 MBs for my bot (pre-`FixedString`). This has to refactor `utils::content_safe` to always take a `Guild` instead of`Cache`, but in practice it was mostly pulling from the guild cache anyway and this means it is more likely to respect nicknames and other information, while losing the ability to clean mentions from DMs, which do not matter. --- examples/e05_command_framework/src/main.rs | 21 +-- src/cache/event.rs | 31 +--- src/cache/mod.rs | 83 ---------- src/model/gateway.rs | 14 -- src/model/user.rs | 17 +-- src/utils/argument_convert/user.rs | 38 +---- src/utils/content_safe.rs | 170 ++++++++------------- 7 files changed, 77 insertions(+), 297 deletions(-) diff --git a/examples/e05_command_framework/src/main.rs b/examples/e05_command_framework/src/main.rs index d38631a82b7..6bef9754cfb 100644 --- a/examples/e05_command_framework/src/main.rs +++ b/examples/e05_command_framework/src/main.rs @@ -344,23 +344,18 @@ async fn commands(ctx: &Context, msg: &Message) -> CommandResult { #[command] async fn say(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { match args.single_quoted::() { - Ok(x) => { - let settings = if let Some(guild_id) = msg.guild_id { + Ok(mut x) => { + // We only need to wipe mentions in guilds, as DM mentions do not matter. + if let Some(guild) = msg.guild(&ctx.cache) { // By default roles, users, and channel mentions are cleaned. - ContentSafeOptions::default() + let settings = ContentSafeOptions::default() // We do not want to clean channal mentions as they do not ping users. - .clean_channel(false) - // If it's a guild channel, we want mentioned users to be displayed as their - // display name. - .display_as_member_from(guild_id) - } else { - ContentSafeOptions::default().clean_channel(false).clean_role(false) - }; + .clean_channel(false); - let content = content_safe(&ctx.cache, x, &settings, &msg.mentions); - - msg.channel_id.say(&ctx.http, &content).await?; + x = content_safe(&guild, x, &settings, &msg.mentions); + } + msg.channel_id.say(&ctx.http, x).await?; return Ok(()); }, Err(_) => { diff --git a/src/cache/event.rs b/src/cache/event.rs index e671de3506e..d88836201bd 100644 --- a/src/cache/event.rs +++ b/src/cache/event.rs @@ -100,14 +100,7 @@ impl CacheUpdate for GuildCreateEvent { fn update(&mut self, cache: &Cache) -> Option<()> { cache.unavailable_guilds.remove(&self.guild.id); - let mut guild = self.guild.clone(); - - for (user_id, member) in &mut guild.members { - cache.update_user_entry(&member.user); - if let Some(u) = cache.user(user_id) { - member.user = u.clone(); - } - } + let guild = self.guild.clone(); cache.guilds.insert(self.guild.id, guild); for channel_id in self.guild.channels.keys() { @@ -162,15 +155,9 @@ impl CacheUpdate for GuildMemberAddEvent { type Output = (); fn update(&mut self, cache: &Cache) -> Option<()> { - let user_id = self.member.user.id; - cache.update_user_entry(&self.member.user); - if let Some(u) = cache.user(user_id) { - self.member.user = u.clone(); - } - if let Some(mut guild) = cache.guilds.get_mut(&self.member.guild_id) { guild.member_count += 1; - guild.members.insert(user_id, self.member.clone()); + guild.members.insert(self.member.user.id, self.member.clone()); } None @@ -194,8 +181,6 @@ impl CacheUpdate for GuildMemberUpdateEvent { type Output = Member; fn update(&mut self, cache: &Cache) -> Option { - cache.update_user_entry(&self.user); - if let Some(mut guild) = cache.guilds.get_mut(&self.guild_id) { let item = if let Some(member) = guild.members.get_mut(&self.user.id) { let item = Some(member.clone()); @@ -247,10 +232,6 @@ impl CacheUpdate for GuildMembersChunkEvent { type Output = (); fn update(&mut self, cache: &Cache) -> Option<()> { - for member in self.members.values() { - cache.update_user_entry(&member.user); - } - if let Some(mut g) = cache.guilds.get_mut(&self.guild_id) { g.members.extend(self.members.clone()); } @@ -422,14 +403,6 @@ impl CacheUpdate for PresenceUpdateEvent { type Output = (); fn update(&mut self, cache: &Cache) -> Option<()> { - if let Some(user) = self.presence.user.to_user() { - cache.update_user_entry(&user); - } - - if let Some(user) = cache.user(self.presence.user.id) { - self.presence.user.update_with_user(&user); - } - if let Some(guild_id) = self.presence.guild_id { if let Some(mut guild) = cache.guilds.get_mut(&guild_id) { // If the member went offline, remove them from the presence list. diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 63a1922fd94..fcc031f659b 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -32,7 +32,6 @@ use std::sync::Arc; #[cfg(feature = "temp_cache")] use std::time::Duration; -use dashmap::mapref::entry::Entry; use dashmap::mapref::one::{MappedRef, Ref}; use dashmap::DashMap; #[cfg(feature = "temp_cache")] @@ -193,23 +192,6 @@ pub struct Cache { /// are "sent in" over time through the receiving of [`Event::GuildCreate`]s. pub(crate) unavailable_guilds: MaybeMap, - // Users cache: - // --- - /// A map of users that the current user sees. - /// - /// Users are added to - and updated from - this map via the following received events: - /// - /// - [`GuildMemberAdd`][`GuildMemberAddEvent`] - /// - [`GuildMemberRemove`][`GuildMemberRemoveEvent`] - /// - [`GuildMembersChunk`][`GuildMembersChunkEvent`] - /// - [`PresenceUpdate`][`PresenceUpdateEvent`] - /// - [`Ready`][`ReadyEvent`] - /// - /// Note, however, that users are _not_ removed from the map on removal events such as - /// [`GuildMemberRemove`][`GuildMemberRemoveEvent`], as other structs such as members or - /// recipients may still exist. - pub(crate) users: MaybeMap, - // Messages cache: // --- pub(crate) messages: MessageCache, @@ -281,8 +263,6 @@ impl Cache { guilds: MaybeMap(settings.cache_guilds.then(DashMap::default)), unavailable_guilds: MaybeMap(settings.cache_guilds.then(DashMap::default)), - users: MaybeMap(settings.cache_users.then(DashMap::default)), - messages: DashMap::default(), message_queue: DashMap::default(), @@ -648,56 +628,6 @@ impl Cache { self.settings.write().max_messages = max; } - /// Retrieves a [`User`] from the cache's [`Self::users`] map, if it exists. - /// - /// The only advantage of this method is that you can pass in anything that is indirectly a - /// [`UserId`]. - /// - /// # Examples - /// - /// Retrieve a user from the cache and print their name: - /// - /// ```rust,no_run - /// # use serenity::client::Context; - /// # - /// # async fn test(context: &Context) -> Result<(), Box> { - /// if let Some(user) = context.cache.user(7) { - /// println!("User with Id 7 is currently named {}", user.name); - /// } - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn user>(&self, user_id: U) -> Option> { - self.user_(user_id.into()) - } - - #[cfg(feature = "temp_cache")] - fn user_(&self, user_id: UserId) -> Option> { - if let Some(user) = self.users.get(&user_id) { - Some(CacheRef::from_ref(user)) - } else { - self.temp_users.get(&user_id).map(CacheRef::from_arc) - } - } - - #[cfg(not(feature = "temp_cache"))] - fn user_(&self, user_id: UserId) -> Option> { - self.users.get(&user_id).map(CacheRef::from_ref) - } - - /// Clones all users and returns them. - #[inline] - pub fn users(&self) -> ReadOnlyMapRef<'_, UserId, User> { - self.users.as_read_only() - } - - /// Returns the amount of cached users. - #[inline] - pub fn user_count(&self) -> usize { - self.users.len() - } - /// This method provides a reference to the user used by the bot. #[inline] pub fn current_user(&self) -> CurrentUserRef<'_> { @@ -750,19 +680,6 @@ impl Cache { pub fn update(&self, e: &mut E) -> Option { e.update(self) } - - pub(crate) fn update_user_entry(&self, user: &User) { - if let Some(users) = &self.users.0 { - match users.entry(user.id) { - Entry::Vacant(e) => { - e.insert(user.clone()); - }, - Entry::Occupied(mut e) => { - e.get_mut().clone_from(user); - }, - } - } - } } impl Default for Cache { diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 8431531b6f0..b2bd4126d2f 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -293,20 +293,6 @@ impl PresenceUser { pub fn to_user(&self) -> Option { self.clone().into_user() } - - #[cfg(feature = "cache")] // method is only used with the cache feature enabled - pub(crate) fn update_with_user(&mut self, user: &User) { - self.id = user.id; - if let Some(avatar) = user.avatar { - self.avatar = Some(avatar); - } - self.bot = Some(user.bot); - self.discriminator = user.discriminator; - self.name = Some(user.name.clone()); - if let Some(public_flags) = user.public_flags { - self.public_flags = Some(public_flags); - } - } } /// Information detailing the current online status of a [`User`]. diff --git a/src/model/user.rs b/src/model/user.rs index 8ce7887ad7e..fc0d800a57b 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -11,8 +11,6 @@ use serde::{Deserialize, Serialize}; use super::prelude::*; #[cfg(feature = "model")] use crate::builder::{Builder, CreateMessage, EditProfile}; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::cache::{Cache, UserRef}; #[cfg(feature = "collector")] use crate::collector::{MessageCollector, ReactionCollector}; #[cfg(feature = "collector")] @@ -725,13 +723,6 @@ impl UserId { self.direct_message(cache_http, builder).await } - /// Attempts to find a [`User`] by its Id in the cache. - #[cfg(feature = "cache")] - #[inline] - pub fn to_user_cached(self, cache: &impl AsRef) -> Option> { - cache.as_ref().user(self) - } - /// First attempts to find a [`User`] by its Id in the cache, upon failure requests it via the /// REST API. /// @@ -748,18 +739,18 @@ impl UserId { /// May also return an [`Error::Json`] if there is an error in deserializing the user. #[inline] pub async fn to_user(self, cache_http: impl CacheHttp) -> Result { - #[cfg(feature = "cache")] + #[cfg(feature = "temp_cache")] { if let Some(cache) = cache_http.cache() { - if let Some(user) = cache.user(self) { - return Ok(user.clone()); + if let Some(user) = cache.temp_users.get(&self) { + return Ok(User::clone(&user)); } } } let user = cache_http.http().get_user(self).await?; - #[cfg(all(feature = "cache", feature = "temp_cache"))] + #[cfg(feature = "temp_cache")] { if let Some(cache) = cache_http.cache() { use crate::cache::MaybeOwnedArc; diff --git a/src/utils/argument_convert/user.rs b/src/utils/argument_convert/user.rs index 24fb459fb86..c49db78a360 100644 --- a/src/utils/argument_convert/user.rs +++ b/src/utils/argument_convert/user.rs @@ -23,36 +23,6 @@ impl fmt::Display for UserParseError { } } -#[cfg(feature = "cache")] -fn lookup_by_global_cache(ctx: impl CacheHttp, s: &str) -> Option { - let users = &ctx.cache()?.users; - - let lookup_by_id = || users.get(&s.parse().ok()?).map(|u| u.clone()); - - let lookup_by_mention = || users.get(&crate::utils::parse_user_mention(s)?).map(|u| u.clone()); - - let lookup_by_name_and_discrim = || { - let (name, discrim) = crate::utils::parse_user_tag(s)?; - users.iter().find_map(|m| { - let user = m.value(); - (user.discriminator == discrim && user.name.eq_ignore_ascii_case(name)) - .then(|| user.clone()) - }) - }; - - let lookup_by_name = || { - users.iter().find_map(|m| { - let user = m.value(); - (&*user.name == s).then(|| user.clone()) - }) - }; - - lookup_by_id() - .or_else(lookup_by_mention) - .or_else(lookup_by_name_and_discrim) - .or_else(lookup_by_name) -} - /// Look up a user by a string case-insensitively. /// /// Requires the cache feature to be enabled. If a user is not in cache, they will not be found! @@ -72,13 +42,7 @@ impl ArgumentConvert for User { channel_id: Option, s: &str, ) -> Result { - // Try to look up in global user cache via a variety of methods - #[cfg(feature = "cache")] - if let Some(user) = lookup_by_global_cache(&ctx, s) { - return Ok(user); - } - - // If not successful, convert as a Member which uses HTTP endpoints instead of cache + // Convert as a Member which uses HTTP endpoints instead of cache if let Ok(member) = Member::convert(&ctx, guild_id, channel_id, s).await { return Ok(member.user); } diff --git a/src/utils/content_safe.rs b/src/utils/content_safe.rs index 818eab287ae..2eaefb26b09 100644 --- a/src/utils/content_safe.rs +++ b/src/utils/content_safe.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; -use crate::cache::Cache; -use crate::model::id::GuildId; +use crate::model::guild::Guild; use crate::model::mention::Mention; use crate::model::user::User; @@ -14,7 +13,6 @@ pub struct ContentSafeOptions { clean_here: bool, clean_everyone: bool, show_discriminator: bool, - guild_reference: Option, } impl ContentSafeOptions { @@ -64,15 +62,6 @@ impl ContentSafeOptions { self } - /// If set, [`content_safe`] will replace a user mention with the user's display name in passed - /// `guild`. - #[must_use] - pub fn display_as_member_from>(mut self, guild: G) -> Self { - self.guild_reference = Some(guild.into()); - - self - } - /// If set, [`content_safe`] will replace `@here` with a non-pinging alternative. #[must_use] pub fn clean_here(mut self, b: bool) -> Self { @@ -100,13 +89,12 @@ impl Default for ContentSafeOptions { clean_here: true, clean_everyone: true, show_discriminator: true, - guild_reference: None, } } } /// Transforms role, channel, user, `@everyone` and `@here` mentions into raw text by using the -/// [`Cache`] and the users passed in with `users`. +/// Guild and the users passed in with `users`. /// /// [`ContentSafeOptions`] decides what kind of mentions should be filtered and how the raw-text /// will be displayed. @@ -116,15 +104,14 @@ impl Default for ContentSafeOptions { /// Sanitise an `@everyone` mention. /// /// ```rust -/// # use serenity::client::Cache; -/// # -/// # let cache = Cache::default(); +/// # let cache = serenity::client::Cache::default(); +/// # let guild = serenity::model::guild::Guild::default(); /// use serenity::utils::{content_safe, ContentSafeOptions}; /// /// let with_mention = "@everyone"; -/// let without_mention = content_safe(&cache, &with_mention, &ContentSafeOptions::default(), &[]); +/// let without_mention = content_safe(&guild, &with_mention, &ContentSafeOptions::default(), &[]); /// -/// assert_eq!("@\u{200B}everyone".to_string(), without_mention); +/// assert_eq!("@\u{200B}everyone", without_mention); /// ``` /// /// Filtering out mentions from a message. @@ -135,16 +122,26 @@ impl Default for ContentSafeOptions { /// use serenity::utils::{content_safe, ContentSafeOptions}; /// /// fn filter_message(cache: &Cache, message: &Message) -> String { -/// content_safe(cache, &message.content, &ContentSafeOptions::default(), &message.mentions) +/// if let Some(guild) = message.guild(cache) { +/// content_safe( +/// &guild, +/// &message.content, +/// &ContentSafeOptions::default(), +/// &message.mentions, +/// ) +/// } else { +/// // We don't need to clean messages in DMs +/// message.content.to_string() +/// } /// } /// ``` pub fn content_safe( - cache: impl AsRef, + guild: &Guild, s: impl AsRef, options: &ContentSafeOptions, users: &[User], ) -> String { - let mut content = clean_mentions(&cache, s, options, users); + let mut content = clean_mentions(guild, s, options, users); if options.clean_here { content = content.replace("@here", "@\u{200B}here"); @@ -158,7 +155,7 @@ pub fn content_safe( } fn clean_mentions( - cache: impl AsRef, + guild: &Guild, s: impl AsRef, options: &ContentSafeOptions, users: &[User], @@ -197,7 +194,7 @@ fn clean_mentions( // NOTE: numeric strings that are too large to fit into u64 will not parse // correctly and will be left unchanged. if let Ok(mention) = mention_str.parse() { - content.push_str(&clean_mention(&cache, mention, options, users)); + content.push_str(&clean_mention(guild, mention, options, users)); cleaned = true; } } @@ -214,54 +211,39 @@ fn clean_mentions( } fn clean_mention( - cache: impl AsRef, + guild: &Guild, mention: Mention, options: &ContentSafeOptions, users: &[User], ) -> Cow<'static, str> { - let cache = cache.as_ref(); match mention { Mention::Channel(id) => { - #[allow(deprecated)] // This is reworked on next already. - if let Some(channel) = id.to_channel_cached(cache) { + if let Some(channel) = guild.channels.get(&id) { format!("#{}", channel.name).into() } else { "#deleted-channel".into() } }, - Mention::Role(id) => options - .guild_reference - .and_then(|id| cache.guild(id)) - .and_then(|g| g.roles.get(&id).map(|role| format!("@{}", role.name).into())) - .unwrap_or(Cow::Borrowed("@deleted-role")), + Mention::Role(id) => guild + .roles + .get(&id) + .map_or(Cow::Borrowed("@deleted-role"), |role| format!("@{}", role.name).into()), Mention::User(id) => { - if let Some(guild_id) = options.guild_reference { - if let Some(guild) = cache.guild(guild_id) { - if let Some(member) = guild.members.get(&id) { - return if options.show_discriminator { - format!("@{}", member.distinct()) - } else { - format!("@{}", member.display_name()) - } - .into(); - } + if let Some(member) = guild.members.get(&id) { + if options.show_discriminator { + format!("@{}", member.distinct()).into() + } else { + format!("@{}", member.display_name()).into() } - } - - let get_username = |user: &User| { + } else if let Some(user) = users.iter().find(|u| u.id == id) { if options.show_discriminator { - format!("@{}", user.tag()) + format!("@{}", user.tag()).into() } else { - format!("@{}", user.name) + format!("@{}", user.name).into() } - .into() - }; - - cache - .user(id) - .map(|u| get_username(&u)) - .or_else(|| users.iter().find(|u| u.id == id).map(get_username)) - .unwrap_or(Cow::Borrowed("@invalid-user")) + } else { + "@invalid-user".into() + } }, } } @@ -269,12 +251,11 @@ fn clean_mention( #[allow(clippy::non_ascii_literal)] #[cfg(test)] mod tests { - use std::sync::Arc; - use super::*; use crate::model::channel::*; use crate::model::guild::*; - use crate::model::id::{ChannelId, RoleId, UserId}; + use crate::model::id::{ChannelId, GuildId, RoleId, UserId}; + use crate::model::user::User; #[test] fn test_content_safe() { @@ -284,17 +265,13 @@ mod tests { ..Default::default() }; - let outside_cache_user = User { - id: UserId::new(100000000000000001), - name: "Boat".to_string().into(), - ..Default::default() - }; - - let mut guild = Guild { + let no_member_guild = Guild { id: GuildId::new(381880193251409931), ..Default::default() }; + let mut guild = no_member_guild.clone(); + let member = Member { nick: Some("Ferris".to_string().into()), ..Default::default() @@ -312,14 +289,9 @@ mod tests { ..Default::default() }; - let cache = Arc::new(Cache::default()); - guild.channels.insert(channel.id, channel.clone()); guild.members.insert(user.id, member.clone()); guild.roles.insert(role.id, role); - cache.users.insert(user.id, user.clone()); - cache.guilds.insert(guild.id, guild.clone()); - cache.channels.insert(channel.id, guild.id); let with_user_mentions = "<@!100000000000000000> <@!000000000000000000> <@123> <@!123> \ <@!123123123123123123123> <@123> <@123123123123123123> <@!invalid> \ @@ -327,7 +299,7 @@ mod tests { <@!i)/==(<<>z/9080)> <@!1231invalid> <@invalid123> \ <@123invalid> <@> <@ "; - let without_user_mentions = "@Crab <@!000000000000000000> @invalid-user @invalid-user \ + let without_user_mentions = "@Ferris <@!000000000000000000> @invalid-user @invalid-user \ <@!123123123123123123123> @invalid-user @invalid-user <@!invalid> \ <@invalid> <@日本語 한국어$§)[/__#\\(/&2032$§#> \ <@!i)/==(<<>z/9080)> <@!1231invalid> <@invalid123> \ @@ -335,49 +307,31 @@ mod tests { // User mentions let options = ContentSafeOptions::default(); - assert_eq!(without_user_mentions, content_safe(&cache, with_user_mentions, &options, &[])); - - let options = ContentSafeOptions::default(); - assert_eq!( - format!("@{}", user.name), - content_safe(&cache, "<@!100000000000000000>", &options, &[]) - ); - - let options = ContentSafeOptions::default(); - assert_eq!( - format!("@{}", user.name), - content_safe(&cache, "<@100000000000000000>", &options, &[]) - ); - - let options = ContentSafeOptions::default(); - assert_eq!("@invalid-user", content_safe(&cache, "<@100000000000000001>", &options, &[])); + assert_eq!(without_user_mentions, content_safe(&guild, with_user_mentions, &options, &[])); - let options = ContentSafeOptions::default(); assert_eq!( - format!("@{}", outside_cache_user.name), - content_safe(&cache, "<@100000000000000001>", &options, &[outside_cache_user]) + "@invalid-user", + content_safe(&no_member_guild, "<@100000000000000001>", &options, &[]) ); - let options = options.show_discriminator(false); + let options = ContentSafeOptions::default().show_discriminator(false); assert_eq!( format!("@{}", user.name), - content_safe(&cache, "<@!100000000000000000>", &options, &[]) + content_safe(&no_member_guild, "<@!100000000000000000>", &options, &[user.clone()]) ); - let options = options.show_discriminator(false); assert_eq!( - format!("@{}", user.name), - content_safe(&cache, "<@100000000000000000>", &options, &[]) + "@invalid-user", + content_safe(&no_member_guild, "<@!100000000000000000>", &options, &[]) ); - let options = options.display_as_member_from(guild.id); assert_eq!( - format!("@{}", member.nick.unwrap()), - content_safe(&cache, "<@!100000000000000000>", &options, &[]) + format!("@{}", member.nick.as_ref().unwrap()), + content_safe(&guild, "<@100000000000000000>", &options, &[]) ); let options = options.clean_user(false); - assert_eq!(with_user_mentions, content_safe(&cache, with_user_mentions, &options, &[])); + assert_eq!(with_user_mentions, content_safe(&guild, with_user_mentions, &options, &[])); // Channel mentions let with_channel_mentions = "<#> <#deleted-channel> #deleted-channel <#1> \ @@ -390,13 +344,13 @@ mod tests { assert_eq!( without_channel_mentions, - content_safe(&cache, with_channel_mentions, &options, &[]) + content_safe(&guild, with_channel_mentions, &options, &[]) ); let options = options.clean_channel(false); assert_eq!( with_channel_mentions, - content_safe(&cache, with_channel_mentions, &options, &[]) + content_safe(&guild, with_channel_mentions, &options, &[]) ); // Role mentions @@ -408,10 +362,10 @@ mod tests { @ferris-club-member @deleted-role \ <@&111111111111111111111111111111> <@&@deleted-role"; - assert_eq!(without_role_mentions, content_safe(&cache, with_role_mentions, &options, &[])); + assert_eq!(without_role_mentions, content_safe(&guild, with_role_mentions, &options, &[])); let options = options.clean_role(false); - assert_eq!(with_role_mentions, content_safe(&cache, with_role_mentions, &options, &[])); + assert_eq!(with_role_mentions, content_safe(&guild, with_role_mentions, &options, &[])); // Everyone mentions let with_everyone_mention = "@everyone"; @@ -420,13 +374,13 @@ mod tests { assert_eq!( without_everyone_mention, - content_safe(&cache, with_everyone_mention, &options, &[]) + content_safe(&guild, with_everyone_mention, &options, &[]) ); let options = options.clean_everyone(false); assert_eq!( with_everyone_mention, - content_safe(&cache, with_everyone_mention, &options, &[]) + content_safe(&guild, with_everyone_mention, &options, &[]) ); // Here mentions @@ -434,9 +388,9 @@ mod tests { let without_here_mention = "@\u{200B}here"; - assert_eq!(without_here_mention, content_safe(&cache, with_here_mention, &options, &[])); + assert_eq!(without_here_mention, content_safe(&guild, with_here_mention, &options, &[])); let options = options.clean_here(false); - assert_eq!(with_here_mention, content_safe(&cache, with_here_mention, &options, &[])); + assert_eq!(with_here_mention, content_safe(&guild, with_here_mention, &options, &[])); } } From eb85287673c6cc6d9a3a56a3f2e52dcb5d141c31 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Thu, 28 Dec 2023 16:01:31 +0000 Subject: [PATCH 012/159] Change `Embed::fields` to use `FixedArray` (#2674) `Embed::fields` previously had to stay as a `Vec` due to `CreateEmbed` wrapping around it, but by implementing `Serialize` manually we can overwrite the `Embed::fields` with a normal `Vec`, for a small performance hit on serialization while saving some space for all stored `Embed`s. --- src/builder/create_embed.rs | 87 ++++++++++++++++++++++--------------- src/model/channel/embed.rs | 4 +- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/builder/create_embed.rs b/src/builder/create_embed.rs index 7ba2443bd52..8f80eb3dc1e 100644 --- a/src/builder/create_embed.rs +++ b/src/builder/create_embed.rs @@ -21,9 +21,22 @@ use crate::model::prelude::*; /// A builder to create an embed in a message /// /// [Discord docs](https://discord.com/developers/docs/resources/channel#embed-object) -#[derive(Clone, Debug, Serialize, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[must_use] -pub struct CreateEmbed(Embed); +pub struct CreateEmbed { + inner: Embed, + fields: Vec, +} + +impl serde::Serialize for CreateEmbed { + fn serialize(&self, serializer: S) -> StdResult { + let owned_self = self.clone(); + + let mut embed = owned_self.inner; + embed.fields = owned_self.fields.into(); + embed.serialize(serializer) + } +} impl CreateEmbed { /// Equivalent to [`Self::default`]. @@ -35,7 +48,7 @@ impl CreateEmbed { /// /// Refer to the documentation for [`CreateEmbedAuthor`] for more information. pub fn author(mut self, author: CreateEmbedAuthor) -> Self { - self.0.author = Some(author.0); + self.inner.author = Some(author.0); self } @@ -50,7 +63,7 @@ impl CreateEmbed { /// Set the colour of the left-hand side of the embed. #[inline] pub fn colour>(mut self, colour: C) -> Self { - self.0.colour = Some(colour.into()); + self.inner.colour = Some(colour.into()); self } @@ -59,7 +72,7 @@ impl CreateEmbed { /// **Note**: This can't be longer than 4096 characters. #[inline] pub fn description(mut self, description: impl Into) -> Self { - self.0.description = Some(description.into().into()); + self.inner.description = Some(description.into().into()); self } @@ -74,7 +87,7 @@ impl CreateEmbed { value: impl Into, inline: bool, ) -> Self { - self.0.fields.push(EmbedField::new(name, value, inline)); + self.fields.push(EmbedField::new(name, value, inline)); self } @@ -88,7 +101,7 @@ impl CreateEmbed { { let fields = fields.into_iter().map(|(name, value, inline)| EmbedField::new(name, value, inline)); - self.0.fields.extend(fields); + self.fields.extend(fields); self } @@ -96,7 +109,7 @@ impl CreateEmbed { /// /// Refer to the documentation for [`CreateEmbedFooter`] for more information. pub fn footer(mut self, footer: CreateEmbedFooter) -> Self { - self.0.footer = Some(footer.0); + self.inner.footer = Some(footer.0); self } @@ -106,7 +119,7 @@ impl CreateEmbed { /// for rules on naming local attachments. #[inline] pub fn image(mut self, url: impl Into) -> Self { - self.0.image = Some(EmbedImage { + self.inner.image = Some(EmbedImage { url: url.into().into(), proxy_url: None, height: None, @@ -118,7 +131,7 @@ impl CreateEmbed { /// Set the thumbnail of the embed. #[inline] pub fn thumbnail(mut self, url: impl Into) -> Self { - self.0.thumbnail = Some(EmbedThumbnail { + self.inner.thumbnail = Some(EmbedThumbnail { url: url.into().into(), proxy_url: None, height: None, @@ -143,21 +156,21 @@ impl CreateEmbed { /// ``` #[inline] pub fn timestamp>(mut self, timestamp: T) -> Self { - self.0.timestamp = Some(timestamp.into()); + self.inner.timestamp = Some(timestamp.into()); self } /// Set the title of the embed. #[inline] pub fn title(mut self, title: impl Into) -> Self { - self.0.title = Some(title.into().into()); + self.inner.title = Some(title.into().into()); self } /// Set the URL to direct to when clicking on the title. #[inline] pub fn url(mut self, url: impl Into) -> Self { - self.0.url = Some(url.into().into()); + self.inner.url = Some(url.into().into()); self } @@ -179,24 +192,24 @@ impl CreateEmbed { #[cfg(feature = "http")] pub(super) fn check_length(&self) -> Result<()> { let mut length = 0; - if let Some(ref author) = self.0.author { + if let Some(ref author) = self.inner.author { length += author.name.chars().count(); } - if let Some(ref description) = self.0.description { + if let Some(ref description) = self.inner.description { length += description.chars().count(); } - for field in &self.0.fields { + for field in &self.fields { length += field.name.chars().count(); length += field.value.chars().count(); } - if let Some(ref footer) = self.0.footer { + if let Some(ref footer) = self.inner.footer { length += footer.text.chars().count(); } - if let Some(ref title) = self.0.title { + if let Some(ref title) = self.inner.title { length += title.chars().count(); } @@ -208,27 +221,33 @@ impl CreateEmbed { impl Default for CreateEmbed { /// Creates a builder with default values, setting the `type` to `rich`. fn default() -> Self { - Self(Embed { + Self { fields: Vec::new(), - description: None, - thumbnail: None, - timestamp: None, - kind: Some("rich".to_string().into()), - author: None, - colour: None, - footer: None, - image: None, - title: None, - url: None, - video: None, - provider: None, - }) + inner: Embed { + fields: FixedArray::new(), + description: None, + thumbnail: None, + timestamp: None, + kind: Some("rich".to_string().into()), + author: None, + colour: None, + footer: None, + image: None, + title: None, + url: None, + video: None, + provider: None, + }, + } } } impl From for CreateEmbed { - fn from(embed: Embed) -> Self { - Self(embed) + fn from(mut embed: Embed) -> Self { + Self { + fields: std::mem::take(&mut embed.fields).into_vec(), + inner: embed, + } } } diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs index 23b924d8b2c..4d6f2568eba 100644 --- a/src/model/channel/embed.rs +++ b/src/model/channel/embed.rs @@ -66,8 +66,8 @@ pub struct Embed { /// /// The maximum number of fields is 25. #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] - pub fields: Vec, + #[serde(skip_serializing_if = "FixedArray::is_empty")] + pub fields: FixedArray, } /// An author object in an embed. From 6b74b4bf64fc671b8a1231d952f785063f94e3f4 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Thu, 28 Dec 2023 16:17:26 +0000 Subject: [PATCH 013/159] Use `FixedArray` and `FixedString` in model enums (#2675) Simply missed these when finding and replacing. --- src/model/application/command_interaction.rs | 8 ++-- src/model/channel/message.rs | 2 +- src/model/guild/audit_log/change.rs | 44 ++++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/model/application/command_interaction.rs b/src/model/application/command_interaction.rs index e63731222fc..44a7b2b7557 100644 --- a/src/model/application/command_interaction.rs +++ b/src/model/application/command_interaction.rs @@ -346,10 +346,10 @@ impl CommandData { for opt in opts { let value = match &opt.value { CommandDataOptionValue::SubCommand(opts) => { - ResolvedValue::SubCommand(resolve_options(opts, resolved)) + ResolvedValue::SubCommand(resolve_options(opts, resolved).into()) }, CommandDataOptionValue::SubCommandGroup(opts) => { - ResolvedValue::SubCommandGroup(resolve_options(opts, resolved)) + ResolvedValue::SubCommandGroup(resolve_options(opts, resolved).into()) }, CommandDataOptionValue::Autocomplete { kind, @@ -454,8 +454,8 @@ pub enum ResolvedValue<'a> { Integer(i64), Number(f64), String(&'a str), - SubCommand(Vec>), - SubCommandGroup(Vec>), + SubCommand(FixedArray>), + SubCommandGroup(FixedArray>), Attachment(&'a Attachment), Channel(&'a PartialChannel), Role(&'a Role), diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 7d3169cf4bd..2b396dd5a35 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -1312,7 +1312,7 @@ impl MessageId { #[derive(Clone, Debug, Serialize)] #[serde(untagged)] pub enum Nonce { - String(String), + String(FixedString), Number(u64), } diff --git a/src/model/guild/audit_log/change.rs b/src/model/guild/audit_log/change.rs index e091e68ae00..d3bb4b71063 100644 --- a/src/model/guild/audit_log/change.rs +++ b/src/model/guild/audit_log/change.rs @@ -30,7 +30,7 @@ pub struct AffectedRole { #[non_exhaustive] pub enum EntityType { Int(u64), - Str(String), + Str(FixedString), } impl<'de> serde::Deserialize<'de> for EntityType { @@ -72,20 +72,20 @@ macro_rules! generate_change { RolesAdded { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "old_value")] - old: Option>, + old: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "new_value")] - new: Option>, + new: Option>, }, /// Role was removed to a member. #[serde(rename = "$remove")] RolesRemove { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "old_value")] - old: Option>, + old: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "new_value")] - new: Option>, + new: Option>, }, /// Unknown key was changed. @@ -120,7 +120,7 @@ macro_rules! generate_change { } generate_change! { - "actions" => Actions(Vec), + "actions" => Actions(FixedArray), /// AFK channel was changed. "afk_channel_id" => AfkChannelId(ChannelId), /// AFK timeout duration was changed. @@ -131,7 +131,7 @@ generate_change! { "application_id" => ApplicationId(ApplicationId), /// Thread is now archived/unarchived. "archived" => Archived(bool), - "asset" => Asset(String), + "asset" => Asset(FixedString), /// Auto archive duration of a thread was changed. "auto_archive_duration" => AutoArchiveDuration(u16), /// Availability of a sticker was changed. @@ -145,7 +145,7 @@ generate_change! { /// Channel for invite code or guild scheduled event was changed. "channel_id" => ChannelId(ChannelId), /// Invite code was changed. - "code" => Code(String), + "code" => Code(FixedString), /// Role color was changed. "color" => Color(u32), /// Member timeout state was changed. @@ -159,7 +159,7 @@ generate_change! { /// Permission on a text or voice channel was denied for a role. "deny" => Deny(Permissions), /// Description for guild, sticker, or guild scheduled event was changed. - "description" => Description(String), + "description" => Description(FixedString), /// Guild's discovery splash was changed. "discovery_splash_hash" => DiscoverySplashHash(ImageHash), "enabled" => Enabled(bool), @@ -168,8 +168,8 @@ generate_change! { /// Entity type of guild scheduled event was changed. "entity_type" => EntityType(u64), "event_type" => EventType(EventType), - "exempt_channels" => ExemptChannels(Vec), - "exempt_roles" => ExemptRoles(Vec), + "exempt_channels" => ExemptChannels(FixedArray), + "exempt_roles" => ExemptRoles(FixedArray), /// Behavior of the expiration of an integration was changed. "expire_behavior" => ExpireBehavior(u64), /// Grace period of the expiration of an integration was changed. @@ -195,7 +195,7 @@ generate_change! { /// ID of the user who created the invite. "inviter_id" => InviterId(UserId), /// Location for a guild scheduled event was changed. - "location" => Location(String), + "location" => Location(FixedString), /// Thread was locked/unlocked. "locked" => Locked(bool), /// How long invite code lasts was changed. @@ -209,21 +209,21 @@ generate_change! { /// User was server muted/unmuted. "mute" => Mute(bool), /// Name of an entity was changed. - "name" => Name(String), + "name" => Name(FixedString), /// Nickname of a member was changed. - "nick" => Nick(String), + "nick" => Nick(FixedString), /// Channel NSFW restriction was changed. "nsfw" => Nsfw(bool), /// Owner of a guild was changed. "owner_id" => OwnerId(UserId), /// Permissions on a channel were changed. - "permission_overwrites" => PermissionOverwrites(Vec), + "permission_overwrites" => PermissionOverwrites(FixedArray), /// Permissions for a role were changed. "permissions" => Permissions(Permissions), /// Channel or role position was changed. "position" => Position(u32), /// Preferred locale of a guild was changed. - "preferred_locale" => PreferredLocale(String), + "preferred_locale" => PreferredLocale(FixedString), /// Privacy level of the stage instance was changed. "privacy_level" => PrivacyLevel(u64), /// Number of days after which inactive and role-unassigned members are kicked was changed. @@ -233,7 +233,7 @@ generate_change! { /// Ratelimit per user in a text channel was changed. "rate_limit_per_user" => RateLimitPerUser(u16), /// Region of a guild was changed. - "region" => Region(String), + "region" => Region(FixedString), /// ID of the rules channel was changed. "rules_channel_id" => RulesChannelId(ChannelId), /// Invite splash page artwork was changed. @@ -245,23 +245,23 @@ generate_change! { /// ID of the system channel was changed. "system_channel_id" => SystemChannelId(ChannelId), /// Related emoji of a sticker was changed. - "tags" => Tags(String), + "tags" => Tags(FixedString), /// Whether an invite is temporary or never expires was changed. "temporary" => Temporary(bool), /// Topic of a text channel or stage instance was changed. - "topic" => Topic(String), + "topic" => Topic(FixedString), "trigger_metadata" => TriggerMetadata(TriggerMetadata), "trigger_type" => TriggerType(TriggerType), /// Type of a created entity. "type" => Type(EntityType), /// Unicode emoji of a role icon was changed. - "unicode_emoji" => UnicodeEmoji(String), + "unicode_emoji" => UnicodeEmoji(FixedString), /// Maximum number of users in a voice channel was changed. "user_limit" => UserLimit(u64), /// Number of uses of an invite was changed. "uses" => Uses(u64), /// Guild invite vanity url was changed. - "vanity_url_code" => VanityUrlCode(String), + "vanity_url_code" => VanityUrlCode(FixedString), /// Required verification level for new members was changed. "verification_level" => VerificationLevel(VerificationLevel), /// Channel of the server widget was changed. @@ -302,7 +302,7 @@ mod tests { fn entity_type_variant() { let value = Change::Type { old: Some(EntityType::Int(123)), - new: Some(EntityType::Str("discord".into())), + new: Some(EntityType::Str("discord".to_string().into())), }; assert_json(&value, json!({"key": "type", "old_value": 123, "new_value": "discord"})); } From 55516a60f2d221b2d1ae0b4421deb1243dc502e4 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Sun, 31 Dec 2023 23:51:57 +0000 Subject: [PATCH 014/159] Bitpack boolean fields using fancy proc macro (#2673) This uses the `bool_to_bitflags` macro to remove boolean (and optional boolean) fields from structs and pack them into a bitflags invocation, so a struct with many bools will only use one or two bytes, instead of a byte per bool as is. This requires using getters and setters for the boolean fields, which changes user experience and is hard to document, which is a significant downside, but is such a nice change and will just become more and more efficient as time goes on. --- Cargo.toml | 3 +- examples/e05_command_framework/src/main.rs | 4 +- src/builder/create_command.rs | 7 +- src/builder/edit_role.rs | 4 +- src/cache/event.rs | 28 ++-- src/framework/standard/configuration.rs | 114 ++++++--------- src/framework/standard/mod.rs | 8 +- src/framework/standard/parse/map.rs | 7 +- src/framework/standard/parse/mod.rs | 8 +- src/lib.rs | 1 - src/model/application/command.rs | 6 +- src/model/channel/message.rs | 3 +- src/model/channel/mod.rs | 3 +- src/model/channel/reaction.rs | 2 +- src/model/connection.rs | 3 +- src/model/event.rs | 12 +- src/model/gateway.rs | 19 ++- src/model/guild/emoji.rs | 7 +- src/model/guild/integration.rs | 3 +- src/model/guild/member.rs | 34 +++-- src/model/guild/mod.rs | 3 +- src/model/guild/partial_guild.rs | 20 ++- src/model/guild/role.rs | 25 +--- src/model/mention.rs | 9 +- src/model/user.rs | 5 +- src/model/voice.rs | 10 +- src/utils/content_safe.rs | 153 ++++++++------------- src/utils/custom_message.rs | 6 +- src/utils/message_builder.rs | 61 ++++---- 29 files changed, 260 insertions(+), 308 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f995b2f489..318bbbdb3b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ secrecy = { version = "0.8.0", features = ["serde"] } arrayvec = { version = "0.7.4", features = ["serde"] } serde_cow = { version = "0.1.0" } small-fixed-array = { git = "https://github.com/GnomedDev/small-fixed-array", features = ["serde", "log_using_tracing"] } +bool_to_bitflags = { git = "https://github.com/GnomedDev/bool-to-bitflags", version = "0.1.0" } # Optional dependencies fxhash = { version = "0.2.1", optional = true } simd-json = { version = "0.13.4", optional = true } @@ -126,7 +127,7 @@ simd_json = ["simd-json", "typesize?/simd_json"] # Enables temporary caching in functions that retrieve data via the HTTP API. temp_cache = ["cache", "mini-moka", "typesize?/mini_moka"] -typesize = ["dep:typesize", "small-fixed-array/typesize"] +typesize = ["dep:typesize", "small-fixed-array/typesize", "bool_to_bitflags/typesize"] # Removed feature (https://github.com/serenity-rs/serenity/pull/2246) absolute_ratelimits = [] diff --git a/examples/e05_command_framework/src/main.rs b/examples/e05_command_framework/src/main.rs index 6bef9754cfb..129d7c70994 100644 --- a/examples/e05_command_framework/src/main.rs +++ b/examples/e05_command_framework/src/main.rs @@ -349,10 +349,10 @@ async fn say(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { if let Some(guild) = msg.guild(&ctx.cache) { // By default roles, users, and channel mentions are cleaned. let settings = ContentSafeOptions::default() - // We do not want to clean channal mentions as they do not ping users. + // We do not want to clean channel mentions as they do not ping users. .clean_channel(false); - x = content_safe(&guild, x, &settings, &msg.mentions); + x = content_safe(&guild, x, settings, &msg.mentions); } msg.channel_id.say(&ctx.http, x).await?; diff --git a/src/builder/create_command.rs b/src/builder/create_command.rs index 39d8c9dd31a..5b1da3c1b9d 100644 --- a/src/builder/create_command.rs +++ b/src/builder/create_command.rs @@ -30,8 +30,7 @@ impl CreateCommandOption { name_localizations: None, description: description.into().into(), description_localizations: None, - required: false, - autocomplete: false, + __generated_flags: CommandOptionGeneratedFlags::empty(), min_value: None, max_value: None, min_length: None, @@ -104,7 +103,7 @@ impl CreateCommandOption { /// /// **Note**: This defaults to `false`. pub fn required(mut self, required: bool) -> Self { - self.0.required = required; + self.0.set_required(required); self } @@ -203,7 +202,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.autocomplete = value; + self.0.set_autocomplete(value); self } diff --git a/src/builder/edit_role.rs b/src/builder/edit_role.rs index 080e816da3e..ee0e886625b 100644 --- a/src/builder/edit_role.rs +++ b/src/builder/edit_role.rs @@ -77,8 +77,8 @@ impl<'a> EditRole<'a> { /// Creates a new builder with the values of the given [`Role`]. pub fn from_role(role: &Role) -> Self { EditRole { - hoist: Some(role.hoist), - mentionable: Some(role.mentionable), + hoist: Some(role.hoist()), + mentionable: Some(role.mentionable()), name: Some(role.name.clone()), permissions: Some(role.permissions.bits()), position: Some(role.position), diff --git a/src/cache/event.rs b/src/cache/event.rs index d88836201bd..f14e4a4f86b 100644 --- a/src/cache/event.rs +++ b/src/cache/event.rs @@ -33,7 +33,7 @@ use crate::model::event::{ VoiceStateUpdateEvent, }; use crate::model::gateway::ShardInfo; -use crate::model::guild::{Guild, GuildMemberFlags, Member, Role}; +use crate::model::guild::{Guild, GuildMemberFlags, Member, MemberGeneratedFlags, Role}; use crate::model::id::ShardId; use crate::model::user::{CurrentUser, OnlineStatus}; use crate::model::voice::VoiceState; @@ -189,13 +189,13 @@ impl CacheUpdate for GuildMemberUpdateEvent { member.nick.clone_from(&self.nick); member.roles.clone_from(&self.roles); member.user.clone_from(&self.user); - member.pending.clone_from(&self.pending); member.premium_since.clone_from(&self.premium_since); - member.deaf.clone_from(&self.deaf); - member.mute.clone_from(&self.mute); member.avatar.clone_from(&self.avatar); member.communication_disabled_until.clone_from(&self.communication_disabled_until); member.unusual_dm_activity_until.clone_from(&self.unusual_dm_activity_until); + member.set_pending(self.pending()); + member.set_deaf(self.deaf()); + member.set_mute(self.mute()); item } else { @@ -203,22 +203,26 @@ impl CacheUpdate for GuildMemberUpdateEvent { }; if item.is_none() { - guild.members.insert(self.user.id, Member { - deaf: false, + let mut new_member = Member { + __generated_flags: MemberGeneratedFlags::empty(), guild_id: self.guild_id, joined_at: Some(self.joined_at), - mute: false, nick: self.nick.clone(), roles: self.roles.clone(), user: self.user.clone(), - pending: self.pending, premium_since: self.premium_since, permissions: None, avatar: self.avatar, communication_disabled_until: self.communication_disabled_until, flags: GuildMemberFlags::default(), unusual_dm_activity_until: self.unusual_dm_activity_until, - }); + }; + + new_member.set_pending(self.pending()); + new_member.set_deaf(self.deaf()); + new_member.set_mute(self.mute()); + + guild.members.insert(self.user.id, new_member); } item @@ -317,7 +321,7 @@ impl CacheUpdate for GuildUpdateEvent { guild.system_channel_id = self.guild.system_channel_id; guild.verification_level = self.guild.verification_level; guild.widget_channel_id = self.guild.widget_channel_id; - guild.widget_enabled = self.guild.widget_enabled; + guild.set_widget_enabled(self.guild.widget_enabled()); } None @@ -415,20 +419,18 @@ impl CacheUpdate for PresenceUpdateEvent { // Create a partial member instance out of the presence update data. if let Some(user) = self.presence.user.to_user() { guild.members.entry(self.presence.user.id).or_insert_with(|| Member { - deaf: false, guild_id, joined_at: None, - mute: false, nick: None, user, roles: FixedArray::default(), - pending: false, premium_since: None, permissions: None, avatar: None, communication_disabled_until: None, flags: GuildMemberFlags::default(), unusual_dm_activity_until: None, + __generated_flags: MemberGeneratedFlags::empty(), }); } } diff --git a/src/framework/standard/configuration.rs b/src/framework/standard/configuration.rs index 9121fb2e347..6c8921ee3ca 100644 --- a/src/framework/standard/configuration.rs +++ b/src/framework/standard/configuration.rs @@ -101,24 +101,56 @@ impl From<(bool, bool, bool)> for WithWhiteSpace { /// [`Client`]: crate::Client /// [`StandardFramework`]: super::StandardFramework /// [default implementation]: Self::default +#[bool_to_bitflags::bool_to_bitflags( + getter_prefix = "get_", + setter_prefix = "", + private_getters, + document_setters, + owning_setters +)] #[derive(Clone)] pub struct Configuration { - pub(crate) allow_dm: bool, pub(crate) with_whitespace: WithWhiteSpace, - pub(crate) by_space: bool, pub(crate) blocked_guilds: HashSet, pub(crate) blocked_users: HashSet, pub(crate) allowed_channels: HashSet, pub(crate) disabled_commands: HashSet, pub(crate) dynamic_prefixes: Vec, - pub(crate) ignore_bots: bool, - pub(crate) ignore_webhooks: bool, pub(crate) on_mention: Option, pub(crate) owners: HashSet, pub(crate) prefixes: Vec, - pub(crate) no_dm_prefix: bool, pub(crate) delimiters: Vec, - pub(crate) case_insensitive: bool, + /// If set to false, bot will ignore any private messages. + /// + /// **Note**: Defaults to `true`. + pub allow_dm: bool, + /// Whether the framework should split the message by a space first to parse the group or + /// command. If set to false, it will only test part of the message by the *length* of the + /// group's or command's names. + /// + /// **Note**: Defaults to `true` + pub by_space: bool, + /// Whether the bot should respond to other bots. + /// + /// For example, if this is set to false, then the bot will respond to any other bots including + /// itself. + /// + /// **Note**: Defaults to `true`. + pub ignore_bots: bool, + /// If set to true, bot will ignore all commands called by webhooks. + /// + /// **Note**: Defaults to `true`. + pub ignore_webhooks: bool, + /// Sets whether command execution can be done without a prefix. Works only in private + /// channels. + /// + /// **Note**: Defaults to `false`. + /// + /// # Note + /// + /// The `cache` feature is required. If disabled this does absolutely nothing. + pub no_dm_prefix: bool, + case_insensitive: bool, } impl Configuration { @@ -128,15 +160,6 @@ impl Configuration { Self::default() } - /// If set to false, bot will ignore any private messages. - /// - /// **Note**: Defaults to `true`. - #[must_use] - pub fn allow_dm(mut self, allow_dm: bool) -> Self { - self.allow_dm = allow_dm; - self - } - /// Whether to allow whitespace being optional between a prefix/group-prefix/command and a /// command. /// @@ -165,17 +188,6 @@ impl Configuration { self } - /// Whether the framework should split the message by a space first to parse the group or - /// command. If set to false, it will only test part of the message by the *length* of the - /// group's or command's names. - /// - /// **Note**: Defaults to `true` - #[must_use] - pub fn by_space(mut self, b: bool) -> Self { - self.by_space = b; - self - } - /// HashSet of channels Ids where commands will be working. /// /// **Note**: Defaults to an empty HashSet. @@ -351,27 +363,6 @@ impl Configuration { self } - /// Whether the bot should respond to other bots. - /// - /// For example, if this is set to false, then the bot will respond to any other bots including - /// itself. - /// - /// **Note**: Defaults to `true`. - #[must_use] - pub fn ignore_bots(mut self, ignore_bots: bool) -> Self { - self.ignore_bots = ignore_bots; - self - } - - /// If set to true, bot will ignore all commands called by webhooks. - /// - /// **Note**: Defaults to `true`. - #[must_use] - pub fn ignore_webhooks(mut self, ignore_webhooks: bool) -> Self { - self.ignore_webhooks = ignore_webhooks; - self - } - /// Whether or not to respond to commands initiated with `id_to_mention`. /// /// **Note**: that this can be used in conjunction with [`Self::prefix`]. @@ -485,20 +476,6 @@ impl Configuration { self } - /// Sets whether command execution can be done without a prefix. Works only in private channels. - /// - /// **Note**: Defaults to `false`. - /// - /// # Note - /// - /// The `cache` feature is required. If disabled this does absolutely nothing. - #[inline] - #[must_use] - pub fn no_dm_prefix(mut self, b: bool) -> Self { - self.no_dm_prefix = b; - self - } - /// Sets a single delimiter to be used when splitting the content after a command. /// /// **Note**: Defaults to a vector with a single element of `' '`. @@ -555,7 +532,7 @@ impl Configuration { /// **Note**: Defaults to `false`. #[must_use] pub fn case_insensitivity(mut self, cs: bool) -> Self { - self.case_insensitive = cs; + self = self.case_insensitive(cs); for prefix in &mut self.prefixes { *prefix = prefix.to_lowercase(); @@ -585,23 +562,20 @@ impl Default for Configuration { /// - **owners** to an empty HashSet /// - **prefix** to "~" fn default() -> Configuration { - Configuration { - allow_dm: true, + let config = Configuration { + __generated_flags: ConfigurationGeneratedFlags::empty(), with_whitespace: WithWhiteSpace::default(), - by_space: true, blocked_guilds: HashSet::default(), blocked_users: HashSet::default(), allowed_channels: HashSet::default(), - case_insensitive: false, delimiters: vec![Delimiter::Single(' ')], disabled_commands: HashSet::default(), dynamic_prefixes: Vec::new(), - ignore_bots: true, - ignore_webhooks: true, - no_dm_prefix: false, on_mention: None, owners: HashSet::default(), prefixes: vec![String::from("~")], - } + }; + + config.allow_dm(true).by_space(true).ignore_bots(true).ignore_webhooks(true) } } diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 4fb3e34917b..6dee7010292 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -203,8 +203,8 @@ impl StandardFramework { fn should_ignore(&self, msg: &Message) -> bool { let config = self.config.read(); - (config.ignore_bots && msg.author.bot) - || (config.ignore_webhooks && msg.webhook_id.is_some()) + (config.get_ignore_bots() && msg.author.bot()) + || (config.get_ignore_webhooks() && msg.webhook_id.is_some()) } async fn should_fail<'a>( @@ -637,7 +637,7 @@ impl Framework for StandardFramework { return; } - if prefix.is_none() && !(config.no_dm_prefix && msg.is_private()) { + if prefix.is_none() && !(config.get_no_dm_prefix() && msg.is_private()) { if let Some(normal) = &self.normal_message { normal(&mut ctx, &msg).await; } @@ -684,7 +684,7 @@ impl Framework for StandardFramework { match invoke { Invoke::Help(name) => { - if !config.allow_dm && msg.is_private() { + if !config.get_allow_dm() && msg.is_private() { return; } diff --git a/src/framework/standard/parse/map.rs b/src/framework/standard/parse/map.rs index 96309e5772a..9cfe9dc1f61 100644 --- a/src/framework/standard/parse/map.rs +++ b/src/framework/standard/parse/map.rs @@ -34,8 +34,11 @@ impl CommandMap { map.min_length = std::cmp::min(len, map.min_length); map.max_length = std::cmp::max(len, map.max_length); - let name = - if conf.case_insensitive { name.to_lowercase() } else { (*name).to_string() }; + let name = if conf.get_case_insensitive() { + name.to_lowercase() + } else { + (*name).to_string() + }; map.cmds.insert(name, (*cmd, Arc::clone(&sub_map))); } diff --git a/src/framework/standard/parse/mod.rs b/src/framework/standard/parse/mod.rs index bd02a80963a..727bf9e5b1d 100644 --- a/src/framework/standard/parse/mod.rs +++ b/src/framework/standard/parse/mod.rs @@ -95,7 +95,7 @@ fn permissions_in( #[inline] fn to_lowercase<'a>(config: &Configuration, s: &'a str) -> Cow<'a, str> { - if config.case_insensitive { + if config.get_case_insensitive() { Cow::Owned(s.to_lowercase()) } else { Cow::Borrowed(s) @@ -209,7 +209,7 @@ async fn check_discrepancy( return Err(DispatchError::OnlyForDM); } - if (!config.allow_dm || options.only_in() == OnlyIn::Guild) && msg.is_private() { + if (!config.get_allow_dm() || options.only_in() == OnlyIn::Guild) && msg.is_private() { return Err(DispatchError::OnlyForGuilds); } @@ -278,7 +278,7 @@ fn parse_cmd<'a>( ) -> BoxFuture<'a, Result<&'static Command, ParseError>> { async move { let (n, r) = - try_parse(stream, map, config.by_space, |s| to_lowercase(config, s).into_owned()); + try_parse(stream, map, config.get_by_space(), |s| to_lowercase(config, s).into_owned()); if config.disabled_commands.contains(&n) { return Err(ParseError::Dispatch { @@ -324,7 +324,7 @@ fn parse_group<'a>( map: &'a GroupMap, ) -> BoxFuture<'a, Result<(&'static CommandGroup, Arc), ParseError>> { async move { - let (n, o) = try_parse(stream, map, config.by_space, ToString::to_string); + let (n, o) = try_parse(stream, map, config.get_by_space(), ToString::to_string); if let Some((group, map, commands)) = o { stream.increment(n.len()); diff --git a/src/lib.rs b/src/lib.rs index 2d6fc5cf578..73d4aff67d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,7 +65,6 @@ #![allow( // Allowed to avoid breaking changes. clippy::module_name_repetitions, - clippy::struct_excessive_bools, clippy::unused_self, // Allowed as they are too pedantic clippy::cast_possible_truncation, diff --git a/src/model/application/command.rs b/src/model/application/command.rs index b85628ce7af..743b4802737 100644 --- a/src/model/application/command.rs +++ b/src/model/application/command.rs @@ -23,8 +23,9 @@ use crate::model::Permissions; /// The base command model that belongs to an application. /// /// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct Command { /// The command Id. @@ -270,8 +271,9 @@ enum_number! { /// The parameters for an [`Command`]. /// /// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct CommandOption { /// The option type. diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 2b396dd5a35..1e6bf99064b 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -31,8 +31,9 @@ use crate::utils; /// /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object) with some /// [extra fields](https://discord.com/developers/docs/topics/gateway-events#message-create-message-create-extra-fields). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct Message { /// The unique Id of the message. Can be used to calculate the creation date of the message. diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 519b3a7568e..11e2908696b 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -436,8 +436,9 @@ pub struct StageInstance { /// A thread data. /// /// [Discord docs](https://discord.com/developers/docs/resources/channel#thread-metadata-object). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct ThreadMetadata { /// Whether the thread is archived. diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs index 293cef013ee..6b6ad8faf46 100644 --- a/src/model/channel/reaction.rs +++ b/src/model/channel/reaction.rs @@ -432,7 +432,7 @@ impl From for ReactionType { impl From for ReactionType { fn from(emoji: Emoji) -> ReactionType { ReactionType::Custom { - animated: emoji.animated, + animated: emoji.animated(), id: emoji.id, name: Some(emoji.name), } diff --git a/src/model/connection.rs b/src/model/connection.rs index 338e4c03727..fcba6e0f186 100644 --- a/src/model/connection.rs +++ b/src/model/connection.rs @@ -6,7 +6,8 @@ use crate::internal::prelude::*; /// Information about a connection between the current user and a third party service. /// /// [Discord docs](https://discord.com/developers/docs/resources/user#connection-object-connection-structure). -#[derive(Clone, Debug, Deserialize, Serialize)] +#[bool_to_bitflags::bool_to_bitflags] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct Connection { /// The ID of the account on the other side of this connection. diff --git a/src/model/event.rs b/src/model/event.rs index ac5ad8e189d..6e6b20e0b69 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -10,6 +10,7 @@ use serde::de::Error as DeError; use serde::Serialize; use crate::constants::Opcode; +use crate::internal::prelude::*; use crate::model::prelude::*; use crate::model::utils::{ deserialize_val, @@ -19,8 +20,6 @@ use crate::model::utils::{ remove_from_map_opt, stickers, }; -use crate::constants::Opcode; -use crate::internal::prelude::*; /// Requires no gateway intents. /// @@ -244,8 +243,9 @@ pub struct GuildMemberRemoveEvent { /// Requires [`GatewayIntents::GUILD_MEMBERS`]. /// /// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#guild-member-update). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct GuildMemberUpdateEvent { pub guild_id: GuildId, @@ -593,15 +593,15 @@ impl MessageUpdateEvent { if let Some(x) = content { message.content.clone_from(x) } if let Some(x) = timestamp { message.timestamp = x.clone() } message.edited_timestamp = *edited_timestamp; - if let Some(x) = tts { message.tts = x.clone() } - if let Some(x) = mention_everyone { message.mention_everyone = x.clone() } + if let Some(x) = tts { message.set_tts(*x) } + if let Some(x) = mention_everyone { message.set_mention_everyone(*x) } if let Some(x) = mentions { message.mentions.clone_from(x) } if let Some(x) = mention_roles { message.mention_roles.clone_from(x) } if let Some(x) = mention_channels { message.mention_channels.clone_from(x) } if let Some(x) = attachments { message.attachments.clone_from(x) } if let Some(x) = embeds { message.embeds.clone_from(x) } if let Some(x) = reactions { message.reactions.clone_from(x) } - if let Some(x) = pinned { message.pinned = x.clone() } + if let Some(x) = pinned { message.set_pinned(*x) } if let Some(x) = webhook_id { message.webhook_id.clone_from(x) } if let Some(x) = kind { message.kind = x.clone() } if let Some(x) = activity { message.activity.clone_from(x) } diff --git a/src/model/gateway.rs b/src/model/gateway.rs index b2bd4126d2f..5a2fe41a1fb 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -240,8 +240,9 @@ pub struct ClientStatus { /// /// [Discord docs](https://discord.com/developers/docs/resources/user#user-object), /// [modification description](https://discord.com/developers/docs/topics/gateway-events#presence-update). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct PresenceUser { pub id: UserId, @@ -263,9 +264,9 @@ impl PresenceUser { /// If one of [`User`]'s required fields is None in `self`, None is returned. #[must_use] pub fn into_user(self) -> Option { - Some(User { + let (bot, verified, mfa_enabled) = (self.bot()?, self.verified(), self.mfa_enabled()); + let mut user = User { avatar: self.avatar, - bot: self.bot?, discriminator: self.discriminator, global_name: None, id: self.id, @@ -274,14 +275,18 @@ impl PresenceUser { banner: None, accent_colour: None, member: None, - system: false, - mfa_enabled: self.mfa_enabled.unwrap_or_default(), locale: None, - verified: self.verified, email: self.email, flags: self.public_flags.unwrap_or_default(), premium_type: PremiumType::None, - }) + __generated_flags: UserGeneratedFlags::empty(), + }; + + user.set_bot(bot); + user.set_verified(verified); + user.set_mfa_enabled(mfa_enabled.unwrap_or_default()); + + Some(user) } /// Attempts to convert this [`PresenceUser`] instance into a [`User`]. diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs index 6e896694c0f..975e68afd33 100644 --- a/src/model/guild/emoji.rs +++ b/src/model/guild/emoji.rs @@ -9,8 +9,9 @@ use crate::model::utils::default_true; /// integration. Emojis created using the API only work within the guild it was created in. /// /// [Discord docs](https://discord.com/developers/docs/resources/emoji#emoji-object). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct Emoji { /// Whether the emoji is animated. @@ -62,7 +63,7 @@ impl Emoji { #[inline] #[must_use] pub fn url(&self) -> String { - let extension = if self.animated { "gif" } else { "png" }; + let extension = if self.animated() { "gif" } else { "png" }; cdn!("/emojis/{}.{}", self.id, extension) } } @@ -73,7 +74,7 @@ impl fmt::Display for Emoji { /// This is in the format of either `<:NAME:EMOJI_ID>` for normal emojis, or /// `` for animated emojis. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.animated { + if self.animated() { f.write_str(" for Member { fn from(partial: PartialMember) -> Self { - Member { + let (pending, deaf, mute) = (partial.pending(), partial.deaf(), partial.mute()); + let mut member = Member { + __generated_flags: MemberGeneratedFlags::empty(), user: partial.user.unwrap_or_default(), nick: partial.nick, avatar: None, roles: partial.roles, joined_at: partial.joined_at, premium_since: partial.premium_since, - deaf: partial.deaf, - mute: partial.mute, flags: GuildMemberFlags::default(), - pending: partial.pending, permissions: partial.permissions, communication_disabled_until: None, guild_id: partial.guild_id.unwrap_or_default(), unusual_dm_activity_until: partial.unusual_dm_activity_until, - } + }; + + member.set_pending(pending); + member.set_deaf(deaf); + member.set_mute(mute); + member } } impl From for PartialMember { fn from(member: Member) -> Self { - PartialMember { - deaf: member.deaf, + let (pending, deaf, mute) = (member.pending(), member.deaf(), member.mute()); + let mut partial = PartialMember { + __generated_flags: PartialMemberGeneratedFlags::empty(), joined_at: member.joined_at, - mute: member.mute, nick: member.nick, roles: member.roles, - pending: member.pending, premium_since: member.premium_since, guild_id: Some(member.guild_id), user: Some(member.user), permissions: member.permissions, unusual_dm_activity_until: member.unusual_dm_activity_until, - } + }; + + partial.set_deaf(deaf); + partial.set_mute(mute); + partial.set_pending(pending); + partial } } diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 1820ae85df3..fc375418766 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -103,8 +103,9 @@ pub struct AfkMetadata { /// /// [Discord docs](https://discord.com/developers/docs/resources/guild#guild-object) plus /// [extension](https://discord.com/developers/docs/topics/gateway-events#guild-create). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct Guild { /// The unique Id identifying the guild. diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index 8c922e502de..017d7e990a8 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -31,8 +31,9 @@ use crate::model::utils::{emojis, roles, stickers}; /// Partial information about a [`Guild`]. This does not include information like member data. /// /// [Discord docs](https://discord.com/developers/docs/resources/guild#guild-object). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(remote = "Self")] #[non_exhaustive] pub struct PartialGuild { @@ -1550,7 +1551,7 @@ impl PartialGuild { } // Manual impl needed to insert guild_id into Role's -impl<'de> Deserialize<'de> for PartialGuild { +impl<'de> Deserialize<'de> for PartialGuildGeneratedOriginal { fn deserialize>(deserializer: D) -> StdResult { let mut guild = Self::deserialize(deserializer)?; // calls #[serde(remote)]-generated inherent method guild.roles.values_mut().for_each(|r| r.guild_id = guild.id); @@ -1558,7 +1559,7 @@ impl<'de> Deserialize<'de> for PartialGuild { } } -impl Serialize for PartialGuild { +impl Serialize for PartialGuildGeneratedOriginal { fn serialize(&self, serializer: S) -> StdResult { Self::serialize(self, serializer) // calls #[serde(remote)]-generated inherent method } @@ -1567,12 +1568,15 @@ impl Serialize for PartialGuild { impl From for PartialGuild { /// Converts this [`Guild`] instance into a [`PartialGuild`] fn from(guild: Guild) -> Self { - Self { + let (premium_progress_bar_enabled, widget_enabled) = + (guild.premium_progress_bar_enabled(), guild.widget_enabled()); + + let mut partial = Self { + __generated_flags: PartialGuildGeneratedFlags::empty(), application_id: guild.application_id, id: guild.id, afk_metadata: guild.afk_metadata, default_message_notifications: guild.default_message_notifications, - widget_enabled: guild.widget_enabled, widget_channel_id: guild.widget_channel_id, emojis: guild.emojis, features: guild.features, @@ -1605,7 +1609,9 @@ impl From for PartialGuild { explicit_content_filter: guild.explicit_content_filter, preferred_locale: guild.preferred_locale, max_stage_video_channel_users: guild.max_stage_video_channel_users, - premium_progress_bar_enabled: guild.premium_progress_bar_enabled, - } + }; + partial.set_premium_progress_bar_enabled(premium_progress_bar_enabled); + partial.set_widget_enabled(widget_enabled); + partial } } diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs index 39aa7d373e0..cc6a315e55f 100644 --- a/src/model/guild/role.rs +++ b/src/model/guild/role.rs @@ -21,8 +21,9 @@ fn minus1_as_0<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result for RoleId { /// The tags of a [`Role`]. /// /// [Discord docs](https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure). -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[bool_to_bitflags::bool_to_bitflags] +#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] pub struct RoleTags { @@ -255,14 +257,8 @@ mod tests { #[test] fn premium_subscriber_role_serde() { - let value = RoleTags { - bot_id: None, - integration_id: None, - premium_subscriber: true, - subscription_listing_id: None, - available_for_purchase: false, - guild_connections: false, - }; + let mut value = RoleTags::default(); + value.set_premium_subscriber(true); assert_json( &value, @@ -272,14 +268,7 @@ mod tests { #[test] fn non_premium_subscriber_role_serde() { - let value = RoleTags { - bot_id: None, - integration_id: None, - premium_subscriber: false, - subscription_listing_id: None, - available_for_purchase: false, - guild_connections: false, - }; + let value = RoleTags::default(); assert_json( &value, diff --git a/src/model/mention.rs b/src/model/mention.rs index 543f64b72aa..0011352d07c 100644 --- a/src/model/mention.rs +++ b/src/model/mention.rs @@ -188,14 +188,11 @@ mod test { #[test] fn test_mention() { + let role = Role::default(); let channel = Channel::Guild(GuildChannel { id: ChannelId::new(4), ..Default::default() }); - let role = Role { - id: RoleId::new(2), - ..Default::default() - }; let user = User { id: UserId::new(6), ..Default::default() @@ -209,8 +206,8 @@ mod test { #[cfg(feature = "model")] assert_eq!(channel.mention().to_string(), "<#4>"); assert_eq!(member.mention().to_string(), "<@6>"); - assert_eq!(role.mention().to_string(), "<@&2>"); - assert_eq!(role.id.mention().to_string(), "<@&2>"); + assert_eq!(role.mention().to_string(), "<@&1>"); + assert_eq!(role.id.mention().to_string(), "<@&1>"); assert_eq!(user.mention().to_string(), "<@6>"); assert_eq!(user.id.mention().to_string(), "<@6>"); } diff --git a/src/model/user.rs b/src/model/user.rs index fc0d800a57b..acd63be559c 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -221,8 +221,9 @@ impl OnlineStatus { /// /// [Discord docs](https://discord.com/developers/docs/resources/user#user-object), existence of /// additional partial member field documented [here](https://discord.com/developers/docs/topics/gateway-events#message-create). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct User { /// The unique Id of the user. Can be used to calculate the account's creation date. @@ -401,7 +402,7 @@ impl User { /// See [`UserId::create_dm_channel`] for what errors may be returned. #[inline] pub async fn create_dm_channel(&self, cache_http: impl CacheHttp) -> Result { - if self.bot { + if self.bot() { return Err(Error::Model(ModelError::MessagingBot)); } diff --git a/src/model/voice.rs b/src/model/voice.rs index a219702ade3..d258d479346 100644 --- a/src/model/voice.rs +++ b/src/model/voice.rs @@ -11,7 +11,8 @@ use crate::model::Timestamp; /// Information about an available voice region. /// /// [Discord docs](https://discord.com/developers/docs/resources/voice#voice-region-object). -#[derive(Clone, Debug, Deserialize, Serialize)] +#[bool_to_bitflags::bool_to_bitflags] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct VoiceRegion { /// Whether it is a custom voice region, which is used for events. @@ -29,8 +30,9 @@ pub struct VoiceRegion { /// A user's state within a voice channel. /// /// [Discord docs](https://discord.com/developers/docs/resources/voice#voice-state-object). +#[bool_to_bitflags::bool_to_bitflags] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(remote = "Self")] #[non_exhaustive] pub struct VoiceState { @@ -53,7 +55,7 @@ pub struct VoiceState { } // Manual impl needed to insert guild_id into Member -impl<'de> Deserialize<'de> for VoiceState { +impl<'de> Deserialize<'de> for VoiceStateGeneratedOriginal { fn deserialize>(deserializer: D) -> Result { // calls #[serde(remote)]-generated inherent method let mut state = Self::deserialize(deserializer)?; @@ -64,7 +66,7 @@ impl<'de> Deserialize<'de> for VoiceState { } } -impl Serialize for VoiceState { +impl Serialize for VoiceStateGeneratedOriginal { fn serialize(&self, serializer: S) -> Result { // calls #[serde(remote)]-generated inherent method Self::serialize(self, serializer) diff --git a/src/utils/content_safe.rs b/src/utils/content_safe.rs index 2eaefb26b09..3b0be8218e4 100644 --- a/src/utils/content_safe.rs +++ b/src/utils/content_safe.rs @@ -5,77 +5,42 @@ use crate::model::mention::Mention; use crate::model::user::User; /// Struct that allows to alter [`content_safe`]'s behaviour. -#[derive(Clone, Debug)] +#[bool_to_bitflags::bool_to_bitflags( + getter_prefix = "get_", + setter_prefix = "", + private_getters, + document_setters, + owning_setters +)] +#[derive(Copy, Clone, Debug)] pub struct ContentSafeOptions { - clean_role: bool, - clean_user: bool, - clean_channel: bool, - clean_here: bool, - clean_everyone: bool, - show_discriminator: bool, -} - -impl ContentSafeOptions { - #[must_use] - pub fn new() -> Self { - ContentSafeOptions::default() - } - /// [`content_safe`] will replace role mentions (`<@&{id}>`) with its name prefixed with `@` /// (`@rolename`) or with `@deleted-role` if the identifier is invalid. - #[must_use] - pub fn clean_role(mut self, b: bool) -> Self { - self.clean_role = b; - - self - } - + pub clean_role: bool, /// If set to true, [`content_safe`] will replace user mentions (`<@!{id}>` or `<@{id}>`) with /// the user's name prefixed with `@` (`@username`) or with `@invalid-user` if the identifier /// is invalid. - #[must_use] - pub fn clean_user(mut self, b: bool) -> Self { - self.clean_user = b; - - self - } - + pub clean_user: bool, /// If set to true, [`content_safe`] will replace channel mentions (`<#{id}>`) with the /// channel's name prefixed with `#` (`#channelname`) or with `#deleted-channel` if the /// identifier is invalid. - #[must_use] - pub fn clean_channel(mut self, b: bool) -> Self { - self.clean_channel = b; - - self - } - + pub clean_channel: bool, + /// If set, [`content_safe`] will replace `@here` with a non-pinging alternative. + pub clean_here: bool, + /// If set, [`content_safe`] will replace `@everyone` with a non-pinging alternative. + pub clean_everyone: bool, /// If set to true, if [`content_safe`] replaces a user mention it will add their four digit /// discriminator with a preceding `#`, turning `@username` to `@username#discriminator`. /// /// This option is ignored if the username is a next-gen username, and /// therefore does not have a discriminator. - #[must_use] - pub fn show_discriminator(mut self, b: bool) -> Self { - self.show_discriminator = b; - - self - } - - /// If set, [`content_safe`] will replace `@here` with a non-pinging alternative. - #[must_use] - pub fn clean_here(mut self, b: bool) -> Self { - self.clean_here = b; - - self - } + pub show_discriminator: bool, +} - /// If set, [`content_safe`] will replace `@everyone` with a non-pinging alternative. +impl ContentSafeOptions { #[must_use] - pub fn clean_everyone(mut self, b: bool) -> Self { - self.clean_everyone = b; - - self + pub fn new() -> Self { + ContentSafeOptions::default() } } @@ -83,12 +48,7 @@ impl Default for ContentSafeOptions { /// Instantiates with all options set to `true`. fn default() -> Self { ContentSafeOptions { - clean_role: true, - clean_user: true, - clean_channel: true, - clean_here: true, - clean_everyone: true, - show_discriminator: true, + __generated_flags: ContentSafeOptionsGeneratedFlags::all(), } } } @@ -109,7 +69,7 @@ impl Default for ContentSafeOptions { /// use serenity::utils::{content_safe, ContentSafeOptions}; /// /// let with_mention = "@everyone"; -/// let without_mention = content_safe(&guild, &with_mention, &ContentSafeOptions::default(), &[]); +/// let without_mention = content_safe(&guild, &with_mention, ContentSafeOptions::default(), &[]); /// /// assert_eq!("@\u{200B}everyone", without_mention); /// ``` @@ -123,12 +83,7 @@ impl Default for ContentSafeOptions { /// /// fn filter_message(cache: &Cache, message: &Message) -> String { /// if let Some(guild) = message.guild(cache) { -/// content_safe( -/// &guild, -/// &message.content, -/// &ContentSafeOptions::default(), -/// &message.mentions, -/// ) +/// content_safe(&guild, &message.content, ContentSafeOptions::default(), &message.mentions) /// } else { /// // We don't need to clean messages in DMs /// message.content.to_string() @@ -138,16 +93,16 @@ impl Default for ContentSafeOptions { pub fn content_safe( guild: &Guild, s: impl AsRef, - options: &ContentSafeOptions, + options: ContentSafeOptions, users: &[User], ) -> String { let mut content = clean_mentions(guild, s, options, users); - if options.clean_here { + if options.get_clean_here() { content = content.replace("@here", "@\u{200B}here"); } - if options.clean_everyone { + if options.get_clean_everyone() { content = content.replace("@everyone", "@\u{200B}everyone"); } @@ -157,7 +112,7 @@ pub fn content_safe( fn clean_mentions( guild: &Guild, s: impl AsRef, - options: &ContentSafeOptions, + options: ContentSafeOptions, users: &[User], ) -> String { let s = s.as_ref(); @@ -177,12 +132,12 @@ fn clean_mentions( let mut chars = mention_str.chars(); chars.next(); let should_parse = match chars.next() { - Some('#') => options.clean_channel, + Some('#') => options.get_clean_channel(), Some('@') => { if let Some('&') = chars.next() { - options.clean_role + options.get_clean_role() } else { - options.clean_user + options.get_clean_user() } }, _ => false, @@ -213,7 +168,7 @@ fn clean_mentions( fn clean_mention( guild: &Guild, mention: Mention, - options: &ContentSafeOptions, + options: ContentSafeOptions, users: &[User], ) -> Cow<'static, str> { match mention { @@ -230,13 +185,13 @@ fn clean_mention( .map_or(Cow::Borrowed("@deleted-role"), |role| format!("@{}", role.name).into()), Mention::User(id) => { if let Some(member) = guild.members.get(&id) { - if options.show_discriminator { + if options.get_show_discriminator() { format!("@{}", member.distinct()).into() } else { format!("@{}", member.display_name()).into() } } else if let Some(user) = users.iter().find(|u| u.id == id) { - if options.show_discriminator { + if options.get_show_discriminator() { format!("@{}", user.tag()).into() } else { format!("@{}", user.name).into() @@ -307,31 +262,32 @@ mod tests { // User mentions let options = ContentSafeOptions::default(); - assert_eq!(without_user_mentions, content_safe(&guild, with_user_mentions, &options, &[])); + assert_eq!(without_user_mentions, content_safe(&guild, with_user_mentions, options, &[])); assert_eq!( "@invalid-user", - content_safe(&no_member_guild, "<@100000000000000001>", &options, &[]) + content_safe(&no_member_guild, "<@100000000000000001>", options, &[]) ); - let options = ContentSafeOptions::default().show_discriminator(false); + let mut options = ContentSafeOptions::default(); + options = options.show_discriminator(false); assert_eq!( format!("@{}", user.name), - content_safe(&no_member_guild, "<@!100000000000000000>", &options, &[user.clone()]) + content_safe(&no_member_guild, "<@!100000000000000000>", options, &[user.clone()]) ); assert_eq!( "@invalid-user", - content_safe(&no_member_guild, "<@!100000000000000000>", &options, &[]) + content_safe(&no_member_guild, "<@!100000000000000000>", options, &[]) ); assert_eq!( format!("@{}", member.nick.as_ref().unwrap()), - content_safe(&guild, "<@100000000000000000>", &options, &[]) + content_safe(&guild, "<@100000000000000000>", options, &[]) ); - let options = options.clean_user(false); - assert_eq!(with_user_mentions, content_safe(&guild, with_user_mentions, &options, &[])); + options = options.clean_user(false); + assert_eq!(with_user_mentions, content_safe(&guild, with_user_mentions, options, &[])); // Channel mentions let with_channel_mentions = "<#> <#deleted-channel> #deleted-channel <#1> \ @@ -344,13 +300,13 @@ mod tests { assert_eq!( without_channel_mentions, - content_safe(&guild, with_channel_mentions, &options, &[]) + content_safe(&guild, with_channel_mentions, options, &[]) ); - let options = options.clean_channel(false); + options = options.clean_channel(false); assert_eq!( with_channel_mentions, - content_safe(&guild, with_channel_mentions, &options, &[]) + content_safe(&guild, with_channel_mentions, options, &[]) ); // Role mentions @@ -362,25 +318,24 @@ mod tests { @ferris-club-member @deleted-role \ <@&111111111111111111111111111111> <@&@deleted-role"; - assert_eq!(without_role_mentions, content_safe(&guild, with_role_mentions, &options, &[])); + assert_eq!(without_role_mentions, content_safe(&guild, with_role_mentions, options, &[])); - let options = options.clean_role(false); - assert_eq!(with_role_mentions, content_safe(&guild, with_role_mentions, &options, &[])); + options = options.clean_role(false); + assert_eq!(with_role_mentions, content_safe(&guild, with_role_mentions, options, &[])); // Everyone mentions let with_everyone_mention = "@everyone"; - let without_everyone_mention = "@\u{200B}everyone"; assert_eq!( without_everyone_mention, - content_safe(&guild, with_everyone_mention, &options, &[]) + content_safe(&guild, with_everyone_mention, options, &[]) ); - let options = options.clean_everyone(false); + options = options.clean_everyone(false); assert_eq!( with_everyone_mention, - content_safe(&guild, with_everyone_mention, &options, &[]) + content_safe(&guild, with_everyone_mention, options, &[]) ); // Here mentions @@ -388,9 +343,9 @@ mod tests { let without_here_mention = "@\u{200B}here"; - assert_eq!(without_here_mention, content_safe(&guild, with_here_mention, &options, &[])); + assert_eq!(without_here_mention, content_safe(&guild, with_here_mention, options, &[])); - let options = options.clean_here(false); - assert_eq!(with_here_mention, content_safe(&guild, with_here_mention, &options, &[])); + options = options.clean_here(false); + assert_eq!(with_here_mention, content_safe(&guild, with_here_mention, options, &[])); } } diff --git a/src/utils/custom_message.rs b/src/utils/custom_message.rs index e447c262d1d..ac479c69b48 100644 --- a/src/utils/custom_message.rs +++ b/src/utils/custom_message.rs @@ -128,7 +128,7 @@ impl CustomMessage { /// If not used, the default value is `false`. #[inline] pub fn mention_everyone(&mut self, mentions: bool) -> &mut Self { - self.msg.mention_everyone = mentions; + self.msg.set_mention_everyone(mentions); self } @@ -158,7 +158,7 @@ impl CustomMessage { /// If not used, the default value is `false`. #[inline] pub fn pinned(&mut self, pinned: bool) -> &mut Self { - self.msg.pinned = pinned; + self.msg.set_pinned(pinned); self } @@ -188,7 +188,7 @@ impl CustomMessage { /// If not used, the default value is `false`. #[inline] pub fn tts(&mut self, tts: bool) -> &mut Self { - self.msg.tts = tts; + self.msg.set_tts(tts); self } diff --git a/src/utils/message_builder.rs b/src/utils/message_builder.rs index 858303680b9..ce879f76ee7 100644 --- a/src/utils/message_builder.rs +++ b/src/utils/message_builder.rs @@ -983,6 +983,7 @@ pub enum ContentModifier { Spoiler, } +#[bool_to_bitflags::bool_to_bitflags] /// Describes formatting on string content #[derive(Clone, Debug, Default)] pub struct Content { @@ -1049,22 +1050,22 @@ impl Content { pub fn apply(&mut self, modifier: &ContentModifier) { match *modifier { ContentModifier::Italic => { - self.italic = true; + self.set_italic(true); }, ContentModifier::Bold => { - self.bold = true; + self.set_bold(true); }, ContentModifier::Strikethrough => { - self.strikethrough = true; + self.set_strikethrough(true); }, ContentModifier::Code => { - self.code = true; + self.set_code(true); }, ContentModifier::Underline => { - self.underline = true; + self.set_underline(true); }, ContentModifier::Spoiler => { - self.spoiler = true; + self.set_spoiler(true); }, } } @@ -1072,53 +1073,53 @@ impl Content { impl std::fmt::Display for Content { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.spoiler { + if self.spoiler() { fmt.write_str("||")?; } - if self.bold { + if self.bold() { fmt.write_str("**")?; } - if self.italic { + if self.italic() { fmt.write_char('*')?; } - if self.strikethrough { + if self.strikethrough() { fmt.write_str("~~")?; } - if self.underline { + if self.underline() { fmt.write_str("__")?; } - if self.code { + if self.code() { fmt.write_char('`')?; } fmt.write_str(&self.inner)?; - if self.code { + if self.code() { fmt.write_char('`')?; } - if self.underline { + if self.underline() { fmt.write_str("__")?; } - if self.strikethrough { + if self.strikethrough() { fmt.write_str("~~")?; } - if self.italic { + if self.italic() { fmt.write_char('*')?; } - if self.bold { + if self.bold() { fmt.write_str("**")?; } - if self.spoiler { + if self.spoiler() { fmt.write_str("||")?; } @@ -1195,18 +1196,18 @@ mod test { #[test] fn mentions() { - let content_emoji = MessageBuilder::new() - .emoji(&Emoji { - animated: false, - available: true, - id: EmojiId::new(32), - name: "Rohrkatze".to_string().into(), - managed: false, - require_colons: true, - roles: vec![].into(), - user: None, - }) - .build(); + let mut emoji = Emoji { + id: EmojiId::new(32), + name: "Rohrkatze".to_string().into(), + roles: vec![].into(), + user: None, + __generated_flags: EmojiGeneratedFlags::empty(), + }; + + emoji.set_available(true); + emoji.set_require_colons(true); + + let content_emoji = MessageBuilder::new().emoji(&emoji).build(); let content_mentions = MessageBuilder::new() .channel(ChannelId::new(1)) .mention(&UserId::new(2)) From a9526a1f1adde1c7b4f575f5ef96a232b1527db0 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Tue, 2 Jan 2024 20:06:52 +0000 Subject: [PATCH 015/159] Use `nonmax::NonMax*` types in `Option`s wherever possible (#2681) This swaps fields that store `Option` for `Option` where the maximum value would be ludicrous. Since `nonmax` uses `NonZero` internally, this gives us niche optimisations, so model sizes can drop some more. I have had to include a workaround for [#17] in `optional_string` by making my own `TryFrom`, so that should be removable once that issue is fixed. [#17]: https://github.com/LPGhatguy/nonmax/issues/17 --- Cargo.toml | 3 +- examples/e05_command_framework/src/main.rs | 7 ++- src/model/channel/attachment.rs | 9 +-- src/model/channel/embed.rs | 14 +++-- src/model/channel/guild_channel.rs | 18 +++--- src/model/channel/message.rs | 4 +- src/model/event.rs | 3 +- src/model/gateway.rs | 6 +- src/model/guild/audit_log/mod.rs | 7 ++- src/model/guild/audit_log/utils.rs | 66 ++++++++++++++++++---- src/model/guild/integration.rs | 8 ++- src/model/guild/mod.rs | 15 ++--- src/model/guild/partial_guild.rs | 15 ++--- src/model/guild/scheduled_event.rs | 4 +- src/model/invite.rs | 8 ++- 15 files changed, 125 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 318bbbdb3b8..4ffbbe1b2a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ arrayvec = { version = "0.7.4", features = ["serde"] } serde_cow = { version = "0.1.0" } small-fixed-array = { git = "https://github.com/GnomedDev/small-fixed-array", features = ["serde", "log_using_tracing"] } bool_to_bitflags = { git = "https://github.com/GnomedDev/bool-to-bitflags", version = "0.1.0" } +nonmax = { version = "0.5.5", features = ["serde"] } # Optional dependencies fxhash = { version = "0.2.1", optional = true } simd-json = { version = "0.13.4", optional = true } @@ -51,7 +52,7 @@ mime_guess = { version = "2.0.4", optional = true } dashmap = { version = "5.5.3", features = ["serde"], optional = true } parking_lot = { version = "0.12.1", optional = true } ed25519-dalek = { version = "2.0.0", optional = true } -typesize = { version = "0.1.4", optional = true, features = ["url", "time", "serde_json", "secrecy", "dashmap", "parking_lot", "details"] } +typesize = { version = "0.1.5", optional = true, features = ["url", "time", "serde_json", "secrecy", "dashmap", "parking_lot", "nonmax", "details"] } # serde feature only allows for serialisation, # Serenity workspace crates command_attr = { version = "0.5.3", path = "./command_attr", optional = true } diff --git a/examples/e05_command_framework/src/main.rs b/examples/e05_command_framework/src/main.rs index 129d7c70994..a11d59fb6c4 100644 --- a/examples/e05_command_framework/src/main.rs +++ b/examples/e05_command_framework/src/main.rs @@ -555,8 +555,11 @@ async fn slow_mode(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul format!("Successfully set slow mode rate to `{slow_mode_rate_seconds}` seconds.") } } else if let Some(channel) = msg.channel_id.to_channel_cached(&ctx.cache) { - let slow_mode_rate = channel.rate_limit_per_user.unwrap_or(0); - format!("Current slow mode rate is `{slow_mode_rate}` seconds.") + if let Some(slow_mode_rate) = channel.rate_limit_per_user { + format!("Current slow mode rate is `{slow_mode_rate}` seconds.") + } else { + "There is no current slow mode rate for this channel.".to_string() + } } else { "Failed to find channel in cache.".to_string() }; diff --git a/src/model/channel/attachment.rs b/src/model/channel/attachment.rs index 535ac8b8251..575335f64e0 100644 --- a/src/model/channel/attachment.rs +++ b/src/model/channel/attachment.rs @@ -1,3 +1,4 @@ +use nonmax::NonMaxU32; #[cfg(feature = "model")] use reqwest::Client as ReqwestClient; use serde_cow::CowStr; @@ -40,15 +41,15 @@ pub struct Attachment { /// Description for the file (max 1024 characters). pub description: Option>, /// If the attachment is an image, then the height of the image is provided. - pub height: Option, + pub height: Option, + /// If the attachment is an image, then the width of the image is provided. + pub width: Option, /// The proxy URL. pub proxy_url: FixedString, /// The size of the file in bytes. pub size: u32, /// The URL of the uploaded attachment. pub url: FixedString, - /// If the attachment is an image, then the width of the image is provided. - pub width: Option, /// The attachment's [media type]. /// /// [media type]: https://en.wikipedia.org/wiki/Media_type @@ -80,7 +81,7 @@ pub struct Attachment { impl Attachment { /// If this attachment is an image, then a tuple of the width and height in pixels is returned. #[must_use] - pub fn dimensions(&self) -> Option<(u32, u32)> { + pub fn dimensions(&self) -> Option<(NonMaxU32, NonMaxU32)> { self.width.and_then(|width| self.height.map(|height| (width, height))) } diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs index 4d6f2568eba..abfc660e246 100644 --- a/src/model/channel/embed.rs +++ b/src/model/channel/embed.rs @@ -1,3 +1,5 @@ +use nonmax::NonMaxU32; + use crate::internal::prelude::*; use crate::model::{Colour, Timestamp}; @@ -171,9 +173,9 @@ pub struct EmbedImage { /// A proxied URL of the image. pub proxy_url: Option, /// The height of the image. - pub height: Option, + pub height: Option, /// The width of the image. - pub width: Option, + pub width: Option, } /// The provider of an embed. @@ -203,9 +205,9 @@ pub struct EmbedThumbnail { /// A proxied URL of the thumbnail. pub proxy_url: Option, /// The height of the thumbnail in pixels. - pub height: Option, + pub height: Option, /// The width of the thumbnail in pixels. - pub width: Option, + pub width: Option, } /// Video information for an embed. @@ -220,7 +222,7 @@ pub struct EmbedVideo { /// A proxied URL of the thumbnail. pub proxy_url: Option, /// The height of the video in pixels. - pub height: Option, + pub height: Option, /// The width of the video in pixels. - pub width: Option, + pub width: Option, } diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index 4cf0395902b..3ca0f172ffe 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -2,6 +2,8 @@ use std::fmt; #[cfg(feature = "model")] use std::sync::Arc; +use nonmax::{NonMaxU16, NonMaxU32, NonMaxU8}; + #[cfg(feature = "model")] use crate::builder::{ Builder, @@ -46,7 +48,7 @@ pub struct GuildChannel { /// The bitrate of the channel. /// /// **Note**: This is only available for voice and stage channels. - pub bitrate: Option, + pub bitrate: Option, /// The Id of the parent category for a channel, or of the parent text channel for a thread. /// /// **Note**: This is only available for channels in a category and thread channels. @@ -90,7 +92,7 @@ pub struct GuildChannel { /// The maximum number of members allowed in the channel. /// /// **Note**: This is only available for voice channels. - pub user_limit: Option, + pub user_limit: Option, /// Used to tell if the channel is not safe for work. // This field can or can not be present sometimes, but if it isn't default to `false`. #[serde(default)] @@ -100,7 +102,7 @@ pub struct GuildChannel { /// **Note**: This is only available for text channels excluding news channels. #[doc(alias = "slowmode")] #[serde(default)] - pub rate_limit_per_user: Option, + pub rate_limit_per_user: Option, /// The region override. /// /// **Note**: This is only available for voice and stage channels. [`None`] for voice and stage @@ -110,14 +112,12 @@ pub struct GuildChannel { pub video_quality_mode: Option, /// An approximate count of messages in the thread. /// - /// This is currently saturated at 255 to prevent breaking. - /// /// **Note**: This is only available on thread channels. - pub message_count: Option, + pub message_count: Option, /// An approximate count of users in a thread, stops counting at 50. /// /// **Note**: This is only available on thread channels. - pub member_count: Option, + pub member_count: Option, /// The thread metadata. /// /// **Note**: This is only available on thread channels. @@ -139,7 +139,7 @@ pub struct GuildChannel { pub flags: ChannelFlags, /// The number of messages ever sent in a thread, it's similar to `message_count` on message /// creation, but will not decrement the number when a message is deleted. - pub total_message_sent: Option, + pub total_message_sent: Option, /// The set of available tags. /// /// **Note**: This is only available in forum channels. @@ -158,7 +158,7 @@ pub struct GuildChannel { /// is copied to the thread at creation time and does not live update. /// /// **Note**: This is only available in a forum or text channel. - pub default_thread_rate_limit_per_user: Option, + pub default_thread_rate_limit_per_user: Option, /// The status of a voice channel. /// /// **Note**: This is only available in voice channels. diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 1e6bf99064b..98bf6fad1ec 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -5,6 +5,8 @@ use std::fmt::Display; #[cfg(all(feature = "cache", feature = "model"))] use std::fmt::Write; +use nonmax::NonMaxU64; + #[cfg(all(feature = "model", feature = "utils"))] use crate::builder::{Builder, CreateAllowedMentions, CreateMessage, EditMessage}; #[cfg(all(feature = "cache", feature = "model"))] @@ -125,7 +127,7 @@ pub struct Message { /// A generally increasing integer (there may be gaps or duplicates) that represents the /// approximate position of the message in a thread, it can be used to estimate the relative /// position of the message in a thread in company with total_message_sent on parent thread. - pub position: Option, + pub position: Option, /// Data of the role subscription purchase or renewal that prompted this /// [`MessageType::RoleSubscriptionPurchase`] message. pub role_subscription_data: Option, diff --git a/src/model/event.rs b/src/model/event.rs index 6e6b20e0b69..fc8c02381f7 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -6,6 +6,7 @@ // Just for MessageUpdateEvent (for some reason the #[allow] doesn't work when placed directly) #![allow(clippy::option_option)] +use nonmax::NonMaxU64; use serde::de::Error as DeError; use serde::Serialize; @@ -535,7 +536,7 @@ pub struct MessageUpdateEvent { pub thread: Option>>, pub components: Option>, pub sticker_items: Option>, - pub position: Option>, + pub position: Option>, pub role_subscription_data: Option>, pub guild_id: Option, pub member: Option>>, diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 5a2fe41a1fb..69d9ea662db 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -1,6 +1,6 @@ //! Models pertaining to the gateway. -use std::num::NonZeroU16; +use std::num::{NonZeroU16, NonZeroU64}; use serde::ser::SerializeSeq; use url::Url; @@ -402,8 +402,8 @@ impl serde::Serialize for ShardInfo { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct ActivityTimestamps { - pub end: Option, - pub start: Option, + pub end: Option, + pub start: Option, } bitflags! { diff --git a/src/model/guild/audit_log/mod.rs b/src/model/guild/audit_log/mod.rs index 832fb583d09..fb382b6a8fa 100644 --- a/src/model/guild/audit_log/mod.rs +++ b/src/model/guild/audit_log/mod.rs @@ -2,6 +2,7 @@ use std::mem::transmute; +use nonmax::{NonMaxU32, NonMaxU64}; use serde::ser::{Serialize, Serializer}; mod change; @@ -369,16 +370,16 @@ pub struct Options { pub application_id: Option, /// Number of days after which inactive members were kicked. #[serde(default, with = "optional_string")] - pub delete_member_days: Option, + pub delete_member_days: Option, /// Number of members removed by the prune #[serde(default, with = "optional_string")] - pub members_removed: Option, + pub members_removed: Option, /// Channel in which the messages were deleted #[serde(default)] pub channel_id: Option, /// Number of deleted messages. #[serde(default, with = "optional_string")] - pub count: Option, + pub count: Option, /// Id of the overwritten entity #[serde(default)] pub id: Option, diff --git a/src/model/guild/audit_log/utils.rs b/src/model/guild/audit_log/utils.rs index f789ab78e43..a0a10998c0c 100644 --- a/src/model/guild/audit_log/utils.rs +++ b/src/model/guild/audit_log/utils.rs @@ -41,28 +41,70 @@ pub mod webhooks { /// Used with `#[serde(with = "optional_string")]`. pub mod optional_string { use std::fmt; + use std::marker::PhantomData; + use std::str::FromStr; use serde::de::{Deserializer, Error, Visitor}; use serde::ser::Serializer; - pub fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result, D::Error> { - deserializer.deserialize_option(OptionalStringVisitor) + // Workaround for https://github.com/LPGhatguy/nonmax/issues/17 + pub(crate) trait TryFromU64 + where + Self: Sized, + { + type Err: fmt::Display; + fn try_from_u64(value: u64) -> Result; + } + + impl TryFromU64 for u64 { + type Err = std::convert::Infallible; + fn try_from_u64(value: u64) -> Result { + Ok(value) + } + } + + impl TryFromU64 for nonmax::NonMaxU64 { + type Err = nonmax::TryFromIntError; + fn try_from_u64(value: u64) -> Result { + Self::try_from(value) + } + } + + impl TryFromU64 for nonmax::NonMaxU32 { + type Err = nonmax::TryFromIntError; + fn try_from_u64(value: u64) -> Result { + Self::try_from(u32::try_from(value)?) + } + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: FromStr + TryFromU64, + ::Err: fmt::Display, + { + deserializer.deserialize_option(OptionalStringVisitor::(PhantomData)) } #[allow(clippy::ref_option)] - pub fn serialize(value: &Option, serializer: S) -> Result { + pub fn serialize( + value: &Option, + serializer: S, + ) -> Result { match value { Some(value) => serializer.serialize_some(&value.to_string()), None => serializer.serialize_none(), } } - struct OptionalStringVisitor; + struct OptionalStringVisitor(PhantomData); - impl<'de> Visitor<'de> for OptionalStringVisitor { - type Value = Option; + impl<'de, T> Visitor<'de> for OptionalStringVisitor + where + T: FromStr + TryFromU64, + ::Err: fmt::Display, + { + type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("an optional integer or a string with a valid number inside") @@ -72,7 +114,7 @@ pub mod optional_string { self, deserializer: D, ) -> Result { - deserializer.deserialize_any(OptionalStringVisitor) + deserializer.deserialize_any(OptionalStringVisitor(PhantomData)) } fn visit_none(self) -> Result { @@ -84,11 +126,11 @@ pub mod optional_string { Ok(None) } - fn visit_u64(self, val: u64) -> Result, E> { - Ok(Some(val)) + fn visit_u64(self, val: u64) -> Result { + T::try_from_u64(val).map(Some).map_err(Error::custom) } - fn visit_str(self, string: &str) -> Result, E> { + fn visit_str(self, string: &str) -> Result { string.parse().map(Some).map_err(Error::custom) } } diff --git a/src/model/guild/integration.rs b/src/model/guild/integration.rs index bbe96bc3e5c..039680119fb 100644 --- a/src/model/guild/integration.rs +++ b/src/model/guild/integration.rs @@ -1,5 +1,9 @@ use crate::model::prelude::*; +use nonmax::{NonMaxU32, NonMaxU64}; + +use super::*; + /// Various information about integrations. /// /// [Discord docs](https://discord.com/developers/docs/resources/guild#integration-object), @@ -20,11 +24,11 @@ pub struct Integration { pub enable_emoticons: Option, #[serde(rename = "expire_behavior")] pub expire_behaviour: Option, - pub expire_grace_period: Option, + pub expire_grace_period: Option, pub user: Option, pub account: IntegrationAccount, pub synced_at: Option, - pub subscriber_count: Option, + pub subscriber_count: Option, pub revoked: Option, pub application: Option, pub scopes: Option>, diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index fc375418766..55bf938c5b8 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -17,6 +17,7 @@ mod welcome_screen; #[cfg(feature = "model")] use std::borrow::Cow; +use nonmax::NonMaxU64; #[cfg(feature = "model")] use tracing::{error, warn}; @@ -202,9 +203,9 @@ pub struct Guild { /// The maximum number of presences for the guild. The default value is currently 25000. /// /// **Note**: It is in effect when it is `None`. - pub max_presences: Option, + pub max_presences: Option, /// The maximum number of members for the guild. - pub max_members: Option, + pub max_members: Option, /// The vanity url code for the guild, if it has one. pub vanity_url_code: Option, /// The server's description, if it has one. @@ -214,7 +215,7 @@ pub struct Guild { /// The server's premium boosting level. pub premium_tier: PremiumTier, /// The total number of users currently boosting this server. - pub premium_subscription_count: Option, + pub premium_subscription_count: Option, /// The preferred locale of this guild only set if guild has the "DISCOVERABLE" feature, /// defaults to en-US. pub preferred_locale: FixedString, @@ -224,13 +225,13 @@ pub struct Guild { /// **Note**: Only available on `COMMUNITY` guild, see [`Self::features`]. pub public_updates_channel_id: Option, /// The maximum amount of users in a video channel. - pub max_video_channel_users: Option, + pub max_video_channel_users: Option, /// The maximum amount of users in a stage video channel - pub max_stage_video_channel_users: Option, + pub max_stage_video_channel_users: Option, /// Approximate number of members in this guild. - pub approximate_member_count: Option, + pub approximate_member_count: Option, /// Approximate number of non-offline members in this guild. - pub approximate_presence_count: Option, + pub approximate_presence_count: Option, /// The welcome screen of the guild. /// /// **Note**: Only available on `COMMUNITY` guild, see [`Self::features`]. diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index 017d7e990a8..1ddd4c79928 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -1,3 +1,4 @@ +use nonmax::NonMaxU64; use serde::Serialize; #[cfg(feature = "model")] @@ -134,9 +135,9 @@ pub struct PartialGuild { /// The maximum number of presences for the guild. The default value is currently 25000. /// /// **Note**: It is in effect when it is `None`. - pub max_presences: Option, + pub max_presences: Option, /// The maximum number of members for the guild. - pub max_members: Option, + pub max_members: Option, /// The vanity url code for the guild, if it has one. pub vanity_url_code: Option, /// The server's description, if it has one. @@ -146,7 +147,7 @@ pub struct PartialGuild { /// The server's premium boosting level. pub premium_tier: PremiumTier, /// The total number of users currently boosting this server. - pub premium_subscription_count: Option, + pub premium_subscription_count: Option, /// The preferred locale of this guild only set if guild has the "DISCOVERABLE" feature, /// defaults to en-US. pub preferred_locale: FixedString, @@ -156,13 +157,13 @@ pub struct PartialGuild { /// **Note**: Only available on `COMMUNITY` guild, see [`Self::features`]. pub public_updates_channel_id: Option, /// The maximum amount of users in a video channel. - pub max_video_channel_users: Option, + pub max_video_channel_users: Option, /// The maximum amount of users in a stage video channel - pub max_stage_video_channel_users: Option, + pub max_stage_video_channel_users: Option, /// Approximate number of members in this guild. - pub approximate_member_count: Option, + pub approximate_member_count: Option, /// Approximate number of non-offline members in this guild. - pub approximate_presence_count: Option, + pub approximate_presence_count: Option, /// The welcome screen of the guild. /// /// **Note**: Only available on `COMMUNITY` guild, see [`Self::features`]. diff --git a/src/model/guild/scheduled_event.rs b/src/model/guild/scheduled_event.rs index ac5994ac20e..99b8f3a7289 100644 --- a/src/model/guild/scheduled_event.rs +++ b/src/model/guild/scheduled_event.rs @@ -1,3 +1,5 @@ +use nonmax::NonMaxU64; + use crate::internal::prelude::*; use crate::model::prelude::*; @@ -49,7 +51,7 @@ pub struct ScheduledEvent { /// /// Only populated if `with_user_count` is set to true provided when calling /// [`GuildId::scheduled_event`] or [`GuildId::scheduled_events`]. - pub user_count: Option, + pub user_count: Option, /// The hash of the event's cover image, if present. pub image: Option, } diff --git a/src/model/invite.rs b/src/model/invite.rs index 75fe9294f62..0049472dc2d 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -1,5 +1,7 @@ //! Models for server and channel invites. +use nonmax::NonMaxU64; + use super::prelude::*; #[cfg(feature = "model")] use crate::builder::CreateInvite; @@ -18,12 +20,12 @@ use crate::internal::prelude::*; #[non_exhaustive] pub struct Invite { /// The approximate number of [`Member`]s in the related [`Guild`]. - pub approximate_member_count: Option, + pub approximate_member_count: Option, /// The approximate number of [`Member`]s with an active session in the related [`Guild`]. /// /// An active session is defined as an open, heartbeating WebSocket connection. /// These include [invisible][`OnlineStatus::Invisible`] members. - pub approximate_presence_count: Option, + pub approximate_presence_count: Option, /// The unique code for the invite. pub code: FixedString, /// A representation of the minimal amount of information needed about the [`GuildChannel`] @@ -219,7 +221,7 @@ pub struct InviteGuild { pub verification_level: VerificationLevel, pub vanity_url_code: Option, pub nsfw_level: NsfwLevel, - pub premium_subscription_count: Option, + pub premium_subscription_count: Option, } #[cfg(feature = "model")] From 9111cf72ecfbc406441c00b7a86a2108008395b9 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Tue, 2 Jan 2024 20:08:21 +0000 Subject: [PATCH 016/159] Remove unused warning `#[allow(...)]`s (#2682) A couple of clippy bugs have been fixed and I have shrunk model sizes enough to make `clippy::large_enum_variant` go away. --- src/client/event_handler.rs | 1 - src/framework/standard/args.rs | 1 - src/framework/standard/parse/mod.rs | 1 - src/model/channel/mod.rs | 1 - src/model/event.rs | 1 - src/model/utils.rs | 1 - 6 files changed, 6 deletions(-) diff --git a/src/client/event_handler.rs b/src/client/event_handler.rs index b813bba44f3..172e7a7711c 100644 --- a/src/client/event_handler.rs +++ b/src/client/event_handler.rs @@ -31,7 +31,6 @@ macro_rules! event_handler { /// This enum stores every possible event that an [`EventHandler`] can receive. #[non_exhaustive] - #[allow(clippy::large_enum_variant)] // TODO: do some boxing to fix this #[derive(Clone, Debug)] pub enum FullEvent { $( diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 71718521718..9fda540354d 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -81,7 +81,6 @@ impl<'a> From<&'a str> for Delimiter { } #[derive(Clone, Copy, Debug, PartialEq)] -#[allow(clippy::enum_variant_names)] enum TokenKind { Argument, QuotedArgument, diff --git a/src/framework/standard/parse/mod.rs b/src/framework/standard/parse/mod.rs index 727bf9e5b1d..63e299da1c1 100644 --- a/src/framework/standard/parse/mod.rs +++ b/src/framework/standard/parse/mod.rs @@ -168,7 +168,6 @@ async fn find_prefix<'a>( /// - Nothing /// /// In all cases, whitespace after the prefix is cleared. -#[allow(clippy::needless_lifetimes)] // Clippy and the compiler disagree pub async fn prefix<'a>( ctx: &Context, msg: &Message, diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 11e2908696b..e01b47127c2 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -34,7 +34,6 @@ use crate::model::utils::is_false; #[derive(Clone, Debug, Serialize)] #[serde(untagged)] #[non_exhaustive] -#[allow(clippy::large_enum_variant)] // https://github.com/rust-lang/rust-clippy/issues/9798 pub enum Channel { /// A channel within a [`Guild`]. Guild(GuildChannel), diff --git a/src/model/event.rs b/src/model/event.rs index fc8c02381f7..cf3692f4a1d 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -1144,7 +1144,6 @@ impl<'de> Deserialize<'de> for GatewayEvent { /// Event received over a websocket connection /// /// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#receive-events). -#[allow(clippy::large_enum_variant)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] diff --git a/src/model/utils.rs b/src/model/utils.rs index 0786f057aa5..58dbd5a9e18 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -298,7 +298,6 @@ pub mod comma_separated_string { Ok(vec) } - #[allow(clippy::ptr_arg)] pub fn serialize( vec: &FixedArray, serializer: S, From 508671d3d3d13fd7f0bfe5f2dc5638c1c887f4a9 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Wed, 3 Jan 2024 18:53:38 +0000 Subject: [PATCH 017/159] Get rid of `unsafe` (#2686) A discord bot library should not be using the tools reserved for low level OS interaction/data structure libraries. --- CONTRIBUTING.md | 7 ++- command_attr/src/util.rs | 2 +- src/http/routing.rs | 25 ++++++----- src/lib.rs | 1 + src/model/guild/audit_log/mod.rs | 74 ++++++++++++++++++++++++-------- src/model/id.rs | 2 +- 6 files changed, 77 insertions(+), 34 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d615bc4cc9..c7ca981d5bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,10 +63,9 @@ your code. ## Unsafe -Code that defines or uses `unsafe` functions must be reasoned with comments. -`unsafe` code can pose a potential for undefined behaviour related bugs and other -kinds of bugs to sprout if misused, weakening security. If you commit code containing -`unsafe`, you should confirm that its usage is necessary and correct. +Unsafe code is forbidden, and safe alternatives must be found. This can be mitigated by using +a third party crate to offload the burden of justifying the unsafe code, or finding a safe +alternative. # Comment / Documentation style diff --git a/command_attr/src/util.rs b/command_attr/src/util.rs index cfaeb81728b..7dc918f8683 100644 --- a/command_attr/src/util.rs +++ b/command_attr/src/util.rs @@ -19,7 +19,7 @@ impl LitExt for Lit { fn to_str(&self) -> String { match self { Self::Str(s) => s.value(), - Self::ByteStr(s) => unsafe { String::from_utf8_unchecked(s.value()) }, + Self::ByteStr(s) => String::from_utf8_lossy(&s.value()).into_owned(), Self::Char(c) => c.value().to_string(), Self::Byte(b) => (b.value() as char).to_string(), _ => panic!("values must be a (byte)string or a char"), diff --git a/src/http/routing.rs b/src/http/routing.rs index 47ce4331a18..20af3e40a29 100644 --- a/src/http/routing.rs +++ b/src/http/routing.rs @@ -6,7 +6,7 @@ use crate::model::id::*; /// Used to group requests together for ratelimiting. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct RatelimitingBucket(Option<(std::mem::Discriminant>, Option)>); +pub struct RatelimitingBucket(Option<(RouteKind, Option)>); impl RatelimitingBucket { #[must_use] @@ -41,7 +41,20 @@ macro_rules! routes { )+ } + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + enum RouteKind { + $($name,)+ + } + impl<$lt> Route<$lt> { + fn kind(&self) -> RouteKind { + match self { + $( + Self::$name {..} => RouteKind::$name, + )+ + } + } + #[must_use] pub fn path(self) -> Cow<'static, str> { match self { @@ -60,20 +73,12 @@ macro_rules! routes { )+ }; - // This avoids adding a lifetime on RatelimitingBucket and causing lifetime infection - // SAFETY: std::mem::discriminant erases lifetimes. - let discriminant = unsafe { - std::mem::transmute::>, Discriminant>>( - std::mem::discriminant(self), - ) - }; - RatelimitingBucket(ratelimiting_kind.map(|r| { let id = match r { RatelimitingKind::PathAndId(id) => Some(id), RatelimitingKind::Path => None, }; - (discriminant, id) + (self.kind(), id) })) } diff --git a/src/lib.rs b/src/lib.rs index 73d4aff67d1..65f17d4c147 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,7 @@ //! [gateway docs]: crate::gateway #![doc(html_root_url = "https://docs.rs/serenity/*")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![forbid(unsafe_code)] #![warn( unused, rust_2018_idioms, diff --git a/src/model/guild/audit_log/mod.rs b/src/model/guild/audit_log/mod.rs index fb382b6a8fa..1ee74d2fa7b 100644 --- a/src/model/guild/audit_log/mod.rs +++ b/src/model/guild/audit_log/mod.rs @@ -1,7 +1,5 @@ //! Audit log types for administrative actions within guilds. -use std::mem::transmute; - use nonmax::{NonMaxU32, NonMaxU64}; use serde::ser::{Serialize, Serializer}; @@ -71,22 +69,62 @@ impl Action { pub fn from_value(value: u8) -> Action { match value { 1 => Action::GuildUpdate, - 10..=12 => Action::Channel(unsafe { transmute(value) }), - 13..=15 => Action::ChannelOverwrite(unsafe { transmute(value) }), - 20..=28 => Action::Member(unsafe { transmute(value) }), - 30..=32 => Action::Role(unsafe { transmute(value) }), - 40..=42 => Action::Invite(unsafe { transmute(value) }), - 50..=52 => Action::Webhook(unsafe { transmute(value) }), - 60..=62 => Action::Emoji(unsafe { transmute(value) }), - 72..=75 => Action::Message(unsafe { transmute(value) }), - 80..=82 => Action::Integration(unsafe { transmute(value) }), - 83..=85 => Action::StageInstance(unsafe { transmute(value) }), - 90..=92 => Action::Sticker(unsafe { transmute(value) }), - 100..=102 => Action::ScheduledEvent(unsafe { transmute(value) }), - 110..=112 => Action::Thread(unsafe { transmute(value) }), - 140..=145 => Action::AutoMod(unsafe { transmute(value) }), - 150..=151 => Action::CreatorMonetization(unsafe { transmute(value) }), - 192..=193 => Action::VoiceChannelStatus(unsafe { transmute(value) }), + 10 => Action::Channel(ChannelAction::Create), + 11 => Action::Channel(ChannelAction::Update), + 12 => Action::Channel(ChannelAction::Delete), + 13 => Action::ChannelOverwrite(ChannelOverwriteAction::Create), + 14 => Action::ChannelOverwrite(ChannelOverwriteAction::Update), + 15 => Action::ChannelOverwrite(ChannelOverwriteAction::Delete), + 20 => Action::Member(MemberAction::Kick), + 21 => Action::Member(MemberAction::Prune), + 22 => Action::Member(MemberAction::BanAdd), + 23 => Action::Member(MemberAction::BanRemove), + 24 => Action::Member(MemberAction::Update), + 25 => Action::Member(MemberAction::RoleUpdate), + 26 => Action::Member(MemberAction::MemberMove), + 27 => Action::Member(MemberAction::MemberDisconnect), + 28 => Action::Member(MemberAction::BotAdd), + 30 => Action::Role(RoleAction::Create), + 31 => Action::Role(RoleAction::Update), + 32 => Action::Role(RoleAction::Delete), + 40 => Action::Invite(InviteAction::Create), + 41 => Action::Invite(InviteAction::Update), + 42 => Action::Invite(InviteAction::Delete), + 50 => Action::Webhook(WebhookAction::Create), + 51 => Action::Webhook(WebhookAction::Update), + 52 => Action::Webhook(WebhookAction::Delete), + 60 => Action::Emoji(EmojiAction::Create), + 61 => Action::Emoji(EmojiAction::Update), + 62 => Action::Emoji(EmojiAction::Delete), + 72 => Action::Message(MessageAction::Delete), + 73 => Action::Message(MessageAction::BulkDelete), + 74 => Action::Message(MessageAction::Pin), + 75 => Action::Message(MessageAction::Unpin), + 80 => Action::Integration(IntegrationAction::Create), + 81 => Action::Integration(IntegrationAction::Update), + 82 => Action::Integration(IntegrationAction::Delete), + 83 => Action::StageInstance(StageInstanceAction::Create), + 84 => Action::StageInstance(StageInstanceAction::Update), + 85 => Action::StageInstance(StageInstanceAction::Delete), + 90 => Action::Sticker(StickerAction::Create), + 91 => Action::Sticker(StickerAction::Update), + 92 => Action::Sticker(StickerAction::Delete), + 100 => Action::ScheduledEvent(ScheduledEventAction::Create), + 101 => Action::ScheduledEvent(ScheduledEventAction::Update), + 102 => Action::ScheduledEvent(ScheduledEventAction::Delete), + 110 => Action::Thread(ThreadAction::Create), + 111 => Action::Thread(ThreadAction::Update), + 112 => Action::Thread(ThreadAction::Delete), + 140 => Action::AutoMod(AutoModAction::RuleCreate), + 141 => Action::AutoMod(AutoModAction::RuleUpdate), + 142 => Action::AutoMod(AutoModAction::RuleDelete), + 143 => Action::AutoMod(AutoModAction::BlockMessage), + 144 => Action::AutoMod(AutoModAction::FlagToChannel), + 145 => Action::AutoMod(AutoModAction::UserCommunicationDisabled), + 150 => Action::CreatorMonetization(CreatorMonetizationAction::RequestCreated), + 151 => Action::CreatorMonetization(CreatorMonetizationAction::TermsAccepted), + 192 => Action::VoiceChannelStatus(VoiceChannelStatusAction::StatusUpdate), + 193 => Action::VoiceChannelStatus(VoiceChannelStatusAction::StatusDelete), _ => Action::Unknown(value), } } diff --git a/src/model/id.rs b/src/model/id.rs index 0032f5d7c86..b4cd562dd85 100644 --- a/src/model/id.rs +++ b/src/model/id.rs @@ -111,7 +111,7 @@ macro_rules! id_u64 { impl From<$name> for NonZeroI64 { fn from(id: $name) -> NonZeroI64 { - unsafe {NonZeroI64::new_unchecked(id.get() as i64)} + NonZeroI64::new(id.get() as i64).unwrap() } } From dae08767dbd6e5a708535b4cab3cd2f19cca5ecd Mon Sep 17 00:00:00 2001 From: Gnome! Date: Wed, 3 Jan 2024 18:54:20 +0000 Subject: [PATCH 018/159] Swap Git dependencies for crates.io published versions (#2685) --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ffbbe1b2a2..d69343947fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,8 @@ base64 = { version = "0.22.0" } secrecy = { version = "0.8.0", features = ["serde"] } arrayvec = { version = "0.7.4", features = ["serde"] } serde_cow = { version = "0.1.0" } -small-fixed-array = { git = "https://github.com/GnomedDev/small-fixed-array", features = ["serde", "log_using_tracing"] } -bool_to_bitflags = { git = "https://github.com/GnomedDev/bool-to-bitflags", version = "0.1.0" } +small-fixed-array = { version = "0.1.1", features = ["serde", "log_using_tracing"] } +bool_to_bitflags = { version = "0.1.0" } nonmax = { version = "0.5.5", features = ["serde"] } # Optional dependencies fxhash = { version = "0.2.1", optional = true } From 9d3a2fff052bf3e6cd4ad93fdcf5565c7341f26b Mon Sep 17 00:00:00 2001 From: Gnome! Date: Fri, 5 Jan 2024 20:51:09 +0000 Subject: [PATCH 019/159] Swap Id from `NonZero` to `NonMax` (#2689) Discord seems to internally default Ids to 0, which is a bug whenever exposed, but this makes ID parsing more resilient. I also took the liberty to remove the `From` implementations, to prevent future headaches, as it was impossible to not break public API as we exposed `NonZero` in `*Id::parse`. --- src/builder/create_command_permission.rs | 2 +- src/http/client.rs | 10 +- src/http/routing.rs | 172 +++++++++++------------ src/model/application/interaction.rs | 8 +- src/model/gateway.rs | 1 - src/model/id.rs | 69 ++++----- src/model/mention.rs | 4 +- src/model/utils.rs | 16 --- src/utils/content_safe.rs | 2 +- 9 files changed, 132 insertions(+), 152 deletions(-) diff --git a/src/builder/create_command_permission.rs b/src/builder/create_command_permission.rs index e5ffbcaf9cc..9177ea4651f 100644 --- a/src/builder/create_command_permission.rs +++ b/src/builder/create_command_permission.rs @@ -101,7 +101,7 @@ impl CreateCommandPermission { /// Creates a permission overwrite for all channels in a guild pub fn all_channels(guild_id: GuildId, allow: bool) -> Self { Self(CommandPermission { - id: std::num::NonZeroU64::new(guild_id.get() - 1).expect("guild ID was 1").into(), + id: CommandPermissionId::new(guild_id.get() - 1), kind: CommandPermissionType::Channel, permission: allow, }) diff --git a/src/http/client.rs b/src/http/client.rs index 4c1b5367d67..44f604f187c 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -1,7 +1,6 @@ #![allow(clippy::missing_errors_doc)] use std::borrow::Cow; -use std::num::NonZeroU64; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; @@ -143,7 +142,8 @@ impl HttpBuilder { /// Use the given configuration to build the `Http` client. #[must_use] pub fn build(self) -> Http { - let application_id = AtomicU64::new(self.application_id.map_or(0, ApplicationId::get)); + let application_id = + AtomicU64::new(self.application_id.map_or(u64::MAX, ApplicationId::get)); let client = self.client.unwrap_or_else(|| { let builder = configure_client_backend(Client::builder()); @@ -211,7 +211,11 @@ impl Http { pub fn application_id(&self) -> Option { let application_id = self.application_id.load(Ordering::Relaxed); - NonZeroU64::new(application_id).map(ApplicationId::from) + if application_id == u64::MAX { + None + } else { + Some(ApplicationId::new(application_id)) + } } fn try_application_id(&self) -> Result { diff --git a/src/http/routing.rs b/src/http/routing.rs index 20af3e40a29..d5080e4b9ef 100644 --- a/src/http/routing.rs +++ b/src/http/routing.rs @@ -1,12 +1,10 @@ use std::borrow::Cow; -use std::mem::Discriminant; -use std::num::NonZeroU64; use crate::model::id::*; /// Used to group requests together for ratelimiting. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct RatelimitingBucket(Option<(RouteKind, Option)>); +pub struct RatelimitingBucket(Option<(RouteKind, Option)>); impl RatelimitingBucket { #[must_use] @@ -18,7 +16,7 @@ impl RatelimitingBucket { enum RatelimitingKind { /// Requests with the same path and major parameter (usually an Id) should be grouped together /// for ratelimiting. - PathAndId(NonZeroU64), + PathAndId(GenericId), /// Requests with the same path should be ratelimited together. Path, } @@ -93,115 +91,115 @@ macro_rules! routes { routes! ('a, { Channel { channel_id: ChannelId }, api!("/channels/{}", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelInvites { channel_id: ChannelId }, api!("/channels/{}/invites", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessage { channel_id: ChannelId, message_id: MessageId }, api!("/channels/{}/messages/{}", channel_id, message_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessageCrosspost { channel_id: ChannelId, message_id: MessageId }, api!("/channels/{}/messages/{}/crosspost", channel_id, message_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessageReaction { channel_id: ChannelId, message_id: MessageId, user_id: UserId, reaction: &'a str }, api!("/channels/{}/messages/{}/reactions/{}/{}", channel_id, message_id, reaction, user_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessageReactionMe { channel_id: ChannelId, message_id: MessageId, reaction: &'a str }, api!("/channels/{}/messages/{}/reactions/{}/@me", channel_id, message_id, reaction), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessageReactionEmoji { channel_id: ChannelId, message_id: MessageId, reaction: &'a str }, api!("/channels/{}/messages/{}/reactions/{}", channel_id, message_id, reaction), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessageReactions { channel_id: ChannelId, message_id: MessageId }, api!("/channels/{}/messages/{}/reactions", channel_id, message_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessages { channel_id: ChannelId }, api!("/channels/{}/messages", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessagesBulkDelete { channel_id: ChannelId }, api!("/channels/{}/messages/bulk-delete", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelFollowNews { channel_id: ChannelId }, api!("/channels/{}/followers", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelPermission { channel_id: ChannelId, target_id: TargetId }, api!("/channels/{}/permissions/{}", channel_id, target_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelPin { channel_id: ChannelId, message_id: MessageId }, api!("/channels/{}/pins/{}", channel_id, message_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelPins { channel_id: ChannelId }, api!("/channels/{}/pins", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelTyping { channel_id: ChannelId }, api!("/channels/{}/typing", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelWebhooks { channel_id: ChannelId }, api!("/channels/{}/webhooks", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelMessageThreads { channel_id: ChannelId, message_id: MessageId }, api!("/channels/{}/messages/{}/threads", channel_id, message_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelThreads { channel_id: ChannelId }, api!("/channels/{}/threads", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelForumPosts { channel_id: ChannelId }, api!("/channels/{}/threads", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelThreadMember { channel_id: ChannelId, user_id: UserId }, api!("/channels/{}/thread-members/{}", channel_id, user_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelThreadMemberMe { channel_id: ChannelId }, api!("/channels/{}/thread-members/@me", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelThreadMembers { channel_id: ChannelId }, api!("/channels/{}/thread-members", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelArchivedPublicThreads { channel_id: ChannelId }, api!("/channels/{}/threads/archived/public", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelArchivedPrivateThreads { channel_id: ChannelId }, api!("/channels/{}/threads/archived/private", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelJoinedPrivateThreads { channel_id: ChannelId }, api!("/channels/{}/users/@me/threads/archived/private", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelPollGetAnswerVoters { channel_id: ChannelId, message_id: MessageId, answer_id: AnswerId }, api!("/channels/{}/polls/{}/answers/{}", channel_id, message_id, answer_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelPollExpire { channel_id: ChannelId, message_id: MessageId }, api!("/channels/{}/polls/{}/expire", channel_id, message_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); ChannelVoiceStatus { channel_id: ChannelId }, api!("/channels/{}/voice-status", channel_id), - Some(RatelimitingKind::PathAndId(channel_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(channel_id.get()))); Gateway, api!("/gateway"), @@ -213,151 +211,151 @@ routes! ('a, { Guild { guild_id: GuildId }, api!("/guilds/{}", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildAuditLogs { guild_id: GuildId }, api!("/guilds/{}/audit-logs", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildAutomodRule { guild_id: GuildId, rule_id: RuleId }, api!("/guilds/{}/auto-moderation/rules/{}", guild_id, rule_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildAutomodRules { guild_id: GuildId }, api!("/guilds/{}/auto-moderation/rules", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildBan { guild_id: GuildId, user_id: UserId }, api!("/guilds/{}/bans/{}", guild_id, user_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildBulkBan { guild_id: GuildId }, api!("/guilds/{}/bulk-ban", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildBans { guild_id: GuildId }, api!("/guilds/{}/bans", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildChannels { guild_id: GuildId }, api!("/guilds/{}/channels", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildWidget { guild_id: GuildId }, api!("/guilds/{}/widget", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildPreview { guild_id: GuildId }, api!("/guilds/{}/preview", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildEmojis { guild_id: GuildId }, api!("/guilds/{}/emojis", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildEmoji { guild_id: GuildId, emoji_id: EmojiId }, api!("/guilds/{}/emojis/{}", guild_id, emoji_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildIntegration { guild_id: GuildId, integration_id: IntegrationId }, api!("/guilds/{}/integrations/{}", guild_id, integration_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildIntegrationSync { guild_id: GuildId, integration_id: IntegrationId }, api!("/guilds/{}/integrations/{}/sync", guild_id, integration_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildIntegrations { guild_id: GuildId }, api!("/guilds/{}/integrations", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildInvites { guild_id: GuildId }, api!("/guilds/{}/invites", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildMember { guild_id: GuildId, user_id: UserId }, api!("/guilds/{}/members/{}", guild_id, user_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildMemberRole { guild_id: GuildId, user_id: UserId, role_id: RoleId }, api!("/guilds/{}/members/{}/roles/{}", guild_id, user_id, role_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildMembers { guild_id: GuildId }, api!("/guilds/{}/members", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildMembersSearch { guild_id: GuildId }, api!("/guilds/{}/members/search", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildMemberMe { guild_id: GuildId }, api!("/guilds/{}/members/@me", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildMfa { guild_id: GuildId }, api!("/guilds/{}/mfa", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildPrune { guild_id: GuildId }, api!("/guilds/{}/prune", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildRegions { guild_id: GuildId }, api!("/guilds/{}/regions", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildRole { guild_id: GuildId, role_id: RoleId }, api!("/guilds/{}/roles/{}", guild_id, role_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildRoles { guild_id: GuildId }, api!("/guilds/{}/roles", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildScheduledEvent { guild_id: GuildId, event_id: ScheduledEventId }, api!("/guilds/{}/scheduled-events/{}", guild_id, event_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildScheduledEvents { guild_id: GuildId }, api!("/guilds/{}/scheduled-events", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildScheduledEventUsers { guild_id: GuildId, event_id: ScheduledEventId }, api!("/guilds/{}/scheduled-events/{}/users", guild_id, event_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildSticker { guild_id: GuildId, sticker_id: StickerId }, api!("/guilds/{}/stickers/{}", guild_id, sticker_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildStickers { guild_id: GuildId }, api!("/guilds/{}/stickers", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildVanityUrl { guild_id: GuildId }, api!("/guilds/{}/vanity-url", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildVoiceStates { guild_id: GuildId, user_id: UserId }, api!("/guilds/{}/voice-states/{}", guild_id, user_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildVoiceStateMe { guild_id: GuildId }, api!("/guilds/{}/voice-states/@me", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildWebhooks { guild_id: GuildId }, api!("/guilds/{}/webhooks", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildWelcomeScreen { guild_id: GuildId }, api!("/guilds/{}/welcome-screen", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); GuildThreadsActive { guild_id: GuildId }, api!("/guilds/{}/threads/active", guild_id), - Some(RatelimitingKind::PathAndId(guild_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(guild_id.get()))); Guilds, api!("/guilds"), @@ -429,75 +427,75 @@ routes! ('a, { Webhook { webhook_id: WebhookId }, api!("/webhooks/{}", webhook_id), - Some(RatelimitingKind::PathAndId(webhook_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(webhook_id.get()))); WebhookWithToken { webhook_id: WebhookId, token: &'a str }, api!("/webhooks/{}/{}", webhook_id, token), - Some(RatelimitingKind::PathAndId(webhook_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(webhook_id.get()))); WebhookMessage { webhook_id: WebhookId, token: &'a str, message_id: MessageId }, api!("/webhooks/{}/{}/messages/{}", webhook_id, token, message_id), - Some(RatelimitingKind::PathAndId(webhook_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(webhook_id.get()))); WebhookOriginalInteractionResponse { application_id: ApplicationId, token: &'a str }, api!("/webhooks/{}/{}/messages/@original", application_id, token), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); WebhookFollowupMessage { application_id: ApplicationId, token: &'a str, message_id: MessageId }, api!("/webhooks/{}/{}/messages/{}", application_id, token, message_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); WebhookFollowupMessages { application_id: ApplicationId, token: &'a str }, api!("/webhooks/{}/{}", application_id, token), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); InteractionResponse { interaction_id: InteractionId, token: &'a str }, api!("/interactions/{}/{}/callback", interaction_id, token), - Some(RatelimitingKind::PathAndId(interaction_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(interaction_id.get()))); Command { application_id: ApplicationId, command_id: CommandId }, api!("/applications/{}/commands/{}", application_id, command_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); Commands { application_id: ApplicationId }, api!("/applications/{}/commands", application_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); GuildCommand { application_id: ApplicationId, guild_id: GuildId, command_id: CommandId }, api!("/applications/{}/guilds/{}/commands/{}", application_id, guild_id, command_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); GuildCommandPermissions { application_id: ApplicationId, guild_id: GuildId, command_id: CommandId }, api!("/applications/{}/guilds/{}/commands/{}/permissions", application_id, guild_id, command_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); GuildCommands { application_id: ApplicationId, guild_id: GuildId }, api!("/applications/{}/guilds/{}/commands", application_id, guild_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); GuildCommandsPermissions { application_id: ApplicationId, guild_id: GuildId }, api!("/applications/{}/guilds/{}/commands/permissions", application_id, guild_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); Skus { application_id: ApplicationId }, api!("/applications/{}/skus", application_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); Emoji { application_id: ApplicationId, emoji_id: EmojiId }, api!("/applications/{}/emojis/{}", application_id, emoji_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); Emojis { application_id: ApplicationId }, api!("/applications/{}/emojis", application_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); Entitlement { application_id: ApplicationId, entitlement_id: EntitlementId }, api!("/applications/{}/entitlements/{}", application_id, entitlement_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); Entitlements { application_id: ApplicationId }, api!("/applications/{}/entitlements", application_id), - Some(RatelimitingKind::PathAndId(application_id.into())); + Some(RatelimitingKind::PathAndId(GenericId::new(application_id.get()))); StageInstances, api!("/stage-instances"), diff --git a/src/model/application/interaction.rs b/src/model/application/interaction.rs index fa4adad36fe..c75afade741 100644 --- a/src/model/application/interaction.rs +++ b/src/model/application/interaction.rs @@ -342,7 +342,13 @@ impl<'de> serde::Deserialize<'de> for AuthorizingIntegrationOwners { // but invoked in a DM, we have to do this fun deserialisation dance. let id_str = map.next_value::>()?; let id_int = id_str.parse().map_err(A::Error::custom)?; - let id = std::num::NonZeroU64::new(id_int).map(GuildId::from); + let id = if id_int == 0 { + None + } else if id_int == u64::MAX { + return Err(serde::de::Error::custom("GuildId cannot be u64::MAX")); + } else { + Some(GuildId::new(id_int)) + }; AuthorizingIntegrationOwner::GuildInstall(id) }, diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 69d9ea662db..40671440a8f 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -37,7 +37,6 @@ pub struct BotGateway { #[non_exhaustive] pub struct Activity { /// The ID of the application for the activity. - #[serde(deserialize_with = "deserialize_buggy_id")] #[serde(default)] pub application_id: Option, /// Images for the presence and their texts. diff --git a/src/model/id.rs b/src/model/id.rs index b4cd562dd85..a83ce72a924 100644 --- a/src/model/id.rs +++ b/src/model/id.rs @@ -1,8 +1,8 @@ //! A collection of newtypes defining type-strong IDs. use std::fmt; -use std::num::{NonZeroI64, NonZeroU64}; +use nonmax::NonMaxU64; use serde::de::Error; use super::prelude::*; @@ -20,33 +20,43 @@ macro_rules! newtype_display_impl { macro_rules! forward_fromstr_impl { ($name:ident, $wrapper:path) => { impl std::str::FromStr for $name { - type Err = ::Err; + type Err = ParseIdError; fn from_str(s: &str) -> Result { - Ok(Self($wrapper(s.parse()?))) + s.parse().map($wrapper).map(Self).map_err(ParseIdError) } } }; } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ParseIdError(nonmax::ParseIntError); + +impl std::error::Error for ParseIdError {} +impl std::fmt::Display for ParseIdError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + macro_rules! id_u64 { ($($name:ident: $doc:literal;)*) => { $( - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] + #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] #[doc = $doc] pub struct $name(InnerId); impl $name { #[doc = concat!("Creates a new ", stringify!($name), " from a u64.")] /// # Panics - /// Panics if `id` is zero. + /// Panics if `id` is u64::MAX. #[inline] #[must_use] #[track_caller] pub const fn new(id: u64) -> Self { - match NonZeroU64::new(id) { + match NonMaxU64::new(id) { Some(inner) => Self(InnerId(inner)), - None => panic!(concat!("Attempted to call ", stringify!($name), "::new with invalid (0) value")) + None => panic!(concat!("Attempted to call ", stringify!($name), "::new with invalid (u64::MAX) value")) } } @@ -54,7 +64,10 @@ macro_rules! id_u64 { #[inline] #[must_use] pub const fn get(self) -> u64 { - self.0.0.get() + // By wrapping `self.0.0` in a block, it forces a Copy, as NonMax::get takes &self. + // If removed, the compiler will auto-ref to `&self.0`, which is a + // reference to a packed field and therefore errors. + {self.0.0}.get() } #[doc = concat!("Retrieves the time that the ", stringify!($name), " was created.")] @@ -64,12 +77,6 @@ macro_rules! id_u64 { } } - impl Default for $name { - fn default() -> Self { - Self(InnerId(NonZeroU64::MIN)) - } - } - // This is a hack so functions can accept iterators that either: // 1. return the id itself (e.g: `MessageId`) // 2. return a reference to it (`&MessageId`). @@ -91,30 +98,12 @@ macro_rules! id_u64 { } } - impl From for $name { - fn from(id: NonZeroU64) -> $name { - $name(InnerId(id)) - } - } - impl PartialEq for $name { fn eq(&self, u: &u64) -> bool { self.get() == *u } } - impl From<$name> for NonZeroU64 { - fn from(id: $name) -> NonZeroU64 { - id.0.0 - } - } - - impl From<$name> for NonZeroI64 { - fn from(id: $name) -> NonZeroI64 { - NonZeroI64::new(id.get() as i64).unwrap() - } - } - impl From<$name> for u64 { fn from(id: $name) -> u64 { id.get() @@ -137,9 +126,9 @@ macro_rules! id_u64 { } /// The inner storage of an ID. -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[repr(packed)] -pub(crate) struct InnerId(NonZeroU64); +pub(crate) struct InnerId(NonMaxU64); struct SnowflakeVisitor; @@ -156,7 +145,7 @@ impl serde::de::Visitor<'_> for SnowflakeVisitor { } fn visit_u64(self, value: u64) -> Result { - NonZeroU64::new(value) + NonMaxU64::new(value) .map(InnerId) .ok_or_else(|| Error::custom("invalid value, expected non-max")) } @@ -212,7 +201,7 @@ id_u64! { /// This identifier is special, it simply models internal IDs for type safety, /// and therefore cannot be [`Serialize`]d or [`Deserialize`]d. #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct ShardId(pub u16); impl ShardId { @@ -236,7 +225,7 @@ newtype_display_impl!(ShardId, |this| this.0); #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] #[repr(packed)] -pub struct AnswerId(u8); +pub struct AnswerId(nonmax::NonMaxU8); impl AnswerId { /// Retrieves the value as a [`u64`]. @@ -244,7 +233,7 @@ impl AnswerId { /// Keep in mind that this is **not a snowflake** and the values are subject to change. #[must_use] pub fn get(self) -> u64 { - self.0.into() + { self.0 }.get().into() } } @@ -253,7 +242,7 @@ forward_fromstr_impl!(AnswerId, std::convert::identity); #[cfg(test)] mod tests { - use std::num::NonZeroU64; + use nonmax::NonMaxU64; use super::{GuildId, InnerId}; @@ -285,7 +274,7 @@ mod tests { assert_json(&id, json!("175928847299117063")); let s = S { - id: InnerId(NonZeroU64::new(17_5928_8472_9911_7063).unwrap()), + id: InnerId(NonMaxU64::new(17_5928_8472_9911_7063).unwrap()), }; assert_json(&s, json!({"id": "175928847299117063"})); diff --git a/src/model/mention.rs b/src/model/mention.rs index 0011352d07c..843db551894 100644 --- a/src/model/mention.rs +++ b/src/model/mention.rs @@ -206,8 +206,8 @@ mod test { #[cfg(feature = "model")] assert_eq!(channel.mention().to_string(), "<#4>"); assert_eq!(member.mention().to_string(), "<@6>"); - assert_eq!(role.mention().to_string(), "<@&1>"); - assert_eq!(role.id.mention().to_string(), "<@&1>"); + assert_eq!(role.mention().to_string(), "<@&0>"); + assert_eq!(role.id.mention().to_string(), "<@&0>"); assert_eq!(user.mention().to_string(), "<@6>"); assert_eq!(user.id.mention().to_string(), "<@6>"); } diff --git a/src/model/utils.rs b/src/model/utils.rs index 58dbd5a9e18..3e58f33a3c8 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -1,7 +1,6 @@ use std::fmt; use std::hash::Hash; use std::marker::PhantomData; -use std::num::NonZeroU64; use arrayvec::ArrayVec; use serde::de::Error as DeError; @@ -71,21 +70,6 @@ where remove_from_map_opt(map, key)?.ok_or_else(|| serde::de::Error::missing_field(key)) } -/// Workaround for Discord sending 0 value Ids as default values. -/// This has been fixed properly on next by swapping to a NonMax based impl. -pub fn deserialize_buggy_id<'de, D, Id>(deserializer: D) -> StdResult, D::Error> -where - D: Deserializer<'de>, - Id: From, -{ - if let Some(val) = Option::>::deserialize(deserializer)? { - let val = val.parse().map_err(serde::de::Error::custom)?; - Ok(NonZeroU64::new(val).map(Id::from)) - } else { - Ok(None) - } -} - pub(super) enum StrOrInt<'de> { String(String), Str(&'de str), diff --git a/src/utils/content_safe.rs b/src/utils/content_safe.rs index 3b0be8218e4..d071b1a3112 100644 --- a/src/utils/content_safe.rs +++ b/src/utils/content_safe.rs @@ -254,7 +254,7 @@ mod tests { <@!i)/==(<<>z/9080)> <@!1231invalid> <@invalid123> \ <@123invalid> <@> <@ "; - let without_user_mentions = "@Ferris <@!000000000000000000> @invalid-user @invalid-user \ + let without_user_mentions = "@Ferris @invalid-user @invalid-user @invalid-user \ <@!123123123123123123123> @invalid-user @invalid-user <@!invalid> \ <@invalid> <@日本語 한국어$§)[/__#\\(/&2032$§#> \ <@!i)/==(<<>z/9080)> <@!1231invalid> <@invalid123> \ From 7b99731509fd39da0b672bcd11d1147dd9dd28fe Mon Sep 17 00:00:00 2001 From: Gnome! Date: Sat, 6 Jan 2024 00:14:25 +0000 Subject: [PATCH 020/159] 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