From e1975a9f6f4300ee1d64fcba1371f78b708448ea 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 | 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 6b3a403a4f6..d0560c1973d 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 fe3330c8c3b..31e0d1f1d56 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 9b294a5ce21..b56b2575fe1 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 9d0799c496a..8040099e302 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 2436943942e..7ca7bf9ad5f 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, @@ -44,7 +46,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. @@ -88,7 +90,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`. @@ -99,7 +101,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 @@ -109,14 +111,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. @@ -138,7 +138,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. @@ -157,7 +157,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 02f49588feb..848c15cb25b 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 5a19299b00c..4fd11eab32e 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; @@ -534,7 +535,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 ce30e751a52..29f16053b67 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 ad600e05805..b7cc316e54b 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; @@ -368,16 +369,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 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 881a7da5708..8a34271f5d1 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}; @@ -189,9 +190,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. @@ -201,7 +202,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, @@ -211,13 +212,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 b15a3a83170..fe86f37a219 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 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")]