use crate::json::Value;
use crate::model::channel::PermissionOverwrite;
use crate::model::guild::automod::{Action, EventType, TriggerMetadata, TriggerType};
use crate::model::guild::{
AfkTimeout,
DefaultMessageNotificationLevel,
ExplicitContentFilter,
MfaLevel,
SystemChannelFlags,
VerificationLevel,
};
use crate::model::id::{ApplicationId, ChannelId, GenericId, GuildId, RoleId, UserId};
use crate::model::misc::ImageHash;
use crate::model::sticker::StickerFormatType;
use crate::model::utils::StrOrInt;
use crate::model::{Permissions, Timestamp};
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
#[non_exhaustive]
pub struct AffectedRole {
pub id: RoleId,
pub name: String,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Debug, PartialEq, Eq, Serialize, Clone)]
#[serde(untagged)]
#[non_exhaustive]
pub enum EntityType {
Int(u64),
Str(String),
}
impl<'de> serde::Deserialize<'de> for EntityType {
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(StrOrInt::deserialize(deserializer)?.into_enum(Self::Str, Self::Int))
}
}
macro_rules! generate_change {
( $(
$( #[doc = $doc:literal] )?
$key:literal => $name:ident ($type:ty),
)* ) => {
#[cfg_attr(not(feature = "simd_json"), allow(clippy::derive_partial_eq_without_eq))]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(not(feature = "simd_json"), derive(Eq))]
#[non_exhaustive]
#[serde(tag = "key")]
#[serde(rename_all = "snake_case")]
pub enum Change {
$(
$( #[doc = $doc] )?
$name {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "old_value")]
old: Option<$type>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "new_value")]
new: Option<$type>,
},
)*
#[serde(rename = "$add")]
RolesAdded {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "old_value")]
old: Option<Vec<AffectedRole>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "new_value")]
new: Option<Vec<AffectedRole>>,
},
#[serde(rename = "$remove")]
RolesRemove {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "old_value")]
old: Option<Vec<AffectedRole>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "new_value")]
new: Option<Vec<AffectedRole>>,
},
Other {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "old_value")]
old_value: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "new_value")]
new_value: Option<Value>,
},
#[serde(other)]
Unknown
}
impl Change {
#[must_use]
pub fn key(&self) -> &str {
match self {
$( Self::$name { .. } => $key, )*
Self::RolesAdded { .. } => "$add",
Self::RolesRemove { .. } => "$remove",
Self::Other { name, .. } => name,
Self::Unknown => "unknown",
}
}
}
};
}
generate_change! {
"actions" => Actions(Vec<Action>),
"afk_channel_id" => AfkChannelId(ChannelId),
"afk_timeout" => AfkTimeout(AfkTimeout),
"allow" => Allow(Permissions),
"application_id" => ApplicationId(ApplicationId),
"archived" => Archived(bool),
"asset" => Asset(String),
"auto_archive_duration" => AutoArchiveDuration(u16),
"available" => Available(bool),
"avatar_hash" => AvatarHash(ImageHash),
"banner_hash" => BannerHash(ImageHash),
"bitrate" => Bitrate(u32),
"channel_id" => ChannelId(ChannelId),
"code" => Code(String),
"color" => Color(u32),
"communication_disabled_until" => CommunicationDisabledUntil(Timestamp),
"deaf" => Deaf(bool),
"default_auto_archive_duration" => DefaultAutoArchiveDuration(u16),
"default_message_notifications" => DefaultMessageNotifications(DefaultMessageNotificationLevel),
"deny" => Deny(Permissions),
"description" => Description(String),
"discovery_splash_hash" => DiscoverySplashHash(ImageHash),
"enabled" => Enabled(bool),
"enable_emoticons" => EnableEmoticons(bool),
"entity_type" => EntityType(u64),
"event_type" => EventType(EventType),
"exempt_channels" => ExemptChannels(Vec<ChannelId>),
"exempt_roles" => ExemptRoles(Vec<RoleId>),
"expire_behavior" => ExpireBehavior(u64),
"expire_grace_period" => ExpireGracePeriod(u64),
"explicit_content_filter" => ExplicitContentFilter(ExplicitContentFilter),
"flags" => Flags(u64),
"format_type" => FormatType(StickerFormatType),
"guild_id" => GuildId(GuildId),
"hoist" => Hoist(bool),
"icon_hash" => IconHash(ImageHash),
"id" => Id(GenericId),
"image_hash" => ImageHash(ImageHash),
"invitable" => Invitable(bool),
"inviter_id" => InviterId(UserId),
"location" => Location(String),
"locked" => Locked(bool),
"max_age" => MaxAge(u32),
"max_uses" => MaxUses(u8),
"mentionable" => Mentionable(bool),
"mfa_level" => MfaLevel(MfaLevel),
"mute" => Mute(bool),
"name" => Name(String),
"nick" => Nick(String),
"nsfw" => Nsfw(bool),
"owner_id" => OwnerId(UserId),
"permission_overwrites" => PermissionOverwrites(Vec<PermissionOverwrite>),
"permissions" => Permissions(Permissions),
"position" => Position(u32),
"preferred_locale" => PreferredLocale(String),
"privacy_level" => PrivacyLevel(u64),
"prune_delete_days" => PruneDeleteDays(u64),
"public_updates_channel_id" => PublicUpdatesChannelId(ChannelId),
"rate_limit_per_user" => RateLimitPerUser(u16),
"region" => Region(String),
"rules_channel_id" => RulesChannelId(ChannelId),
"splash_hash" => SplashHash(ImageHash),
"status" => Status(u64),
"system_channel_flags" => SystemChannelFlags(SystemChannelFlags),
"system_channel_id" => SystemChannelId(ChannelId),
"tags" => Tags(String),
"temporary" => Temporary(bool),
"topic" => Topic(String),
"trigger_metadata" => TriggerMetadata(TriggerMetadata),
"trigger_type" => TriggerType(TriggerType),
"type" => Type(EntityType),
"unicode_emoji" => UnicodeEmoji(String),
"user_limit" => UserLimit(u64),
"uses" => Uses(u64),
"vanity_url_code" => VanityUrlCode(String),
"verification_level" => VerificationLevel(VerificationLevel),
"widget_channel_id" => WidgetChannelId(ChannelId),
"widget_enabled" => WidgetEnabled(bool),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::json::{assert_json, json};
#[test]
fn afk_channel_id_variant() {
let value = Change::AfkChannelId {
old: Some(ChannelId::new(1)),
new: Some(ChannelId::new(2)),
};
assert_json(&value, json!({"key": "afk_channel_id", "old_value": "1", "new_value": "2"}));
}
#[test]
fn skip_serializing_if_none() {
let value = Change::AfkChannelId {
old: None,
new: Some(ChannelId::new(2)),
};
assert_json(&value, json!({"key": "afk_channel_id", "new_value": "2"}));
let value = Change::AfkChannelId {
old: Some(ChannelId::new(1)),
new: None,
};
assert_json(&value, json!({"key": "afk_channel_id", "old_value": "1"}));
}
#[test]
fn entity_type_variant() {
let value = Change::Type {
old: Some(EntityType::Int(123)),
new: Some(EntityType::Str("discord".into())),
};
assert_json(&value, json!({"key": "type", "old_value": 123, "new_value": "discord"}));
}
#[test]
fn permissions_variant() {
let value = Change::Permissions {
old: Some(Permissions::default()),
new: Some(Permissions::MANAGE_GUILD),
};
assert_json(&value, json!({"key": "permissions", "old_value": "0", "new_value": "32"}));
}
#[test]
fn system_channels() {
let value = Change::SystemChannelFlags {
old: Some(
SystemChannelFlags::SUPPRESS_GUILD_REMINDER_NOTIFICATIONS
| SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATION_REPLIES,
),
new: Some(
SystemChannelFlags::SUPPRESS_GUILD_REMINDER_NOTIFICATIONS
| SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATION_REPLIES
| SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATIONS,
),
};
assert_json(
&value,
json!({"key": "system_channel_flags", "old_value": 12, "new_value": 13 }),
);
}
}