1use crate::json::Value;
2use crate::model::channel::PermissionOverwrite;
3use crate::model::guild::automod::{Action, EventType, TriggerMetadata, TriggerType};
4use crate::model::guild::{
5 AfkTimeout,
6 DefaultMessageNotificationLevel,
7 ExplicitContentFilter,
8 MfaLevel,
9 SystemChannelFlags,
10 VerificationLevel,
11};
12use crate::model::id::{ApplicationId, ChannelId, GenericId, GuildId, RoleId, UserId};
13use crate::model::misc::ImageHash;
14use crate::model::sticker::StickerFormatType;
15use crate::model::utils::StrOrInt;
16use crate::model::{Permissions, Timestamp};
17
18#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
19#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
20#[non_exhaustive]
21pub struct AffectedRole {
22 pub id: RoleId,
23 pub name: String,
24}
25
26#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
27#[derive(Debug, PartialEq, Eq, Serialize, Clone)]
28#[serde(untagged)]
29#[non_exhaustive]
30pub enum EntityType {
31 Int(u64),
32 Str(String),
33}
34
35impl<'de> serde::Deserialize<'de> for EntityType {
36 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
37 Ok(StrOrInt::deserialize(deserializer)?.into_enum(Self::Str, Self::Int))
38 }
39}
40
41macro_rules! generate_change {
42 ( $(
43 $( #[doc = $doc:literal] )?
44 $key:literal => $name:ident ($type:ty),
45 )* ) => {
46 #[cfg_attr(not(feature = "simd_json"), allow(clippy::derive_partial_eq_without_eq))]
47 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
48 #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
49 #[cfg_attr(not(feature = "simd_json"), derive(Eq))]
51 #[non_exhaustive]
52 #[serde(tag = "key")]
53 #[serde(rename_all = "snake_case")]
54 pub enum Change {
55 $(
56 $( #[doc = $doc] )?
57 $name {
58 #[serde(skip_serializing_if = "Option::is_none")]
59 #[serde(rename = "old_value")]
60 old: Option<$type>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 #[serde(rename = "new_value")]
63 new: Option<$type>,
64 },
65 )*
66
67 #[serde(rename = "$add")]
71 RolesAdded {
72 #[serde(skip_serializing_if = "Option::is_none")]
73 #[serde(rename = "old_value")]
74 old: Option<Vec<AffectedRole>>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 #[serde(rename = "new_value")]
77 new: Option<Vec<AffectedRole>>,
78 },
79 #[serde(rename = "$remove")]
81 RolesRemove {
82 #[serde(skip_serializing_if = "Option::is_none")]
83 #[serde(rename = "old_value")]
84 old: Option<Vec<AffectedRole>>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 #[serde(rename = "new_value")]
87 new: Option<Vec<AffectedRole>>,
88 },
89
90 Other {
92 name: String,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 #[serde(rename = "old_value")]
95 old_value: Option<Value>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 #[serde(rename = "new_value")]
98 new_value: Option<Value>,
99 },
100
101 #[serde(other)]
103 Unknown
104 }
105
106 impl Change {
107 #[must_use]
108 pub fn key(&self) -> &str {
109 match self {
110 $( Self::$name { .. } => $key, )*
111 Self::RolesAdded { .. } => "$add",
112 Self::RolesRemove { .. } => "$remove",
113 Self::Other { name, .. } => name,
114 Self::Unknown => "unknown",
115 }
116 }
117 }
118 };
119}
120
121generate_change! {
122 "actions" => Actions(Vec<Action>),
123 "afk_channel_id" => AfkChannelId(ChannelId),
125 "afk_timeout" => AfkTimeout(AfkTimeout),
127 "allow" => Allow(Permissions),
129 "application_id" => ApplicationId(ApplicationId),
131 "archived" => Archived(bool),
133 "asset" => Asset(String),
134 "auto_archive_duration" => AutoArchiveDuration(u16),
136 "available" => Available(bool),
138 "avatar_hash" => AvatarHash(ImageHash),
140 "banner_hash" => BannerHash(ImageHash),
142 "bitrate" => Bitrate(u32),
144 "channel_id" => ChannelId(ChannelId),
146 "code" => Code(String),
148 "color" => Color(u32),
150 "communication_disabled_until" => CommunicationDisabledUntil(Timestamp),
152 "deaf" => Deaf(bool),
154 "default_auto_archive_duration" => DefaultAutoArchiveDuration(u16),
156 "default_message_notifications" => DefaultMessageNotifications(DefaultMessageNotificationLevel),
158 "deny" => Deny(Permissions),
160 "description" => Description(String),
162 "discovery_splash_hash" => DiscoverySplashHash(ImageHash),
164 "enabled" => Enabled(bool),
165 "enable_emoticons" => EnableEmoticons(bool),
167 "entity_type" => EntityType(u64),
169 "event_type" => EventType(EventType),
170 "exempt_channels" => ExemptChannels(Vec<ChannelId>),
171 "exempt_roles" => ExemptRoles(Vec<RoleId>),
172 "expire_behavior" => ExpireBehavior(u64),
174 "expire_grace_period" => ExpireGracePeriod(u64),
176 "explicit_content_filter" => ExplicitContentFilter(ExplicitContentFilter),
178 "flags" => Flags(u64),
180 "format_type" => FormatType(StickerFormatType),
182 "guild_id" => GuildId(GuildId),
184 "hoist" => Hoist(bool),
186 "icon_hash" => IconHash(ImageHash),
188 "id" => Id(GenericId),
190 "image_hash" => ImageHash(ImageHash),
192 "invitable" => Invitable(bool),
194 "inviter_id" => InviterId(UserId),
196 "location" => Location(String),
198 "locked" => Locked(bool),
200 "max_age" => MaxAge(u32),
202 "max_uses" => MaxUses(u8),
204 "mentionable" => Mentionable(bool),
206 "mfa_level" => MfaLevel(MfaLevel),
208 "mute" => Mute(bool),
210 "name" => Name(String),
212 "nick" => Nick(String),
214 "nsfw" => Nsfw(bool),
216 "owner_id" => OwnerId(UserId),
218 "permission_overwrites" => PermissionOverwrites(Vec<PermissionOverwrite>),
220 "permissions" => Permissions(Permissions),
222 "position" => Position(u32),
224 "preferred_locale" => PreferredLocale(String),
226 "privacy_level" => PrivacyLevel(u64),
228 "prune_delete_days" => PruneDeleteDays(u64),
230 "public_updates_channel_id" => PublicUpdatesChannelId(ChannelId),
232 "rate_limit_per_user" => RateLimitPerUser(u16),
234 "region" => Region(String),
236 "rules_channel_id" => RulesChannelId(ChannelId),
238 "splash_hash" => SplashHash(ImageHash),
240 "status" => Status(u64),
242 "system_channel_flags" => SystemChannelFlags(SystemChannelFlags),
244 "system_channel_id" => SystemChannelId(ChannelId),
246 "tags" => Tags(String),
248 "temporary" => Temporary(bool),
250 "topic" => Topic(String),
252 "trigger_metadata" => TriggerMetadata(TriggerMetadata),
253 "trigger_type" => TriggerType(TriggerType),
254 "type" => Type(EntityType),
256 "unicode_emoji" => UnicodeEmoji(String),
258 "user_limit" => UserLimit(u64),
260 "uses" => Uses(u64),
262 "vanity_url_code" => VanityUrlCode(String),
264 "verification_level" => VerificationLevel(VerificationLevel),
266 "widget_channel_id" => WidgetChannelId(ChannelId),
268 "widget_enabled" => WidgetEnabled(bool),
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::json::{assert_json, json};
276
277 #[test]
278 fn afk_channel_id_variant() {
279 let value = Change::AfkChannelId {
280 old: Some(ChannelId::new(1)),
281 new: Some(ChannelId::new(2)),
282 };
283 assert_json(&value, json!({"key": "afk_channel_id", "old_value": "1", "new_value": "2"}));
284 }
285
286 #[test]
287 fn skip_serializing_if_none() {
288 let value = Change::AfkChannelId {
289 old: None,
290 new: Some(ChannelId::new(2)),
291 };
292 assert_json(&value, json!({"key": "afk_channel_id", "new_value": "2"}));
293 let value = Change::AfkChannelId {
294 old: Some(ChannelId::new(1)),
295 new: None,
296 };
297 assert_json(&value, json!({"key": "afk_channel_id", "old_value": "1"}));
298 }
299
300 #[test]
301 fn entity_type_variant() {
302 let value = Change::Type {
303 old: Some(EntityType::Int(123)),
304 new: Some(EntityType::Str("discord".into())),
305 };
306 assert_json(&value, json!({"key": "type", "old_value": 123, "new_value": "discord"}));
307 }
308
309 #[test]
310 fn permissions_variant() {
311 let value = Change::Permissions {
312 old: Some(Permissions::default()),
313 new: Some(Permissions::MANAGE_GUILD),
314 };
315 assert_json(&value, json!({"key": "permissions", "old_value": "0", "new_value": "32"}));
316 }
317
318 #[test]
319 fn system_channels() {
320 let value = Change::SystemChannelFlags {
321 old: Some(
322 SystemChannelFlags::SUPPRESS_GUILD_REMINDER_NOTIFICATIONS
323 | SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATION_REPLIES,
324 ),
325 new: Some(
326 SystemChannelFlags::SUPPRESS_GUILD_REMINDER_NOTIFICATIONS
327 | SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATION_REPLIES
328 | SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATIONS,
329 ),
330 };
331 assert_json(
332 &value,
333 json!({"key": "system_channel_flags", "old_value": 12, "new_value": 13 }),
334 );
335 }
336}