diff --git a/Cargo.toml b/Cargo.toml index 4988368c330..5b5c5aaa4bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ base64 = { version = "0.21.5" } secrecy = { version = "0.8.0", features = ["serde"] } arrayvec = { version = "0.7.4", features = ["serde"] } serde_cow = { version = "0.1.0" } -small-fixed-array = { version = "0.1.1", features = ["serde", "log_using_tracing"] } +small-fixed-array = { version = "0.2", features = ["serde"] } bool_to_bitflags = { version = "0.1.0" } nonmax = { version = "0.5.5", features = ["serde"] } # Optional dependencies diff --git a/src/cache/event.rs b/src/cache/event.rs index 2fe5a8cb4cf..8c6a552ec29 100644 --- a/src/cache/event.rs +++ b/src/cache/event.rs @@ -508,7 +508,9 @@ impl CacheUpdate for ThreadCreateEvent { // 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(); + + g.threads = FixedArray::try_from(threads.into_boxed_slice()) + .expect("A guild should not have 4 billion threads"); None } @@ -529,7 +531,9 @@ impl CacheUpdate for ThreadUpdateEvent { // 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(); + + g.threads = FixedArray::try_from(threads.into_boxed_slice()) + .expect("A guild should not have 4 billion threads"); None } @@ -547,7 +551,9 @@ impl CacheUpdate for ThreadDeleteEvent { 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(); + + g.threads = FixedArray::try_from(threads.into_boxed_slice()) + .expect("A guild should not have 4 billion threads"); thread }) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index d747907d92f..fbef6747a20 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -88,7 +88,7 @@ impl ActivityData { #[must_use] pub fn playing(name: impl Into) -> Self { Self { - name: name.into().into(), + name: name.into().trunc_into(), kind: ActivityType::Playing, state: None, url: None, @@ -103,7 +103,7 @@ impl ActivityData { #[cfg(feature = "http")] pub fn streaming(name: impl Into, url: impl IntoUrl) -> Result { Ok(Self { - name: name.into().into(), + name: name.into().trunc_into(), kind: ActivityType::Streaming, state: None, url: Some(url.into_url()?), @@ -114,7 +114,7 @@ impl ActivityData { #[must_use] pub fn listening(name: impl Into) -> Self { Self { - name: name.into().into(), + name: name.into().trunc_into(), kind: ActivityType::Listening, state: None, url: None, @@ -125,7 +125,7 @@ impl ActivityData { #[must_use] pub fn watching(name: impl Into) -> Self { Self { - name: name.into().into(), + name: name.into().trunc_into(), kind: ActivityType::Watching, state: None, url: None, @@ -136,7 +136,7 @@ impl ActivityData { #[must_use] pub fn competing(name: impl Into) -> Self { Self { - name: name.into().into(), + name: name.into().trunc_into(), kind: ActivityType::Competing, state: None, url: None, @@ -149,9 +149,9 @@ impl ActivityData { Self { // discord seems to require a name for custom activities // even though it's not displayed - name: "~".to_string().into(), + name: FixedString::from_str_trunc("~"), kind: ActivityType::Custom, - state: Some(state.into().into()), + state: Some(state.into().trunc_into()), url: None, } } diff --git a/src/http/error.rs b/src/http/error.rs index a63bfcbcff2..a2e1004afc4 100644 --- a/src/http/error.rs +++ b/src/http/error.rs @@ -43,9 +43,9 @@ pub struct DiscordJsonSingleError { #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub struct ErrorResponse { + pub method: Method, pub status_code: StatusCode, pub url: FixedString, - pub method: Method, pub error: DiscordJsonError, } @@ -53,13 +53,13 @@ impl ErrorResponse { // We need a freestanding from-function since we cannot implement an async From-trait. pub async fn from_response(r: Response, method: Method) -> Self { ErrorResponse { - status_code: r.status(), - url: r.url().to_string().into(), method, + status_code: r.status(), + url: FixedString::from_str_trunc(r.url().as_str()), error: decode_resp(r).await.unwrap_or_else(|e| DiscordJsonError { code: -1, errors: FixedArray::empty(), - message: format!("[Serenity] Could not decode json when receiving error response from discord:, {e}").into(), + message: format!("[Serenity] Could not decode json when receiving error response from discord:, {e}").trunc_into(), }), } } @@ -197,7 +197,7 @@ pub fn deserialize_errors<'de, D: Deserializer<'de>>( let mut path = Vec::new(); loop_errors(map, &mut errors, &mut path).map_err(D::Error::custom)?; - Ok(errors.into()) + Ok(errors.trunc_into()) } fn make_error( @@ -253,8 +253,8 @@ mod test { async fn test_error_response_into() { let error = DiscordJsonError { code: 43121215, - message: String::from("This is a Ferris error").into(), errors: FixedArray::empty(), + message: FixedString::from_str_trunc("This is a Ferris error"), }; let mut builder = Builder::new(); @@ -268,7 +268,7 @@ mod test { let known = ErrorResponse { status_code: reqwest::StatusCode::from_u16(403).unwrap(), - url: String::from("https://ferris.crab/").into(), + url: FixedString::from_str_trunc("https://ferris.crab/"), method: Method::POST, error, }; diff --git a/src/internal/prelude.rs b/src/internal/prelude.rs index 7a1e846f11d..ec3e2d3a8b9 100644 --- a/src/internal/prelude.rs +++ b/src/internal/prelude.rs @@ -4,7 +4,7 @@ pub use std::result::Result as StdResult; -pub use small_fixed_array::{FixedArray, FixedString}; +pub use small_fixed_array::{FixedArray, FixedString, TruncatingInto}; pub use crate::error::{Error, Result}; pub use crate::json::{JsonMap, Value}; diff --git a/src/lib.rs b/src/lib.rs index 1387436b149..efa95afd84d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,4 +187,4 @@ pub mod all { } // Re-exports of crates used internally which are already publically exposed. -pub use nonmax; +pub use {nonmax, small_fixed_array}; diff --git a/src/model/application/command_interaction.rs b/src/model/application/command_interaction.rs index dc97c61ce49..c463ea4ac28 100644 --- a/src/model/application/command_interaction.rs +++ b/src/model/application/command_interaction.rs @@ -339,10 +339,10 @@ impl CommandData { for opt in opts { let value = match &opt.value { CommandDataOptionValue::SubCommand(opts) => { - ResolvedValue::SubCommand(resolve_options(opts, resolved).into()) + ResolvedValue::SubCommand(resolve_options(opts, resolved).trunc_into()) }, CommandDataOptionValue::SubCommandGroup(opts) => { - ResolvedValue::SubCommandGroup(resolve_options(opts, resolved).into()) + ResolvedValue::SubCommandGroup(resolve_options(opts, resolved).trunc_into()) }, CommandDataOptionValue::Autocomplete { kind, @@ -566,13 +566,13 @@ fn option_from_raw(raw: RawCommandDataOption) -> Result { let options = raw.options.ok_or_else::(|| DeError::missing_field("options"))?; let options = options.into_iter().map(option_from_raw).collect::>()?; - CommandDataOptionValue::SubCommand(options) + CommandDataOptionValue::SubCommand(FixedArray::from_vec_trunc(options)) }, CommandOptionType::SubCommandGroup => { let options = raw.options.ok_or_else::(|| DeError::missing_field("options"))?; let options = options.into_iter().map(option_from_raw).collect::>()?; - CommandDataOptionValue::SubCommandGroup(options) + CommandDataOptionValue::SubCommandGroup(FixedArray::from_vec_trunc(options)) }, CommandOptionType::Attachment => CommandDataOptionValue::Attachment(value!()), CommandOptionType::Channel => CommandDataOptionValue::Channel(value!()), @@ -811,19 +811,19 @@ mod tests { #[test] fn nested_options() { let value = CommandDataOption { - name: "subcommand_group".to_string().into(), + name: FixedString::from_str_trunc("subcommand_group"), value: CommandDataOptionValue::SubCommandGroup( vec![CommandDataOption { - name: "subcommand".to_string().into(), + name: FixedString::from_str_trunc("subcommand"), value: CommandDataOptionValue::SubCommand( vec![CommandDataOption { - name: "channel".to_string().into(), + name: FixedString::from_str_trunc("channel"), value: CommandDataOptionValue::Channel(ChannelId::new(3)), }] - .into(), + .trunc_into(), ), }] - .into(), + .trunc_into(), ), }; @@ -845,30 +845,30 @@ mod tests { fn mixed_options() { let value = vec![ CommandDataOption { - name: "boolean".to_string().into(), + name: FixedString::from_str_trunc("boolean"), value: CommandDataOptionValue::Boolean(true), }, CommandDataOption { - name: "integer".to_string().into(), + name: FixedString::from_str_trunc("integer"), value: CommandDataOptionValue::Integer(1), }, CommandDataOption { - name: "number".to_string().into(), + name: FixedString::from_str_trunc("number"), value: CommandDataOptionValue::Number(2.0), }, CommandDataOption { - name: "string".to_string().into(), - value: CommandDataOptionValue::String("foobar".to_string().into()), + name: FixedString::from_str_trunc("string"), + value: CommandDataOptionValue::String(FixedString::from_str_trunc("foobar")), }, CommandDataOption { - name: "empty_subcommand".to_string().into(), + name: FixedString::from_str_trunc("empty_subcommand"), value: CommandDataOptionValue::SubCommand(FixedArray::default()), }, CommandDataOption { - name: "autocomplete".to_string().into(), + name: FixedString::from_str_trunc("autocomplete"), value: CommandDataOptionValue::Autocomplete { kind: CommandOptionType::Integer, - value: "not an integer".to_string().into(), + value: FixedString::from_str_trunc("not an integer"), }, }, ]; diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 6c6887aae8d..a75f82fbf2b 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -304,10 +304,10 @@ mod tests { let mut button = Button { kind: ComponentType::Button, data: ButtonKind::NonLink { - custom_id: "hello".to_string().into(), + custom_id: FixedString::from_str_trunc("hello"), style: ButtonStyle::Danger, }, - label: Some("a".to_string().into()), + label: Some(FixedString::from_str_trunc("a")), emoji: None, disabled: false, }; @@ -317,7 +317,7 @@ mod tests { ); button.data = ButtonKind::Link { - url: "https://google.com".to_string().into(), + url: FixedString::from_str_trunc("https://google.com"), }; assert_json( &button, diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs index 8040099e302..b41afeef0a6 100644 --- a/src/model/channel/embed.rs +++ b/src/model/channel/embed.rs @@ -124,7 +124,7 @@ impl EmbedField { T: Into, U: Into, { - Self::_new(name.into().into(), value.into().into(), inline) + Self::_new(name.into().trunc_into(), value.into().trunc_into(), inline) } pub(crate) const fn _new( diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs index ab9e91258ee..cf5ea17abdc 100644 --- a/src/model/channel/reaction.rs +++ b/src/model/channel/reaction.rs @@ -382,7 +382,7 @@ impl From for ReactionType { /// # fn main() {} /// ``` fn from(ch: char) -> ReactionType { - ReactionType::Unicode(ch.to_string().into()) + ReactionType::Unicode(ch.to_string().trunc_into()) } } @@ -436,7 +436,7 @@ impl TryFrom for ReactionType { } if !emoji_string.starts_with('<') { - return Ok(ReactionType::Unicode(emoji_string.into())); + return Ok(ReactionType::Unicode(emoji_string.trunc_into())); } ReactionType::try_from(&emoji_string[..]) } @@ -470,13 +470,14 @@ impl<'a> TryFrom<&'a str> for ReactionType { /// ```rust /// use serenity::model::channel::ReactionType; /// use serenity::model::id::EmojiId; + /// use serenity::small_fixed_array::FixedString; /// /// let emoji_string = "<:customemoji:600404340292059257>"; /// let reaction = ReactionType::try_from(emoji_string).unwrap(); /// let reaction2 = ReactionType::Custom { /// animated: false, /// id: EmojiId::new(600404340292059257), - /// name: Some("customemoji".to_string().into()), + /// name: Some(FixedString::from_str_trunc("customemoji")), /// }; /// /// assert_eq!(reaction, reaction2); @@ -490,7 +491,7 @@ impl<'a> TryFrom<&'a str> for ReactionType { } if !emoji_str.starts_with('<') { - return Ok(ReactionType::Unicode(emoji_str.to_string().into())); + return Ok(ReactionType::Unicode(emoji_str.to_string().trunc_into())); } if !emoji_str.ends_with('>') { @@ -502,7 +503,7 @@ impl<'a> TryFrom<&'a str> for ReactionType { let mut split_iter = emoji_str.split(':'); let animated = split_iter.next().ok_or(ReactionConversionError)? == "a"; - let name = Some(split_iter.next().ok_or(ReactionConversionError)?.to_string().into()); + let name = Some(split_iter.next().ok_or(ReactionConversionError)?.to_string().trunc_into()); let id = split_iter.next().and_then(|s| s.parse().ok()).ok_or(ReactionConversionError)?; Ok(ReactionType::Custom { diff --git a/src/model/guild/audit_log/change.rs b/src/model/guild/audit_log/change.rs index d7d59a30ef1..4652e920339 100644 --- a/src/model/guild/audit_log/change.rs +++ b/src/model/guild/audit_log/change.rs @@ -299,7 +299,7 @@ mod tests { fn entity_type_variant() { let value = Change::Type { old: Some(EntityType::Int(123)), - new: Some(EntityType::Str("discord".to_string().into())), + new: Some(EntityType::Str(FixedString::from_str_trunc("discord"))), }; assert_json(&value, json!({"key": "type", "old_value": 123, "new_value": "discord"})); } diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index ddef74ac9a4..2316af6cb76 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -2645,9 +2645,9 @@ mod test { fn gen_member() -> Member { Member { - nick: Some("aaaa".to_string().into()), + nick: Some(FixedString::from_str_trunc("aaaa")), user: User { - name: "test".to_string().into(), + name: FixedString::from_str_trunc("test"), discriminator: NonZeroU16::new(1432), ..User::default() }, diff --git a/src/model/misc.rs b/src/model/misc.rs index 3b0214551be..8f1a239bd04 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -205,7 +205,7 @@ impl fmt::Display for EmojiIdentifier { #[derive(Debug)] #[cfg(all(feature = "model", feature = "utils"))] pub struct EmojiIdentifierParseError { - parsed_string: FixedString, + parsed_string: String, } #[cfg(all(feature = "model", feature = "utils"))] @@ -224,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().into(), + parsed_string: s.to_owned(), }) } } diff --git a/src/model/user.rs b/src/model/user.rs index edb4dbfaf81..736055b130f 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -801,6 +801,8 @@ mod test { use std::num::NonZeroU16; use std::str::FromStr; + use small_fixed_array::FixedString; + use crate::model::id::UserId; use crate::model::misc::ImageHash; use crate::model::user::User; @@ -811,7 +813,7 @@ mod test { id: UserId::new(210), avatar: Some(ImageHash::from_str("fb211703bcc04ee612c88d494df0272f").unwrap()), discriminator: NonZeroU16::new(1432), - name: "test".to_string().into(), + name: FixedString::from_str_trunc("test"), ..Default::default() }; diff --git a/src/model/utils.rs b/src/model/utils.rs index c94a8394dc8..7e049711c2e 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -232,13 +232,15 @@ pub fn deserialize_buttons<'de, D: Deserializer<'de>>( deserializer: D, ) -> StdResult, D::Error> { ArrayVec::<_, 2>::deserialize(deserializer).map(|labels| { - labels - .into_iter() - .map(|l| ActivityButton { - label: l, - url: FixedString::default(), - }) - .collect() + FixedArray::from_vec_trunc( + labels + .into_iter() + .map(|l| ActivityButton { + label: l, + url: FixedString::default(), + }) + .collect(), + ) }) } @@ -291,9 +293,9 @@ pub mod comma_separated_string { deserializer: D, ) -> Result, D::Error> { let str_sequence = CowStr::deserialize(deserializer)?.0; - let vec = str_sequence.split(", ").map(str::to_owned).map(FixedString::from).collect(); + let vec = str_sequence.split(", ").map(FixedString::from_str_trunc).collect(); - Ok(vec) + Ok(FixedArray::from_vec_trunc(vec)) } pub fn serialize( diff --git a/src/utils/content_safe.rs b/src/utils/content_safe.rs index 1a3bf1ee4b1..ebc1e02a986 100644 --- a/src/utils/content_safe.rs +++ b/src/utils/content_safe.rs @@ -196,6 +196,8 @@ fn clean_mention( #[allow(clippy::non_ascii_literal)] #[cfg(test)] mod tests { + use small_fixed_array::FixedString; + use super::*; use crate::model::channel::*; use crate::model::guild::*; @@ -206,7 +208,7 @@ mod tests { fn test_content_safe() { let user = User { id: UserId::new(100000000000000000), - name: "Crab".to_string().into(), + name: FixedString::from_str_trunc("Crab"), ..Default::default() }; @@ -218,19 +220,19 @@ mod tests { let mut guild = no_member_guild.clone(); let member = Member { - nick: Some("Ferris".to_string().into()), + nick: Some(FixedString::from_str_trunc("Ferris")), ..Default::default() }; let role = Role { id: RoleId::new(333333333333333333), - name: "ferris-club-member".to_string().into(), + name: FixedString::from_str_trunc("ferris-club-member"), ..Default::default() }; let channel = GuildChannel { id: ChannelId::new(111880193700067777), - name: "general".to_string().into(), + name: FixedString::from_str_trunc("general"), ..Default::default() }; diff --git a/src/utils/custom_message.rs b/src/utils/custom_message.rs index fc492df60f4..3c8d9ebf5f4 100644 --- a/src/utils/custom_message.rs +++ b/src/utils/custom_message.rs @@ -32,7 +32,7 @@ impl CustomMessage { /// /// If not used, the default value is an empty vector (`Vec::default()`). pub fn attachments(&mut self, attachments: Vec) -> &mut Self { - self.msg.attachments = attachments.into(); + self.msg.attachments = attachments.trunc_into(); self } @@ -59,7 +59,7 @@ impl CustomMessage { /// /// If not used, the default value is an empty string (`String::default()`). pub fn content(&mut self, s: impl Into) -> &mut Self { - self.msg.content = s.into().into(); + self.msg.content = s.into().trunc_into(); self } @@ -77,7 +77,7 @@ impl CustomMessage { /// /// If not used, the default value is an empty vector (`Vec::default()`). pub fn embeds(&mut self, embeds: Vec) -> &mut Self { - self.msg.embeds = embeds.into(); + self.msg.embeds = embeds.trunc_into(); self } @@ -124,7 +124,7 @@ impl CustomMessage { /// /// If not used, the default value is an empty vector (`Vec::default()`). pub fn mention_roles(&mut self, roles: Vec) -> &mut Self { - self.msg.mention_roles = roles.into(); + self.msg.mention_roles = roles.trunc_into(); self } @@ -133,7 +133,7 @@ impl CustomMessage { /// /// If not used, the default value is an empty vector (`Vec::default()`). pub fn mentions(&mut self, mentions: Vec) -> &mut Self { - self.msg.mentions = mentions.into(); + self.msg.mentions = mentions.trunc_into(); self } @@ -151,7 +151,7 @@ impl CustomMessage { /// /// If not used, the default value is an empty vector (`Vec::default()`). pub fn reactions(&mut self, reactions: Vec) -> &mut Self { - self.msg.reactions = reactions.into(); + self.msg.reactions = reactions.trunc_into(); self } diff --git a/src/utils/message_builder.rs b/src/utils/message_builder.rs index 7192c26ab72..f9d0a2fa828 100644 --- a/src/utils/message_builder.rs +++ b/src/utils/message_builder.rs @@ -1185,8 +1185,8 @@ mod test { fn mentions() { let mut emoji = Emoji { id: EmojiId::new(32), - name: "Rohrkatze".to_string().into(), - roles: vec![].into(), + name: FixedString::from_str_trunc("Rohrkatze"), + roles: FixedArray::empty(), user: None, __generated_flags: EmojiGeneratedFlags::empty(), }; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 0268e3bb7bb..719e837dc3c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -289,7 +289,7 @@ pub fn parse_emoji(mention: &str) -> Option { } id.parse().ok().map(|id| EmojiIdentifier { - name: name.into(), + name: name.trunc_into(), animated, id, }) diff --git a/src/utils/quick_modal.rs b/src/utils/quick_modal.rs index 2b90308f6fb..492846e4bf4 100644 --- a/src/utils/quick_modal.rs +++ b/src/utils/quick_modal.rs @@ -107,8 +107,8 @@ impl<'a> CreateQuickModal<'a> { ); builder.execute(ctx, (interaction_id, token)).await?; - let collector = - ModalInteractionCollector::new(&ctx.shard).custom_ids(vec![modal_custom_id.into()]); + let collector = ModalInteractionCollector::new(&ctx.shard) + .custom_ids(vec![modal_custom_id.trunc_into()]); let collector = match self.timeout { Some(timeout) => collector.timeout(timeout), @@ -144,7 +144,7 @@ impl<'a> CreateQuickModal<'a> { .collect(); Ok(Some(QuickModalResponse { - inputs, + inputs: FixedArray::from_vec_trunc(inputs), interaction: modal_interaction, })) } diff --git a/tests/test_reaction.rs b/tests/test_reaction.rs index cafd1e2c01b..935d91b4956 100644 --- a/tests/test_reaction.rs +++ b/tests/test_reaction.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use serenity::model::channel::ReactionType; use serenity::model::id::EmojiId; +use small_fixed_array::FixedString; #[test] fn str_to_reaction_type() { @@ -10,7 +11,7 @@ fn str_to_reaction_type() { let reaction2 = ReactionType::Custom { animated: false, id: EmojiId::new(600404340292059257), - name: Some("customemoji".to_string().into()), + name: Some(FixedString::from_str_trunc("customemoji")), }; assert_eq!(reaction, reaction2); } @@ -22,7 +23,7 @@ fn str_to_reaction_type_animated() { let reaction2 = ReactionType::Custom { animated: true, id: EmojiId::new(600409340292059257), - name: Some("customemoji2".to_string().into()), + name: Some(FixedString::from_str_trunc("customemoji2")), }; assert_eq!(reaction, reaction2); } @@ -34,7 +35,7 @@ fn string_to_reaction_type() { let reaction2 = ReactionType::Custom { animated: false, id: EmojiId::new(600404340292059257), - name: Some("customemoji".to_string().into()), + name: Some(FixedString::from_str_trunc("customemoji")), }; assert_eq!(reaction, reaction2); }