From a3eac29cbc5bfae27fa2804c636bf9ee2778ba42 Mon Sep 17 00:00:00 2001 From: kangalioo Date: Wed, 16 Nov 2022 20:35:25 +0100 Subject: [PATCH] Use #[serde(remote = "Self")] to simplify serde impls --- .../interaction/application_command.rs | 41 ++--- .../interaction/message_component.rs | 44 ++---- src/model/application/interaction/mod.rs | 23 +-- src/model/application/interaction/modal.rs | 44 ++---- src/model/channel/guild_channel.rs | 1 + src/model/channel/mod.rs | 1 + src/model/channel/reaction.rs | 112 ++++---------- src/model/event.rs | 143 +++++------------- src/model/guild/audit_log/mod.rs | 1 + src/model/guild/member.rs | 1 + src/model/guild/partial_guild.rs | 104 ++----------- src/model/guild/role.rs | 41 +---- src/model/guild/welcome_screen.rs | 1 + src/model/permissions.rs | 1 + src/model/utils.rs | 33 +--- src/model/voice.rs | 74 ++------- voice-model/src/speaking_state.rs | 2 + 17 files changed, 147 insertions(+), 520 deletions(-) diff --git a/src/model/application/interaction/application_command.rs b/src/model/application/interaction/application_command.rs index 78fa888548b..f06cc05d06a 100644 --- a/src/model/application/interaction/application_command.rs +++ b/src/model/application/interaction/application_command.rs @@ -19,7 +19,6 @@ use crate::client::Context; use crate::http::Http; use crate::internal::prelude::*; use crate::model::application::command::{CommandOptionType, CommandType}; -use crate::model::application::interaction::add_guild_id_to_resolved; use crate::model::channel::{Attachment, Message, PartialChannel}; use crate::model::guild::{Member, PartialMember, Role}; use crate::model::id::{ @@ -36,13 +35,13 @@ use crate::model::id::{ UserId, }; use crate::model::user::User; -use crate::model::utils::{remove_from_map, remove_from_map_opt}; use crate::model::Permissions; /// An interaction when a user invokes a slash command. /// /// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object). -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(remote = "Self")] #[non_exhaustive] pub struct CommandInteraction { /// Id of the interaction. @@ -238,35 +237,20 @@ impl CommandInteraction { } } +// Manual impl needed to insert guild_id into resolved Role's impl<'de> Deserialize<'de> for CommandInteraction { fn deserialize>(deserializer: D) -> StdResult { - let mut map = JsonMap::deserialize(deserializer)?; - - let guild_id = remove_from_map_opt::(&mut map, "guild_id")?; - - if let Some(guild_id) = guild_id { - add_guild_id_to_resolved(&mut map, guild_id); + let mut interaction = Self::deserialize(deserializer)?; // calls #[serde(remote)]-generated inherent method + if let Some(guild_id) = interaction.guild_id { + interaction.data.resolved.roles.values_mut().for_each(|r| r.guild_id = guild_id); } + Ok(interaction) + } +} - let member = remove_from_map_opt::, _>(&mut map, "member")?; - let user = remove_from_map_opt(&mut map, "user")? - .or_else(|| member.as_ref().map(|m| m.user.clone())) - .ok_or_else(|| DeError::custom("expected user or member"))?; - - Ok(Self { - guild_id, - member, - user, - id: remove_from_map(&mut map, "id")?, - application_id: remove_from_map(&mut map, "application_id")?, - data: remove_from_map(&mut map, "data")?, - channel_id: remove_from_map(&mut map, "channel_id")?, - token: remove_from_map(&mut map, "token")?, - version: remove_from_map(&mut map, "version")?, - app_permissions: remove_from_map_opt(&mut map, "app_permissions")?, - locale: remove_from_map(&mut map, "locale")?, - guild_locale: remove_from_map_opt(&mut map, "guild_locale")?, - }) +impl Serialize for CommandInteraction { + fn serialize(&self, serializer: S) -> StdResult { + Self::serialize(self, serializer) // calls #[serde(remote)]-generated inherent method } } @@ -622,6 +606,7 @@ fn option_to_raw(option: &CommandDataOption) -> StdResult Deserialize<'de> for CommandDataOption { fn deserialize>(deserializer: D) -> StdResult { option_from_raw(RawCommandDataOption::deserialize(deserializer)?) diff --git a/src/model/application/interaction/message_component.rs b/src/model/application/interaction/message_component.rs index 79048d8da4f..1a6af5f044a 100644 --- a/src/model/application/interaction/message_component.rs +++ b/src/model/application/interaction/message_component.rs @@ -15,7 +15,6 @@ use crate::client::Context; use crate::http::Http; use crate::internal::prelude::*; use crate::model::application::component::ComponentType; -use crate::model::application::interaction::add_guild_id_to_resolved; use crate::model::channel::Message; use crate::model::guild::Member; #[cfg(feature = "model")] @@ -30,13 +29,13 @@ use crate::model::id::{ UserId, }; use crate::model::user::User; -use crate::model::utils::{remove_from_map, remove_from_map_opt}; use crate::model::Permissions; /// An interaction triggered by a message component. /// /// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure). -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(remote = "Self")] #[non_exhaustive] pub struct ComponentInteraction { /// Id of the interaction. @@ -217,36 +216,22 @@ impl ComponentInteraction { } } +// Manual impl needed to insert guild_id into model data impl<'de> Deserialize<'de> for ComponentInteraction { fn deserialize>(deserializer: D) -> StdResult { - let mut map = JsonMap::deserialize(deserializer)?; - - let guild_id = remove_from_map_opt(&mut map, "guild_id")?; - - if let Some(guild_id) = guild_id { - add_guild_id_to_resolved(&mut map, guild_id); + let mut interaction = Self::deserialize(deserializer)?; // calls #[serde(remote)]-generated inherent method + if let (Some(guild_id), Some(member)) = (interaction.guild_id, &mut interaction.member) { + member.guild_id = guild_id; + // If `member` is present, `user` wasn't sent and is still filled with default data + interaction.user = member.user.clone(); } + Ok(interaction) + } +} - let member = remove_from_map_opt::(&mut map, "member")?; - let user = remove_from_map_opt(&mut map, "user")? - .or_else(|| member.as_ref().map(|m| m.user.clone())) - .ok_or_else(|| DeError::custom("expected user or member"))?; - - Ok(Self { - guild_id, - member, - user, - id: remove_from_map(&mut map, "id")?, - application_id: remove_from_map(&mut map, "application_id")?, - data: remove_from_map(&mut map, "data")?, - channel_id: remove_from_map(&mut map, "channel_id")?, - token: remove_from_map(&mut map, "token")?, - version: remove_from_map(&mut map, "version")?, - message: remove_from_map(&mut map, "message")?, - app_permissions: remove_from_map_opt(&mut map, "app_permissions")?, - locale: remove_from_map(&mut map, "locale")?, - guild_locale: remove_from_map_opt(&mut map, "guild_locale")?, - }) +impl Serialize for ComponentInteraction { + fn serialize(&self, serializer: S) -> StdResult { + Self::serialize(self, serializer) // calls #[serde(remote)]-generated inherent method } } @@ -261,6 +246,7 @@ pub enum ComponentInteractionDataKind { Unknown(u8), } +// Manual impl needed to emulate integer enum tags impl<'de> Deserialize<'de> for ComponentInteractionDataKind { fn deserialize>(deserializer: D) -> StdResult { #[derive(Deserialize)] diff --git a/src/model/application/interaction/mod.rs b/src/model/application/interaction/mod.rs index 86999fa946f..ee6f37333c5 100644 --- a/src/model/application/interaction/mod.rs +++ b/src/model/application/interaction/mod.rs @@ -13,7 +13,7 @@ use self::ping::PingInteraction; use crate::internal::prelude::*; use crate::json::from_value; use crate::model::guild::PartialMember; -use crate::model::id::{ApplicationId, GuildId, InteractionId}; +use crate::model::id::{ApplicationId, InteractionId}; use crate::model::user::User; use crate::model::utils::deserialize_val; use crate::model::Permissions; @@ -217,6 +217,7 @@ impl Interaction { } } +// Manual impl needed to emulate integer enum tags impl<'de> Deserialize<'de> for Interaction { fn deserialize>(deserializer: D) -> std::result::Result { let map = JsonMap::deserialize(deserializer)?; @@ -302,23 +303,3 @@ pub struct MessageInteraction { #[serde(skip_serializing_if = "Option::is_none")] pub member: Option, } - -fn add_guild_id_to_resolved(map: &mut JsonMap, guild_id: GuildId) { - if let Some(member) = map.get_mut("member").and_then(Value::as_object_mut) { - member.insert("guild_id".to_string(), guild_id.get().into()); - } - - if let Some(data) = map.get_mut("data") { - if let Some(resolved) = data.get_mut("resolved") { - if let Some(roles) = resolved.get_mut("roles") { - if let Some(values) = roles.as_object_mut() { - for value in values.values_mut() { - if let Some(role) = value.as_object_mut() { - role.insert("guild_id".to_string(), guild_id.get().into()); - }; - } - } - } - } - } -} diff --git a/src/model/application/interaction/modal.rs b/src/model/application/interaction/modal.rs index 89743e458df..3d8f5990a7f 100644 --- a/src/model/application/interaction/modal.rs +++ b/src/model/application/interaction/modal.rs @@ -1,7 +1,6 @@ -use serde::de::{Deserialize, Deserializer, Error as DeError}; +use serde::de::{Deserialize, Deserializer}; use serde::Serialize; -use super::add_guild_id_to_resolved; #[cfg(feature = "model")] use crate::builder::{ CreateInteractionResponse, @@ -18,13 +17,13 @@ use crate::model::guild::Member; use crate::model::id::MessageId; use crate::model::id::{ApplicationId, ChannelId, GuildId, InteractionId}; use crate::model::user::User; -use crate::model::utils::{remove_from_map, remove_from_map_opt}; use crate::model::Permissions; /// An interaction triggered by a modal submit. /// /// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object). -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(remote = "Self")] #[non_exhaustive] pub struct ModalInteraction { /// Id of the interaction. @@ -180,35 +179,22 @@ impl ModalInteraction { } } +// Manual impl needed to insert guild_id into resolved Role's impl<'de> Deserialize<'de> for ModalInteraction { fn deserialize>(deserializer: D) -> StdResult { - let mut map = JsonMap::deserialize(deserializer)?; - - let guild_id = remove_from_map_opt::(&mut map, "guild_id")?; - if let Some(guild_id) = guild_id { - add_guild_id_to_resolved(&mut map, guild_id); + let mut interaction = Self::deserialize(deserializer)?; // calls #[serde(remote)]-generated inherent method + if let (Some(guild_id), Some(member)) = (interaction.guild_id, &mut interaction.member) { + member.guild_id = guild_id; + // If `member` is present, `user` wasn't sent and is still filled with default data + interaction.user = member.user.clone(); } + Ok(interaction) + } +} - let member = remove_from_map_opt::(&mut map, "member")?; - let user = remove_from_map_opt(&mut map, "user")? - .or_else(|| member.as_ref().map(|m| m.user.clone())) - .ok_or_else(|| DeError::custom("expected user or member"))?; - - Ok(Self { - member, - user, - id: remove_from_map(&mut map, "id")?, - guild_id, - application_id: remove_from_map(&mut map, "application_id")?, - data: remove_from_map(&mut map, "data")?, - channel_id: remove_from_map(&mut map, "channel_id")?, - token: remove_from_map(&mut map, "token")?, - version: remove_from_map(&mut map, "version")?, - message: remove_from_map_opt(&mut map, "message")?, - app_permissions: remove_from_map_opt(&mut map, "app_permissions")?, - locale: remove_from_map(&mut map, "locale")?, - guild_locale: remove_from_map_opt(&mut map, "guild_locale")?, - }) +impl Serialize for ModalInteraction { + fn serialize(&self, serializer: S) -> StdResult { + Self::serialize(self, serializer) // calls #[serde(remote)]-generated inherent method } } diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index 88f9c09d2c6..6d1448c3350 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -55,6 +55,7 @@ pub struct GuildChannel { /// /// The original voice channel has an Id equal to the guild's Id, /// incremented by one. + #[serde(default)] pub guild_id: GuildId, /// The type of the channel. #[serde(rename = "type")] diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 16a73e58682..328da05b4c2 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -175,6 +175,7 @@ impl Channel { } } +// Manual impl needed to emulate integer enum tags impl<'de> Deserialize<'de> for Channel { fn deserialize>(deserializer: D) -> StdResult { let map = JsonMap::deserialize(deserializer)?; diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs index c5d800b3b28..7f7d973ac70 100644 --- a/src/model/channel/reaction.rs +++ b/src/model/channel/reaction.rs @@ -8,7 +8,7 @@ use std::str::FromStr; #[cfg(feature = "model")] use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; -use serde::de::{Deserialize, Error as DeError, MapAccess, Visitor}; +use serde::de::{Deserialize, Error as DeError}; use serde::ser::{Serialize, SerializeMap, Serializer}; #[cfg(feature = "model")] use tracing::warn; @@ -17,12 +17,12 @@ use tracing::warn; use crate::http::{CacheHttp, Http}; use crate::internal::prelude::*; use crate::model::prelude::*; -use crate::model::utils::{remove_from_map, remove_from_map_opt}; /// An emoji reaction to a message. /// /// [Discord docs](https://discord.com/developers/docs/topics/gateway#message-reaction-add-message-reaction-add-event-fields). -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(remote = "Self")] #[non_exhaustive] pub struct Reaction { /// The [`Channel`] of the associated [`Message`]. @@ -41,28 +41,20 @@ pub struct Reaction { pub member: Option, } +// Manual impl needed to insert guild_id into PartialMember impl<'de> Deserialize<'de> for Reaction { fn deserialize>(deserializer: D) -> StdResult { - let mut map = JsonMap::deserialize(deserializer)?; - - let guild_id = remove_from_map_opt::(&mut map, "guild_id")?; - - if let Some(id) = guild_id { - if let Some(member) = map.get_mut("member") { - if let Some(object) = member.as_object_mut() { - object.insert("guild_id".to_owned(), id.get().into()); - } - } + let mut reaction = Self::deserialize(deserializer)?; // calls #[serde(remote)]-generated inherent method + if let (Some(guild_id), Some(member)) = (reaction.guild_id, reaction.member.as_mut()) { + member.guild_id = Some(guild_id); } + Ok(reaction) + } +} - Ok(Self { - guild_id, - channel_id: remove_from_map(&mut map, "channel_id")?, - message_id: remove_from_map(&mut map, "message_id")?, - user_id: remove_from_map_opt(&mut map, "user_id")?, - member: remove_from_map_opt(&mut map, "member")?, - emoji: remove_from_map(&mut map, "emoji")?, - }) +impl Serialize for Reaction { + fn serialize(&self, serializer: S) -> StdResult { + Self::serialize(self, serializer) // calls #[serde(remote)]-generated inherent method } } @@ -287,72 +279,26 @@ pub enum ReactionType { Unicode(String), } +// Manual impl needed to decide enum variant by presence of `id` impl<'de> Deserialize<'de> for ReactionType { fn deserialize>(deserializer: D) -> StdResult { #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "snake_case")] - enum Field { - Animated, - Id, - Name, + struct PartialEmoji { + #[serde(default)] + animated: bool, + id: Option, + name: Option, } - - struct ReactionTypeVisitor; - - impl<'de> Visitor<'de> for ReactionTypeVisitor { - type Value = ReactionType; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("enum ReactionType") - } - - fn visit_map>(self, mut map: V) -> StdResult { - let mut animated = None; - let mut id = None; - let mut name = None; - - while let Some(key) = map.next_key()? { - match key { - Field::Animated => { - if animated.is_some() { - return Err(DeError::duplicate_field("animated")); - } - - animated = Some(map.next_value()?); - }, - Field::Id => { - if id.is_some() { - return Err(DeError::duplicate_field("id")); - } - - if let Ok(emoji_id) = map.next_value::() { - id = Some(emoji_id); - } - }, - Field::Name => { - if name.is_some() { - return Err(DeError::duplicate_field("name")); - } - - name = Some(map.next_value::>()?); - }, - } - } - - let rt = match (id, name) { - (Some(id), name) => ReactionType::Custom { - animated: animated.unwrap_or_default(), - id, - name: name.flatten(), - }, - (None, Some(Some(name))) => ReactionType::Unicode(name), - _ => return Err(DeError::custom("invalid reaction type data")), - }; - Ok(rt) - } - } - - deserializer.deserialize_map(ReactionTypeVisitor) + let emoji = PartialEmoji::deserialize(deserializer)?; + Ok(match (emoji.id, emoji.name) { + (Some(id), name) => ReactionType::Custom { + animated: emoji.animated, + id, + name, + }, + (None, Some(name)) => ReactionType::Unicode(name), + (None, None) => return Err(DeError::custom("invalid reaction type data")), + }) } } diff --git a/src/model/event.rs b/src/model/event.rs index 69b9c32e8cd..d28af507f9a 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -7,18 +7,17 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::fmt; -use serde::de::{Error as DeError, IgnoredAny, MapAccess}; +use serde::de::Error as DeError; +use serde::Serialize; use super::application::component::ActionRow; use super::prelude::*; use super::utils::{ - add_guild_id_to_map, deserialize_val, emojis, ignore_input, remove_from_map, remove_from_map_opt, - roles, stickers, }; use crate::constants::Opcode; @@ -156,18 +155,14 @@ pub struct GuildCreateEvent { pub guild: Guild, } +// Manual impl needed to insert guild_id fields in GuildChannel, Member, Role impl<'de> Deserialize<'de> for GuildCreateEvent { fn deserialize>(deserializer: D) -> StdResult { - let mut map = JsonMap::deserialize(deserializer)?; - - let id_val = map.get("id").ok_or_else(|| DeError::missing_field("id"))?; - let id = deserialize_val(id_val.clone())?; - - add_guild_id_to_map(&mut map, "channels", id); - add_guild_id_to_map(&mut map, "members", id); - add_guild_id_to_map(&mut map, "roles", id); - - deserialize_val(Value::from(map)).map(|guild| Self { + let mut guild: Guild = Guild::deserialize(deserializer)?; + guild.channels.values_mut().for_each(|x| x.guild_id = guild.id); + guild.members.values_mut().for_each(|x| x.guild_id = guild.id); + guild.roles.values_mut().for_each(|x| x.guild_id = guild.id); + Ok(Self { guild, }) } @@ -248,7 +243,8 @@ pub struct GuildMemberUpdateEvent { /// Requires no gateway intents. /// /// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#guild-members-chunk). -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(remote = "Self")] #[non_exhaustive] pub struct GuildMembersChunkEvent { pub guild_id: GuildId, @@ -258,104 +254,28 @@ pub struct GuildMembersChunkEvent { pub nonce: Option, } +// Manual impl needed to insert guild_id fields in Member impl<'de> Deserialize<'de> for GuildMembersChunkEvent { fn deserialize>(deserializer: D) -> StdResult { - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "snake_case")] - enum Field { - GuildId, - ChunkIndex, - ChunkCount, - Members, - Nonce, - Unknown(String), - } - - struct GuildMembersChunkVisitor; - - impl<'de> Visitor<'de> for GuildMembersChunkVisitor { - type Value = GuildMembersChunkEvent; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("struct GuildMembersChunkEvent") - } - - fn visit_map>(self, mut map: A) -> StdResult { - let mut guild_id = None; - let mut chunk_index = None; - let mut chunk_count = None; - let mut members = None; - let mut nonce = None; - - while let Some(key) = map.next_key()? { - match key { - Field::GuildId => { - if guild_id.is_some() { - return Err(DeError::duplicate_field("guild_id")); - } - guild_id = Some(map.next_value()?); - }, - Field::ChunkIndex => { - if chunk_index.is_some() { - return Err(DeError::duplicate_field("chunk_index")); - } - chunk_index = Some(map.next_value()?); - }, - Field::ChunkCount => { - if chunk_count.is_some() { - return Err(DeError::duplicate_field("chunk_count")); - } - chunk_count = Some(map.next_value()?); - }, - Field::Members => { - if members.is_some() { - return Err(DeError::duplicate_field("members")); - } - members = Some(map.next_value::>()?); - }, - Field::Nonce => { - if nonce.is_some() { - return Err(DeError::duplicate_field("nonce")); - } - nonce = Some(map.next_value()?); - }, - Field::Unknown(_) => { - // ignore unknown keys - map.next_value::()?; - }, - } - } - - let guild_id = guild_id.ok_or_else(|| DeError::missing_field("guild_id"))?; - let chunk_index = - chunk_index.ok_or_else(|| DeError::missing_field("chunk_index"))?; - let chunk_count = - chunk_count.ok_or_else(|| DeError::missing_field("chunk_count"))?; - let members = members.ok_or_else(|| DeError::missing_field("members"))?; - - let members = members - .into_iter() - .map(|mut m| { - m.guild_id = Some(guild_id); - (m.user.id, Member::from(m)) - }) - .collect(); - - Ok(GuildMembersChunkEvent { - guild_id, - members, - chunk_index, - chunk_count, - nonce, - }) - } - } + let mut event = Self::deserialize(deserializer)?; // calls #[serde(remote)]-generated inherent method + event.members.values_mut().for_each(|m| m.guild_id = event.guild_id); + Ok(event) + } +} - const FIELDS: &[&str] = &["guild_id", "chunk_index", "chunk_count", "members", "nonce"]; - deserializer.deserialize_struct("GuildMembersChunkEvent", FIELDS, GuildMembersChunkVisitor) +impl Serialize for GuildMembersChunkEvent { + fn serialize(&self, serializer: S) -> StdResult { + Self::serialize(self, serializer) // calls #[serde(remote)]-generated inherent method } } +/// Helper to deserialize `GuildRoleCreateEvent` and `GuildRoleUpdateEvent`. +#[derive(Deserialize)] +struct RoleEventHelper { + guild_id: GuildId, + role: Role, +} + /// Requires [`GatewayIntents::GUILDS`]. /// /// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#guild-role-create). @@ -365,10 +285,13 @@ pub struct GuildRoleCreateEvent { pub role: Role, } +// Manual impl needed to insert guild_id field in Role impl<'de> Deserialize<'de> for GuildRoleCreateEvent { fn deserialize>(deserializer: D) -> StdResult { + let mut event = RoleEventHelper::deserialize(deserializer)?; + event.role.guild_id = event.guild_id; Ok(Self { - role: roles::deserialize_event(deserializer)?, + role: event.role, }) } } @@ -392,10 +315,13 @@ pub struct GuildRoleUpdateEvent { pub role: Role, } +// Manual impl needed to insert guild_id field in Role impl<'de> Deserialize<'de> for GuildRoleUpdateEvent { fn deserialize>(deserializer: D) -> StdResult { + let mut event = RoleEventHelper::deserialize(deserializer)?; + event.role.guild_id = event.guild_id; Ok(Self { - role: roles::deserialize_event(deserializer)?, + role: event.role, }) } } @@ -884,6 +810,7 @@ pub enum GatewayEvent { HeartbeatAck, } +// Manual impl needed to emulate integer enum tags impl<'de> Deserialize<'de> for GatewayEvent { fn deserialize>(deserializer: D) -> StdResult { let mut map = JsonMap::deserialize(deserializer)?; diff --git a/src/model/guild/audit_log/mod.rs b/src/model/guild/audit_log/mod.rs index 842334b0edb..8478a9d7263 100644 --- a/src/model/guild/audit_log/mod.rs +++ b/src/model/guild/audit_log/mod.rs @@ -84,6 +84,7 @@ impl Action { } } +// Manual impl needed to emulate integer enum tags impl<'de> Deserialize<'de> for Action { fn deserialize>(deserializer: D) -> StdResult { let value = u8::deserialize(deserializer)?; diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs index cd2ecb53203..3749e9918b0 100644 --- a/src/model/guild/member.rs +++ b/src/model/guild/member.rs @@ -25,6 +25,7 @@ pub struct Member { /// Indicator of whether the member can hear in voice channels. pub deaf: bool, /// The unique Id of the guild that the member is a part of. + #[serde(default)] pub guild_id: GuildId, /// Timestamp representing the date when the member joined. pub joined_at: Option, diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index 327321be74b..e2931700989 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -1,4 +1,4 @@ -use serde::de::Error as DeError; +use serde::Serialize; #[cfg(feature = "cache")] use tracing::{error, warn}; @@ -24,26 +24,19 @@ use crate::client::bridge::gateway::ShardMessenger; use crate::collector::{MessageCollector, ReactionCollector}; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http}; -use crate::json::prelude::*; #[cfg(feature = "model")] use crate::model::application::command::{Command, CommandPermission}; #[cfg(feature = "model")] use crate::model::guild::automod::Rule; use crate::model::prelude::*; -use crate::model::utils::{ - add_guild_id_to_map, - emojis, - remove_from_map, - remove_from_map_opt, - roles, - stickers, -}; +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). -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(remote = "Self")] #[non_exhaustive] pub struct PartialGuild { /// Application ID of the guild creator if it is bot-created. @@ -1561,94 +1554,23 @@ impl PartialGuild { } } +// Manual impl needed to insert guild_id into Role's impl<'de> Deserialize<'de> for PartialGuild { fn deserialize>(deserializer: D) -> StdResult { - let mut map = JsonMap::deserialize(deserializer)?; - - let id = remove_from_map(&mut map, "id")?; - - add_guild_id_to_map(&mut map, "roles", id); - - let emojis = map - .remove("emojis") - .ok_or_else(|| DeError::custom("expected guild emojis")) - .and_then(emojis::deserialize) - .map_err(DeError::custom)?; - - let roles = map - .remove("roles") - .ok_or_else(|| DeError::custom("expected guild roles")) - .and_then(roles::deserialize) - .map_err(DeError::custom)?; - - let premium_subscription_count = match map.remove("premium_subscription_count") { - #[cfg(not(feature = "simd-json"))] - Some(Value::Null) | None => 0, - #[cfg(feature = "simd-json")] - Some(Value::Static(StaticNode::Null)) | None => 0, - Some(v) => u64::deserialize(v).map_err(DeError::custom)?, - }; + let mut guild = Self::deserialize(deserializer)?; // calls #[serde(remote)]-generated inherent method + guild.roles.values_mut().for_each(|r| r.guild_id = guild.id); + Ok(guild) + } +} - let stickers = map - .remove("stickers") - .ok_or_else(|| DeError::custom("expected guild stickers")) - .and_then(stickers::deserialize) - .map_err(DeError::custom)?; - - Ok(Self { - id, - emojis, - roles, - premium_subscription_count, - stickers, - afk_channel_id: remove_from_map_opt(&mut map, "afk_channel_id")?.flatten(), - afk_timeout: remove_from_map(&mut map, "afk_timeout")?, - application_id: remove_from_map_opt(&mut map, "application_id")?.flatten(), - default_message_notifications: remove_from_map( - &mut map, - "default_message_notifications", - )?, - features: remove_from_map(&mut map, "features")?, - widget_enabled: remove_from_map_opt(&mut map, "widget_enabled")?.flatten(), - widget_channel_id: remove_from_map_opt(&mut map, "widget_channel_id")?.flatten(), - icon: remove_from_map_opt(&mut map, "icon")?.flatten(), - mfa_level: remove_from_map(&mut map, "mfa_level")?, - name: remove_from_map(&mut map, "name")?, - owner_id: remove_from_map(&mut map, "owner_id")?, - owner: remove_from_map_opt(&mut map, "owner")?.unwrap_or_default(), - splash: remove_from_map_opt(&mut map, "splash")?.flatten(), - discovery_splash: remove_from_map_opt(&mut map, "discovery_splash")?.flatten(), - system_channel_id: remove_from_map_opt(&mut map, "system_channel_id")?.flatten(), - system_channel_flags: remove_from_map(&mut map, "system_channel_flags")?, - rules_channel_id: remove_from_map_opt(&mut map, "rules_channel_id")?.flatten(), - public_updates_channel_id: remove_from_map_opt(&mut map, "public_updates_channel_id")? - .flatten(), - verification_level: remove_from_map(&mut map, "verification_level")?, - description: remove_from_map_opt(&mut map, "description")?.flatten(), - premium_tier: remove_from_map_opt(&mut map, "premium_tier")?.unwrap_or_default(), - banner: remove_from_map_opt(&mut map, "banner")?.flatten(), - vanity_url_code: remove_from_map_opt(&mut map, "vanity_url_code")?.flatten(), - welcome_screen: remove_from_map_opt(&mut map, "welcome_screen")?, - approximate_member_count: remove_from_map_opt(&mut map, "approximate_member_count")?, - approximate_presence_count: remove_from_map_opt( - &mut map, - "approximate_presence_count", - )?, - nsfw_level: remove_from_map(&mut map, "nsfw_level")?, - max_video_channel_users: remove_from_map(&mut map, "max_video_channel_users")?, - max_presences: remove_from_map_opt(&mut map, "max_presences")?.flatten(), - max_members: remove_from_map_opt(&mut map, "max_members")?.flatten(), - permissions: remove_from_map_opt(&mut map, "permissions")?.unwrap_or_default(), - }) +impl Serialize for PartialGuild { + fn serialize(&self, serializer: S) -> StdResult { + Self::serialize(self, serializer) // calls #[serde(remote)]-generated inherent method } } impl From for PartialGuild { /// Converts this [`Guild`] instance into a [`PartialGuild`] - /// - /// [`PartialGuild`] is not a strict subset and contains some data specific to the current user - /// that [`Guild`] does not contain. Therefore, this method needs access to cache and HTTP to - /// generate the missing data fn from(guild: Guild) -> Self { Self { application_id: guild.application_id, diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs index a5934823701..6eb24db02de 100644 --- a/src/model/guild/role.rs +++ b/src/model/guild/role.rs @@ -32,6 +32,7 @@ pub struct Role { /// The Id of the role. Can be used to calculate the role's creation date. pub id: RoleId, /// The Id of the Guild the Role is in. + #[serde(default)] pub guild_id: GuildId, /// The colour of the role. #[serde(rename = "color")] @@ -78,46 +79,6 @@ pub struct Role { pub unicode_emoji: Option, } -/// Helper for deserialization without a `GuildId` but then later updated to the correct `GuildId`. -/// -/// The only difference to `Role` is `guild_id` is wrapped in `Option`. -#[derive(Deserialize)] -pub(crate) struct InterimRole { - pub id: RoleId, - #[serde(default)] - pub guild_id: Option, - #[serde(rename = "color")] - pub colour: Colour, - pub hoist: bool, - pub managed: bool, - #[serde(default)] - pub mentionable: bool, - pub name: String, - pub permissions: Permissions, - pub position: u32, - #[serde(default)] - pub tags: RoleTags, -} - -impl From for Role { - fn from(r: InterimRole) -> Self { - Self { - id: r.id, - guild_id: r.guild_id.expect("GuildID was not set on InterimRole"), - colour: r.colour, - hoist: r.hoist, - managed: r.managed, - mentionable: r.mentionable, - name: r.name, - permissions: r.permissions, - position: r.position, - tags: r.tags, - icon: None, - unicode_emoji: None, - } - } -} - #[cfg(feature = "model")] impl Role { /// Deletes the role. diff --git a/src/model/guild/welcome_screen.rs b/src/model/guild/welcome_screen.rs index b059d33fb73..e73162c2a2e 100644 --- a/src/model/guild/welcome_screen.rs +++ b/src/model/guild/welcome_screen.rs @@ -29,6 +29,7 @@ pub struct GuildWelcomeChannel { pub emoji: Option, } +// Manual impl needed to deserialize emoji_id and emoji_name into a single GuildWelcomeChannelEmoji impl<'de> Deserialize<'de> for GuildWelcomeChannel { fn deserialize>(deserializer: D) -> Result { #[derive(Deserialize)] diff --git a/src/model/permissions.rs b/src/model/permissions.rs index 1a7a7cda7bf..a4c487c4c95 100644 --- a/src/model/permissions.rs +++ b/src/model/permissions.rs @@ -788,6 +788,7 @@ impl Permissions { } } +// Manual impl needed because Permissions are sent as a stringified integer impl<'de> Deserialize<'de> for Permissions { fn deserialize>(deserializer: D) -> Result { let permissions_str = String::deserialize(deserializer)?; diff --git a/src/model/utils.rs b/src/model/utils.rs index 78450805b8b..6d42f2c5174 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -46,16 +46,6 @@ where remove_from_map_opt(map, key)?.ok_or_else(|| serde::de::Error::missing_field(key)) } -pub fn add_guild_id_to_map(map: &mut JsonMap, key: &str, id: GuildId) { - if let Some(array) = map.get_mut(key).and_then(Value::as_array_mut) { - for value in array { - if let Some(item) = value.as_object_mut() { - item.insert("guild_id".to_string(), id.get().into()); - } - } - } -} - /// Used with `#[serde(with = "emojis")]` pub mod emojis { use std::collections::HashMap; @@ -165,11 +155,11 @@ pub mod private_channels { pub mod roles { use std::collections::HashMap; - use serde::{Deserialize, Deserializer}; + use serde::Deserializer; use super::SequenceToMapVisitor; - use crate::model::guild::{InterimRole, Role}; - use crate::model::id::{GuildId, RoleId}; + use crate::model::guild::Role; + use crate::model::id::RoleId; pub fn deserialize<'de, D: Deserializer<'de>>( deserializer: D, @@ -178,23 +168,6 @@ pub mod roles { } pub use super::serialize_map_values as serialize; - - /// Helper to deserialize `GuildRoleCreateEvent` and `GuildRoleUpdateEvent`. - pub fn deserialize_event<'de, D: Deserializer<'de>>(deserializer: D) -> Result { - #[derive(Deserialize)] - struct Event { - guild_id: GuildId, - role: InterimRole, - } - - let Event { - guild_id, - mut role, - } = Event::deserialize(deserializer)?; - - role.guild_id = Some(guild_id); - Ok(Role::from(role)) - } } /// Used with `#[serde(with = "stickers")]` diff --git a/src/model/voice.rs b/src/model/voice.rs index 23e1e695077..1a8e7fa3892 100644 --- a/src/model/voice.rs +++ b/src/model/voice.rs @@ -1,10 +1,9 @@ //! Representations of voice information. -use std::fmt; - use serde::de::{Deserialize, Deserializer}; +use serde::Serialize; -use crate::model::guild::{InterimMember, Member}; +use crate::model::guild::Member; use crate::model::id::{ChannelId, GuildId, UserId}; use crate::model::Timestamp; @@ -29,7 +28,8 @@ pub struct VoiceRegion { /// A user's state within a voice channel. /// /// [Discord docs](https://discord.com/developers/docs/resources/voice#voice-state-object). -#[derive(Clone, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(remote = "Self")] #[non_exhaustive] pub struct VoiceState { pub channel_id: Option, @@ -51,67 +51,19 @@ pub struct VoiceState { pub request_to_speak_timestamp: Option, } -impl fmt::Debug for VoiceState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("VoiceState") - .field("channel_id", &self.channel_id) - .field("deaf", &self.deaf) - .field("guild_id", &self.guild_id) - .field("member", &self.member) - .field("mute", &self.mute) - .field("self_deaf", &self.self_deaf) - .field("self_mute", &self.self_mute) - .field("self_stream", &self.self_stream) - .field("self_video", &self.self_video) - .field("session_id", &self.session_id) - .field("suppress", &self.suppress) - .field("user_id", &self.user_id) - .field("request_to_speak_timestamp", &self.request_to_speak_timestamp) - .finish() - } -} - +// Manual impl needed to insert guild_id into Member impl<'de> Deserialize<'de> for VoiceState { fn deserialize>(deserializer: D) -> Result { - #[derive(Deserialize)] - struct InterimVoiceState { - channel_id: Option, - deaf: bool, - guild_id: Option, - member: Option, - mute: bool, - self_deaf: bool, - self_mute: bool, - self_stream: Option, - self_video: bool, - session_id: String, - suppress: bool, - token: Option, - user_id: UserId, - request_to_speak_timestamp: Option, - } - - let mut state = InterimVoiceState::deserialize(deserializer)?; - + let mut state = Self::deserialize(deserializer)?; // calls #[serde(remote)]-generated inherent method if let (Some(guild_id), Some(member)) = (state.guild_id, state.member.as_mut()) { - member.guild_id = Some(guild_id); + member.guild_id = guild_id; } + Ok(state) + } +} - Ok(VoiceState { - channel_id: state.channel_id, - deaf: state.deaf, - guild_id: state.guild_id, - member: state.member.map(Member::from), - mute: state.mute, - self_deaf: state.self_deaf, - self_mute: state.self_mute, - self_stream: state.self_stream, - self_video: state.self_video, - session_id: state.session_id, - suppress: state.suppress, - token: state.token, - user_id: state.user_id, - request_to_speak_timestamp: state.request_to_speak_timestamp, - }) +impl Serialize for VoiceState { + fn serialize(&self, serializer: S) -> Result { + Self::serialize(self, serializer) // calls #[serde(remote)]-generated inherent method } } diff --git a/voice-model/src/speaking_state.rs b/voice-model/src/speaking_state.rs index bfdaa07407e..e1065f2a45d 100644 --- a/voice-model/src/speaking_state.rs +++ b/voice-model/src/speaking_state.rs @@ -31,6 +31,8 @@ impl SpeakingState { } } +// Manual impl needed because object is sent as a flags integer +// (could maybe just put `#[serde(transparent)]` on the type?) impl<'de> Deserialize<'de> for SpeakingState { fn deserialize>(deserializer: D) -> Result { Ok(Self::from_bits_truncate(u8::deserialize(deserializer)?))