Skip to main content

twilight_model/application/interaction/
mod.rs

1//! Used when receiving interactions through gateway or webhooks.
2//!
3//! See [Discord Docs/Receiving and Responding].
4//!
5//! [Discord Docs/Receiving and Responding]: https://discord.com/developers/docs/interactions/receiving-and-responding
6
7pub mod application_command;
8pub mod callback;
9pub mod message_component;
10pub mod modal;
11
12mod context_type;
13mod interaction_type;
14mod metadata;
15mod resolved;
16
17pub use self::{
18    context_type::InteractionContextType,
19    interaction_type::InteractionType,
20    metadata::InteractionMetadata,
21    resolved::{InteractionChannel, InteractionDataResolved, InteractionMember},
22};
23
24use self::{
25    application_command::CommandData, message_component::MessageComponentInteractionData,
26    modal::ModalInteractionData,
27};
28use crate::{
29    channel::{Channel, Message},
30    guild::{GuildFeature, PartialMember, Permissions},
31    id::{
32        AnonymizableId, Id,
33        marker::{ApplicationMarker, ChannelMarker, GuildMarker, InteractionMarker, UserMarker},
34    },
35    oauth::ApplicationIntegrationMap,
36    user::User,
37};
38use serde::{
39    Deserialize, Deserializer, Serialize,
40    de::{Error as DeError, IgnoredAny, MapAccess, Visitor},
41};
42use serde_value::{DeserializerError, Value};
43use std::fmt::{Formatter, Result as FmtResult};
44
45use super::monetization::Entitlement;
46
47/// Payload received when a user executes an interaction.
48///
49/// See [Discord Docs/Interaction Object].
50///
51/// [Discord Docs/Interaction Object]: https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure
52#[derive(Clone, Debug, PartialEq, Serialize)]
53pub struct Interaction {
54    /// App's permissions in the channel the interaction was sent from.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub app_permissions: Option<Permissions>,
57    /// ID of the associated application.
58    pub application_id: Id<ApplicationMarker>,
59    /// Maximum file size in bytes the user may upload in this interaction.
60    ///
61    /// Computed by Discord per-interaction as the maximum of the user's
62    /// upload cap (e.g. Nitro) and the guild's boost-tier cap.
63    pub attachment_size_limit: u64,
64    /// Mapping of installation contexts that the interaction was
65    /// authorized for to related user or guild IDs.
66    pub authorizing_integration_owners:
67        ApplicationIntegrationMap<AnonymizableId<GuildMarker>, Id<UserMarker>>,
68    /// The channel the interaction was invoked in.
69    ///
70    /// Present on all interactions types, except [`Ping`].
71    ///
72    /// [`Ping`]: InteractionType::Ping
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub channel: Option<Channel>,
75    /// ID of the channel the interaction was invoked in.
76    ///
77    /// Present on all interactions types, except [`Ping`].
78    ///
79    /// [`Ping`]: InteractionType::Ping
80    #[serde(skip_serializing_if = "Option::is_none")]
81    #[deprecated(
82        note = "channel_id is deprecated in the discord API and will no be sent in the future, users should use the channel field instead."
83    )]
84    pub channel_id: Option<Id<ChannelMarker>>,
85    /// Context where the interaction was triggered from.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub context: Option<InteractionContextType>,
88    /// Data from the interaction.
89    ///
90    /// This field present on [`ApplicationCommand`], [`MessageComponent`],
91    /// [`ApplicationCommandAutocomplete`] and [`ModalSubmit`] interactions.
92    /// The inner enum variant matches the interaction type.
93    ///
94    /// [`ApplicationCommand`]: InteractionType::ApplicationCommand
95    /// [`MessageComponent`]: InteractionType::MessageComponent
96    /// [`ApplicationCommandAutocomplete`]: InteractionType::ApplicationCommandAutocomplete
97    /// [`ModalSubmit`]: InteractionType::ModalSubmit
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub data: Option<InteractionData>,
100    /// For monetized apps, any entitlements for the invoking user, representing access to premium SKUs
101    pub entitlements: Vec<Entitlement>,
102    /// Guild that the interaction was sent from.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub guild: Option<InteractionPartialGuild>,
105    /// ID of the guild the interaction was invoked in.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub guild_id: Option<Id<GuildMarker>>,
108    /// Guild’s preferred locale.
109    ///
110    /// Present when the interaction is invoked in a guild.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub guild_locale: Option<String>,
113    /// ID of the interaction.
114    pub id: Id<InteractionMarker>,
115    /// Type of interaction.
116    #[serde(rename = "type")]
117    pub kind: InteractionType,
118    /// Selected language of the user who invoked the interaction.
119    ///
120    /// Present on all interactions types, except [`Ping`].
121    ///
122    /// [`Ping`]: InteractionType::Ping
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub locale: Option<String>,
125    /// Member that invoked the interaction.
126    ///
127    /// Present when the interaction is invoked in a guild.
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub member: Option<PartialMember>,
130    /// Message attached to the interaction.
131    ///
132    /// Present on [`MessageComponent`] interactions.
133    ///
134    /// [`MessageComponent`]: InteractionType::MessageComponent
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub message: Option<Message>,
137    /// Token for responding to the interaction.
138    pub token: String,
139    /// User that invoked the interaction.
140    ///
141    /// Present when the interaction is invoked in a direct message.
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub user: Option<User>,
144}
145
146impl Interaction {
147    /// ID of the user that invoked the interaction.
148    ///
149    /// This will first check for the [`member`]'s
150    /// [`user`][`PartialMember::user`]'s ID and then, if not present, check the
151    /// [`user`]'s ID.
152    ///
153    /// [`member`]: Self::member
154    /// [`user`]: Self::user
155    pub const fn author_id(&self) -> Option<Id<UserMarker>> {
156        if let Some(user) = self.author() {
157            Some(user.id)
158        } else {
159            None
160        }
161    }
162
163    /// The user that invoked the interaction.
164    ///
165    /// This will first check for the [`member`]'s
166    /// [`user`][`PartialMember::user`] and then, if not present, check the
167    /// [`user`].
168    ///
169    /// [`member`]: Self::member
170    /// [`user`]: Self::user
171    pub const fn author(&self) -> Option<&User> {
172        match self.member.as_ref() {
173            Some(member) if member.user.is_some() => member.user.as_ref(),
174            _ => self.user.as_ref(),
175        }
176    }
177
178    /// The user that invoked the interaction.
179    ///
180    /// This will first check for the [`member`]'s
181    /// [`user`][`PartialMember::user`] and then, if not present, check the
182    /// [`user`].
183    ///
184    /// [`member`]: Self::member
185    /// [`user`]: Self::user
186    pub fn into_author(self) -> Option<User> {
187        match self.member {
188            Some(member) if member.user.is_some() => member.user,
189            _ => self.user,
190        }
191    }
192
193    /// Whether the interaction was invoked in a DM.
194    pub const fn is_dm(&self) -> bool {
195        self.user.is_some()
196    }
197
198    /// Whether the interaction was invoked in a guild.
199    pub const fn is_guild(&self) -> bool {
200        self.member.is_some()
201    }
202}
203
204impl<'de> Deserialize<'de> for Interaction {
205    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
206        deserializer.deserialize_map(InteractionVisitor)
207    }
208}
209
210#[derive(Debug, Deserialize)]
211#[serde(field_identifier, rename_all = "snake_case")]
212enum InteractionField {
213    AppPermissions,
214    ApplicationId,
215    Context,
216    Channel,
217    ChannelId,
218    Data,
219    Entitlements,
220    Guild,
221    GuildId,
222    GuildLocale,
223    Id,
224    Locale,
225    Member,
226    Message,
227    Token,
228    Type,
229    User,
230    Version,
231    AuthorizingIntegrationOwners,
232    AttachmentSizeLimit,
233}
234
235struct InteractionVisitor;
236
237impl<'de> Visitor<'de> for InteractionVisitor {
238    type Value = Interaction;
239
240    fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
241        f.write_str("enum Interaction")
242    }
243
244    #[allow(clippy::too_many_lines, deprecated)]
245    fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Self::Value, V::Error> {
246        let mut app_permissions: Option<Permissions> = None;
247        let mut application_id: Option<Id<ApplicationMarker>> = None;
248        let mut channel: Option<Channel> = None;
249        let mut channel_id: Option<Id<ChannelMarker>> = None;
250        let mut context: Option<InteractionContextType> = None;
251        let mut data: Option<Value> = None;
252        let mut entitlements: Option<Vec<Entitlement>> = None;
253        let mut guild: Option<InteractionPartialGuild> = None;
254        let mut guild_id: Option<Id<GuildMarker>> = None;
255        let mut guild_locale: Option<String> = None;
256        let mut id: Option<Id<InteractionMarker>> = None;
257        let mut kind: Option<InteractionType> = None;
258        let mut locale: Option<String> = None;
259        let mut member: Option<PartialMember> = None;
260        let mut message: Option<Message> = None;
261        let mut token: Option<String> = None;
262        let mut user: Option<User> = None;
263        let mut authorizing_integration_owners: Option<
264            ApplicationIntegrationMap<AnonymizableId<GuildMarker>, Id<UserMarker>>,
265        > = None;
266        let mut attachment_size_limit: Option<u64> = None;
267
268        loop {
269            let key = match map.next_key() {
270                Ok(Some(key)) => key,
271                Ok(None) => break,
272                Err(_) => {
273                    map.next_value::<IgnoredAny>()?;
274
275                    continue;
276                }
277            };
278
279            match key {
280                InteractionField::AppPermissions => {
281                    if app_permissions.is_some() {
282                        return Err(DeError::duplicate_field("app_permissions"));
283                    }
284
285                    app_permissions = map.next_value()?;
286                }
287                InteractionField::ApplicationId => {
288                    if application_id.is_some() {
289                        return Err(DeError::duplicate_field("application_id"));
290                    }
291
292                    application_id = Some(map.next_value()?);
293                }
294                InteractionField::Context => {
295                    if context.is_some() {
296                        return Err(DeError::duplicate_field("context"));
297                    }
298
299                    context = map.next_value()?;
300                }
301                InteractionField::Channel => {
302                    if channel.is_some() {
303                        return Err(DeError::duplicate_field("channel"));
304                    }
305
306                    channel = map.next_value()?;
307                }
308                InteractionField::ChannelId => {
309                    if channel_id.is_some() {
310                        return Err(DeError::duplicate_field("channel_id"));
311                    }
312
313                    channel_id = map.next_value()?;
314                }
315                InteractionField::Data => {
316                    if data.is_some() {
317                        return Err(DeError::duplicate_field("data"));
318                    }
319
320                    data = map.next_value()?;
321                }
322                InteractionField::Entitlements => {
323                    if entitlements.is_some() {
324                        return Err(DeError::duplicate_field("entitlements"));
325                    }
326
327                    entitlements = map.next_value()?;
328                }
329                InteractionField::Guild => {
330                    if guild.is_some() {
331                        return Err(DeError::duplicate_field("guild"));
332                    }
333
334                    guild = map.next_value()?;
335                }
336                InteractionField::GuildId => {
337                    if guild_id.is_some() {
338                        return Err(DeError::duplicate_field("guild_id"));
339                    }
340
341                    guild_id = map.next_value()?;
342                }
343                InteractionField::GuildLocale => {
344                    if guild_locale.is_some() {
345                        return Err(DeError::duplicate_field("guild_locale"));
346                    }
347
348                    guild_locale = map.next_value()?;
349                }
350                InteractionField::Id => {
351                    if id.is_some() {
352                        return Err(DeError::duplicate_field("id"));
353                    }
354
355                    id = Some(map.next_value()?);
356                }
357                InteractionField::Locale => {
358                    if locale.is_some() {
359                        return Err(DeError::duplicate_field("locale"));
360                    }
361
362                    locale = map.next_value()?;
363                }
364                InteractionField::Member => {
365                    if member.is_some() {
366                        return Err(DeError::duplicate_field("member"));
367                    }
368
369                    member = map.next_value()?;
370                }
371                InteractionField::Message => {
372                    if message.is_some() {
373                        return Err(DeError::duplicate_field("message"));
374                    }
375
376                    message = map.next_value()?;
377                }
378                InteractionField::Token => {
379                    if token.is_some() {
380                        return Err(DeError::duplicate_field("token"));
381                    }
382
383                    token = Some(map.next_value()?);
384                }
385                InteractionField::Type => {
386                    if kind.is_some() {
387                        return Err(DeError::duplicate_field("kind"));
388                    }
389
390                    kind = Some(map.next_value()?);
391                }
392                InteractionField::User => {
393                    if user.is_some() {
394                        return Err(DeError::duplicate_field("user"));
395                    }
396
397                    user = map.next_value()?;
398                }
399                InteractionField::Version => {
400                    // Ignoring the version field.
401                    map.next_value::<IgnoredAny>()?;
402                }
403                InteractionField::AuthorizingIntegrationOwners => {
404                    if authorizing_integration_owners.is_some() {
405                        return Err(DeError::duplicate_field("authorizing_integration_owners"));
406                    }
407
408                    authorizing_integration_owners = map.next_value()?;
409                }
410                InteractionField::AttachmentSizeLimit => {
411                    if attachment_size_limit.is_some() {
412                        return Err(DeError::duplicate_field("attachment_size_limit"));
413                    }
414
415                    attachment_size_limit = map.next_value()?;
416                }
417            }
418        }
419
420        let application_id =
421            application_id.ok_or_else(|| DeError::missing_field("application_id"))?;
422        let attachment_size_limit =
423            attachment_size_limit.ok_or_else(|| DeError::missing_field("attachment_size_limit"))?;
424        let authorizing_integration_owners = authorizing_integration_owners
425            .ok_or_else(|| DeError::missing_field("authorizing_integration_owners"))?;
426        let id = id.ok_or_else(|| DeError::missing_field("id"))?;
427        let token = token.ok_or_else(|| DeError::missing_field("token"))?;
428        let kind = kind.ok_or_else(|| DeError::missing_field("kind"))?;
429
430        let data = match kind {
431            InteractionType::Ping => None,
432            InteractionType::ApplicationCommand => {
433                let data = data
434                    .ok_or_else(|| DeError::missing_field("data"))?
435                    .deserialize_into()
436                    .map_err(DeserializerError::into_error)?;
437
438                Some(InteractionData::ApplicationCommand(data))
439            }
440            InteractionType::MessageComponent => {
441                let data = data
442                    .ok_or_else(|| DeError::missing_field("data"))?
443                    .deserialize_into()
444                    .map_err(DeserializerError::into_error)?;
445
446                Some(InteractionData::MessageComponent(data))
447            }
448            InteractionType::ApplicationCommandAutocomplete => {
449                let data = data
450                    .ok_or_else(|| DeError::missing_field("data"))?
451                    .deserialize_into()
452                    .map_err(DeserializerError::into_error)?;
453
454                Some(InteractionData::ApplicationCommand(data))
455            }
456            InteractionType::ModalSubmit => {
457                let data = data
458                    .ok_or_else(|| DeError::missing_field("data"))?
459                    .deserialize_into()
460                    .map_err(DeserializerError::into_error)?;
461
462                Some(InteractionData::ModalSubmit(data))
463            }
464        };
465
466        let entitlements = entitlements.unwrap_or_default();
467
468        Ok(Self::Value {
469            app_permissions,
470            application_id,
471            attachment_size_limit,
472            authorizing_integration_owners,
473            channel,
474            channel_id,
475            context,
476            data,
477            entitlements,
478            guild,
479            guild_id,
480            guild_locale,
481            id,
482            kind,
483            locale,
484            member,
485            message,
486            token,
487            user,
488        })
489    }
490}
491
492/// Additional [`Interaction`] data, such as the invoking user.
493#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
494#[non_exhaustive]
495#[serde(untagged)]
496pub enum InteractionData {
497    /// Data received for the [`ApplicationCommand`] and [`ApplicationCommandAutocomplete`]
498    /// interaction types.
499    ///
500    /// [`ApplicationCommand`]: InteractionType::ApplicationCommand
501    /// [`ApplicationCommandAutocomplete`]: InteractionType::ApplicationCommandAutocomplete
502    ApplicationCommand(Box<CommandData>),
503    /// Data received for the [`MessageComponent`] interaction type.
504    ///
505    /// [`MessageComponent`]: InteractionType::MessageComponent
506    MessageComponent(Box<MessageComponentInteractionData>),
507    /// Data received for the [`ModalSubmit`] interaction type.
508    ///
509    /// [`ModalSubmit`]: InteractionType::ModalSubmit
510    ModalSubmit(Box<ModalInteractionData>),
511}
512
513impl From<Box<CommandData>> for InteractionData {
514    fn from(value: Box<CommandData>) -> Self {
515        InteractionData::ApplicationCommand(value)
516    }
517}
518
519impl From<Box<MessageComponentInteractionData>> for InteractionData {
520    fn from(value: Box<MessageComponentInteractionData>) -> Self {
521        InteractionData::MessageComponent(value)
522    }
523}
524
525impl From<Box<ModalInteractionData>> for InteractionData {
526    fn from(value: Box<ModalInteractionData>) -> Self {
527        InteractionData::ModalSubmit(value)
528    }
529}
530
531impl TryFrom<InteractionData> for Box<CommandData> {
532    type Error = InteractionData;
533
534    fn try_from(value: InteractionData) -> Result<Self, Self::Error> {
535        match value {
536            InteractionData::ApplicationCommand(inner) => Ok(inner),
537            _ => Err(value),
538        }
539    }
540}
541
542impl TryFrom<InteractionData> for Box<MessageComponentInteractionData> {
543    type Error = InteractionData;
544
545    fn try_from(value: InteractionData) -> Result<Self, Self::Error> {
546        match value {
547            InteractionData::MessageComponent(inner) => Ok(inner),
548            _ => Err(value),
549        }
550    }
551}
552
553impl TryFrom<InteractionData> for Box<ModalInteractionData> {
554    type Error = InteractionData;
555
556    fn try_from(value: InteractionData) -> Result<Self, Self::Error> {
557        match value {
558            InteractionData::ModalSubmit(inner) => Ok(inner),
559            _ => Err(value),
560        }
561    }
562}
563
564/// Partial guild containing only the fields sent in the partial guild
565/// in interactions.
566///
567/// # Note that the field `locale` does not exists on the full guild
568/// object, and is only found here. See
569/// <https://github.com/discord/discord-api-docs/issues/6938> for more
570/// info.
571#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Hash)]
572pub struct InteractionPartialGuild {
573    /// Enabled guild features
574    pub features: Option<Vec<GuildFeature>>,
575    /// Id of the guild.
576    pub id: Option<Id<GuildMarker>>,
577    pub locale: Option<String>,
578}
579
580#[cfg(test)]
581mod tests {
582    use super::{
583        Interaction, InteractionData, InteractionDataResolved, InteractionMember, InteractionType,
584        application_command::{CommandData, CommandDataOption, CommandOptionValue},
585    };
586    use crate::{
587        application::{
588            command::{CommandOptionType, CommandType},
589            monetization::{EntitlementType, entitlement::Entitlement},
590        },
591        channel::Channel,
592        guild::{MemberFlags, PartialMember, Permissions},
593        id::Id,
594        oauth::ApplicationIntegrationMap,
595        test::image_hash,
596        user::User,
597        util::datetime::{Timestamp, TimestampParseError},
598    };
599    use serde_test::Token;
600    use std::{collections::HashMap, str::FromStr};
601
602    #[test]
603    #[allow(clippy::too_many_lines, deprecated)]
604    fn test_interaction_full() -> Result<(), TimestampParseError> {
605        let joined_at = Some(Timestamp::from_str("2020-01-01T00:00:00.000000+00:00")?);
606        let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
607
608        let value = Interaction {
609            app_permissions: Some(Permissions::SEND_MESSAGES),
610            application_id: Id::new(100),
611            attachment_size_limit: 8_388_608,
612            authorizing_integration_owners: ApplicationIntegrationMap {
613                guild: None,
614                user: None,
615            },
616            channel: Some(Channel {
617                bitrate: None,
618                guild_id: None,
619                id: Id::new(400),
620                kind: crate::channel::ChannelType::GuildText,
621                last_message_id: None,
622                last_pin_timestamp: None,
623                name: None,
624                nsfw: None,
625                owner_id: None,
626                parent_id: None,
627                permission_overwrites: None,
628                position: None,
629                rate_limit_per_user: None,
630                recipients: None,
631                rtc_region: None,
632                topic: None,
633                user_limit: None,
634                application_id: None,
635                applied_tags: None,
636                available_tags: None,
637                default_auto_archive_duration: None,
638                default_forum_layout: None,
639                default_reaction_emoji: None,
640                default_sort_order: None,
641                default_thread_rate_limit_per_user: None,
642                flags: None,
643                icon: None,
644                invitable: None,
645                managed: None,
646                member: None,
647                member_count: None,
648                message_count: None,
649                newly_created: None,
650                thread_metadata: None,
651                video_quality_mode: None,
652            }),
653            channel_id: Some(Id::new(200)),
654            context: None,
655            data: Some(InteractionData::ApplicationCommand(Box::new(CommandData {
656                guild_id: None,
657                id: Id::new(300),
658                kind: CommandType::ChatInput,
659                name: "command name".into(),
660                options: Vec::from([CommandDataOption {
661                    name: "member".into(),
662                    value: CommandOptionValue::User(Id::new(600)),
663                }]),
664                resolved: Some(InteractionDataResolved {
665                    attachments: HashMap::new(),
666                    channels: HashMap::new(),
667                    members: IntoIterator::into_iter([(
668                        Id::new(600),
669                        InteractionMember {
670                            avatar: None,
671                            avatar_decoration_data: None,
672                            banner: None,
673                            communication_disabled_until: None,
674                            flags,
675                            joined_at,
676                            nick: Some("nickname".into()),
677                            pending: false,
678                            permissions: Permissions::empty(),
679                            premium_since: None,
680                            roles: Vec::new(),
681                        },
682                    )])
683                    .collect(),
684                    messages: HashMap::new(),
685                    roles: HashMap::new(),
686                    users: IntoIterator::into_iter([(
687                        Id::new(600),
688                        User {
689                            accent_color: None,
690                            avatar: Some(image_hash::AVATAR),
691                            avatar_decoration: None,
692                            avatar_decoration_data: None,
693                            banner: None,
694                            bot: false,
695                            discriminator: 1111,
696                            email: None,
697                            flags: None,
698                            global_name: Some("test".into()),
699                            id: Id::new(600),
700                            locale: None,
701                            mfa_enabled: None,
702                            name: "username".into(),
703                            premium_type: None,
704                            primary_guild: None,
705                            public_flags: None,
706                            system: None,
707                            verified: None,
708                        },
709                    )])
710                    .collect(),
711                }),
712                target_id: None,
713            }))),
714            entitlements: vec![Entitlement {
715                application_id: Id::new(100),
716                consumed: Some(false),
717                deleted: false,
718                ends_at: None,
719                guild_id: None,
720                id: Id::new(200),
721                kind: EntitlementType::ApplicationSubscription,
722                sku_id: Id::new(300),
723                starts_at: None,
724                user_id: None,
725            }],
726            guild: None,
727            guild_id: Some(Id::new(400)),
728            guild_locale: Some("de".to_owned()),
729            id: Id::new(500),
730            kind: InteractionType::ApplicationCommand,
731            locale: Some("en-GB".to_owned()),
732            member: Some(PartialMember {
733                avatar: None,
734                avatar_decoration_data: None,
735                banner: None,
736                communication_disabled_until: None,
737                deaf: false,
738                flags,
739                joined_at,
740                mute: false,
741                nick: Some("nickname".into()),
742                permissions: Some(Permissions::empty()),
743                premium_since: None,
744                roles: Vec::new(),
745                user: Some(User {
746                    accent_color: None,
747                    avatar: Some(image_hash::AVATAR),
748                    avatar_decoration: None,
749                    avatar_decoration_data: None,
750                    banner: None,
751                    bot: false,
752                    discriminator: 1111,
753                    email: None,
754                    flags: None,
755                    global_name: Some("test".into()),
756                    id: Id::new(600),
757                    locale: None,
758                    mfa_enabled: None,
759                    name: "username".into(),
760                    premium_type: None,
761                    primary_guild: None,
762                    public_flags: None,
763                    system: None,
764                    verified: None,
765                }),
766            }),
767            message: None,
768            token: "interaction token".into(),
769            user: None,
770        };
771
772        // TODO: switch the `assert_tokens` see #2190
773        serde_test::assert_ser_tokens(
774            &value,
775            &[
776                Token::Struct {
777                    name: "Interaction",
778                    len: 15,
779                },
780                Token::Str("app_permissions"),
781                Token::Some,
782                Token::Str("2048"),
783                Token::Str("application_id"),
784                Token::NewtypeStruct { name: "Id" },
785                Token::Str("100"),
786                Token::Str("attachment_size_limit"),
787                Token::U64(8_388_608),
788                Token::Str("authorizing_integration_owners"),
789                Token::Struct {
790                    name: "ApplicationIntegrationMap",
791                    len: 0,
792                },
793                Token::StructEnd,
794                Token::Str("channel"),
795                Token::Some,
796                Token::Struct {
797                    name: "Channel",
798                    len: 2,
799                },
800                Token::Str("id"),
801                Token::NewtypeStruct { name: "Id" },
802                Token::Str("400"),
803                Token::Str("type"),
804                Token::U8(0),
805                Token::StructEnd,
806                Token::Str("channel_id"),
807                Token::Some,
808                Token::NewtypeStruct { name: "Id" },
809                Token::Str("200"),
810                Token::Str("data"),
811                Token::Some,
812                Token::Struct {
813                    name: "CommandData",
814                    len: 5,
815                },
816                Token::Str("id"),
817                Token::NewtypeStruct { name: "Id" },
818                Token::Str("300"),
819                Token::Str("type"),
820                Token::U8(1),
821                Token::Str("name"),
822                Token::Str("command name"),
823                Token::Str("options"),
824                Token::Seq { len: Some(1) },
825                Token::Struct {
826                    name: "CommandDataOption",
827                    len: 3,
828                },
829                Token::Str("name"),
830                Token::Str("member"),
831                Token::Str("type"),
832                Token::U8(CommandOptionType::User as u8),
833                Token::Str("value"),
834                Token::NewtypeStruct { name: "Id" },
835                Token::Str("600"),
836                Token::StructEnd,
837                Token::SeqEnd,
838                Token::Str("resolved"),
839                Token::Some,
840                Token::Struct {
841                    name: "InteractionDataResolved",
842                    len: 2,
843                },
844                Token::Str("members"),
845                Token::Map { len: Some(1) },
846                Token::NewtypeStruct { name: "Id" },
847                Token::Str("600"),
848                Token::Struct {
849                    name: "InteractionMember",
850                    len: 7,
851                },
852                Token::Str("communication_disabled_until"),
853                Token::None,
854                Token::Str("flags"),
855                Token::U64(flags.bits()),
856                Token::Str("joined_at"),
857                Token::Some,
858                Token::Str("2020-01-01T00:00:00.000000+00:00"),
859                Token::Str("nick"),
860                Token::Some,
861                Token::Str("nickname"),
862                Token::Str("pending"),
863                Token::Bool(false),
864                Token::Str("permissions"),
865                Token::Str("0"),
866                Token::Str("roles"),
867                Token::Seq { len: Some(0) },
868                Token::SeqEnd,
869                Token::StructEnd,
870                Token::MapEnd,
871                Token::Str("users"),
872                Token::Map { len: Some(1) },
873                Token::NewtypeStruct { name: "Id" },
874                Token::Str("600"),
875                Token::Struct {
876                    name: "User",
877                    len: 10,
878                },
879                Token::Str("accent_color"),
880                Token::None,
881                Token::Str("avatar"),
882                Token::Some,
883                Token::Str(image_hash::AVATAR_INPUT),
884                Token::Str("avatar_decoration"),
885                Token::None,
886                Token::Str("avatar_decoration_data"),
887                Token::None,
888                Token::Str("banner"),
889                Token::None,
890                Token::Str("bot"),
891                Token::Bool(false),
892                Token::Str("discriminator"),
893                Token::Str("1111"),
894                Token::Str("global_name"),
895                Token::Some,
896                Token::Str("test"),
897                Token::Str("id"),
898                Token::NewtypeStruct { name: "Id" },
899                Token::Str("600"),
900                Token::Str("username"),
901                Token::Str("username"),
902                Token::StructEnd,
903                Token::MapEnd,
904                Token::StructEnd,
905                Token::StructEnd,
906                Token::Str("entitlements"),
907                Token::Seq { len: Some(1) },
908                Token::Struct {
909                    name: "Entitlement",
910                    len: 6,
911                },
912                Token::Str("application_id"),
913                Token::NewtypeStruct { name: "Id" },
914                Token::Str("100"),
915                Token::Str("consumed"),
916                Token::Some,
917                Token::Bool(false),
918                Token::Str("deleted"),
919                Token::Bool(false),
920                Token::Str("id"),
921                Token::NewtypeStruct { name: "Id" },
922                Token::Str("200"),
923                Token::Str("type"),
924                Token::U8(8),
925                Token::Str("sku_id"),
926                Token::NewtypeStruct { name: "Id" },
927                Token::Str("300"),
928                Token::StructEnd,
929                Token::SeqEnd,
930                Token::Str("guild_id"),
931                Token::Some,
932                Token::NewtypeStruct { name: "Id" },
933                Token::Str("400"),
934                Token::Str("guild_locale"),
935                Token::Some,
936                Token::String("de"),
937                Token::Str("id"),
938                Token::NewtypeStruct { name: "Id" },
939                Token::Str("500"),
940                Token::Str("type"),
941                Token::U8(InteractionType::ApplicationCommand as u8),
942                Token::Str("locale"),
943                Token::Some,
944                Token::Str("en-GB"),
945                Token::Str("member"),
946                Token::Some,
947                Token::Struct {
948                    name: "PartialMember",
949                    len: 9,
950                },
951                Token::Str("communication_disabled_until"),
952                Token::None,
953                Token::Str("deaf"),
954                Token::Bool(false),
955                Token::Str("flags"),
956                Token::U64(flags.bits()),
957                Token::Str("joined_at"),
958                Token::Some,
959                Token::Str("2020-01-01T00:00:00.000000+00:00"),
960                Token::Str("mute"),
961                Token::Bool(false),
962                Token::Str("nick"),
963                Token::Some,
964                Token::Str("nickname"),
965                Token::Str("permissions"),
966                Token::Some,
967                Token::Str("0"),
968                Token::Str("roles"),
969                Token::Seq { len: Some(0) },
970                Token::SeqEnd,
971                Token::Str("user"),
972                Token::Some,
973                Token::Struct {
974                    name: "User",
975                    len: 10,
976                },
977                Token::Str("accent_color"),
978                Token::None,
979                Token::Str("avatar"),
980                Token::Some,
981                Token::Str(image_hash::AVATAR_INPUT),
982                Token::Str("avatar_decoration"),
983                Token::None,
984                Token::Str("avatar_decoration_data"),
985                Token::None,
986                Token::Str("banner"),
987                Token::None,
988                Token::Str("bot"),
989                Token::Bool(false),
990                Token::Str("discriminator"),
991                Token::Str("1111"),
992                Token::Str("global_name"),
993                Token::Some,
994                Token::Str("test"),
995                Token::Str("id"),
996                Token::NewtypeStruct { name: "Id" },
997                Token::Str("600"),
998                Token::Str("username"),
999                Token::Str("username"),
1000                Token::StructEnd,
1001                Token::StructEnd,
1002                Token::Str("token"),
1003                Token::Str("interaction token"),
1004                Token::StructEnd,
1005            ],
1006        );
1007
1008        Ok(())
1009    }
1010}