From cf3d911f0f9038b7d97d8617d22c2e82f1b09461 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Tue, 2 Jan 2024 20:06:52 +0000 Subject: [PATCH] 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 | 6 +- 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, 123 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2884e696c6..4d2cb3b77c7 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"] } 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 } @@ -50,7 +51,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.1", path = "./command_attr", optional = true } diff --git a/examples/e05_command_framework/src/main.rs b/examples/e05_command_framework/src/main.rs index 6c6fbdc03c0..aa754deff6b 100644 --- a/examples/e05_command_framework/src/main.rs +++ b/examples/e05_command_framework/src/main.rs @@ -554,8 +554,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 c1239b0db1a..9eaf19d1f14 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; @@ -39,15 +40,15 @@ pub struct Attachment { /// Sescription 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 @@ -79,7 +80,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 e0679ec8b2a..d4cc6cef97b 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 1ff1af44583..f9c19c69ec5 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, @@ -45,7 +47,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. @@ -89,7 +91,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. Note however, it's recommended to use /// [`Self::is_nsfw`] as it's gonna be more accurate. // This field can or can not be present sometimes, but if it isn't default to `false`. @@ -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 e112eb59f90..ad933146a4d 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"))] @@ -123,7 +125,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 ca73c530feb..ee71e9736c6 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; +use nonmax::NonMaxU64; use serde::de::Error as DeError; use serde::Serialize; @@ -538,7 +539,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 bd2ef951cbb..c2821a2c1a6 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; @@ -400,8 +400,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 bf6d11b0d1d..1e983a48024 100644 --- a/src/model/guild/audit_log/mod.rs +++ b/src/model/guild/audit_log/mod.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::mem::transmute; +use nonmax::{NonMaxU32, NonMaxU64}; use serde::de::Deserializer; use serde::ser::{Serialize, Serializer}; @@ -370,16 +371,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 353acc02adb..07dce2e4fe4 100644 --- a/src/model/guild/audit_log/utils.rs +++ b/src/model/guild/audit_log/utils.rs @@ -41,27 +41,69 @@ 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)) } - 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") @@ -71,7 +113,7 @@ pub mod optional_string { self, deserializer: D, ) -> Result { - deserializer.deserialize_any(OptionalStringVisitor) + deserializer.deserialize_any(OptionalStringVisitor(PhantomData)) } fn visit_none(self) -> Result { @@ -83,11 +125,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 b47b8d9087d..c4a3c77bb49 100644 --- a/src/model/guild/integration.rs +++ b/src/model/guild/integration.rs @@ -1,3 +1,5 @@ +use nonmax::{NonMaxU32, NonMaxU64}; + use super::*; use crate::model::Timestamp; @@ -21,11 +23,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 950e6f1e852..1e35b447a42 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}; @@ -194,9 +195,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. @@ -206,7 +207,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, @@ -216,13 +217,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 17edacbb0a8..5c55d41fcd7 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")] @@ -138,9 +139,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. @@ -150,7 +151,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, @@ -160,13 +161,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 eb942c3430c..54321a19d72 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")]