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                total_message_sent: None,
652                video_quality_mode: None,
653            }),
654            channel_id: Some(Id::new(200)),
655            context: None,
656            data: Some(InteractionData::ApplicationCommand(Box::new(CommandData {
657                guild_id: None,
658                id: Id::new(300),
659                kind: CommandType::ChatInput,
660                name: "command name".into(),
661                options: Vec::from([CommandDataOption {
662                    name: "member".into(),
663                    value: CommandOptionValue::User(Id::new(600)),
664                }]),
665                resolved: Some(InteractionDataResolved {
666                    attachments: HashMap::new(),
667                    channels: HashMap::new(),
668                    members: IntoIterator::into_iter([(
669                        Id::new(600),
670                        InteractionMember {
671                            avatar: None,
672                            avatar_decoration_data: None,
673                            banner: None,
674                            communication_disabled_until: None,
675                            flags,
676                            joined_at,
677                            nick: Some("nickname".into()),
678                            pending: false,
679                            permissions: Permissions::empty(),
680                            premium_since: None,
681                            roles: Vec::new(),
682                        },
683                    )])
684                    .collect(),
685                    messages: HashMap::new(),
686                    roles: HashMap::new(),
687                    users: IntoIterator::into_iter([(
688                        Id::new(600),
689                        User {
690                            accent_color: None,
691                            avatar: Some(image_hash::AVATAR),
692                            avatar_decoration: None,
693                            avatar_decoration_data: None,
694                            banner: None,
695                            bot: false,
696                            discriminator: 1111,
697                            email: None,
698                            flags: None,
699                            global_name: Some("test".into()),
700                            id: Id::new(600),
701                            locale: None,
702                            mfa_enabled: None,
703                            name: "username".into(),
704                            premium_type: None,
705                            primary_guild: None,
706                            public_flags: None,
707                            system: None,
708                            verified: None,
709                        },
710                    )])
711                    .collect(),
712                }),
713                target_id: None,
714            }))),
715            entitlements: vec![Entitlement {
716                application_id: Id::new(100),
717                consumed: Some(false),
718                deleted: false,
719                ends_at: None,
720                guild_id: None,
721                id: Id::new(200),
722                kind: EntitlementType::ApplicationSubscription,
723                sku_id: Id::new(300),
724                starts_at: None,
725                user_id: None,
726            }],
727            guild: None,
728            guild_id: Some(Id::new(400)),
729            guild_locale: Some("de".to_owned()),
730            id: Id::new(500),
731            kind: InteractionType::ApplicationCommand,
732            locale: Some("en-GB".to_owned()),
733            member: Some(PartialMember {
734                avatar: None,
735                avatar_decoration_data: None,
736                banner: None,
737                communication_disabled_until: None,
738                deaf: false,
739                flags,
740                joined_at,
741                mute: false,
742                nick: Some("nickname".into()),
743                permissions: Some(Permissions::empty()),
744                premium_since: None,
745                roles: Vec::new(),
746                user: Some(User {
747                    accent_color: None,
748                    avatar: Some(image_hash::AVATAR),
749                    avatar_decoration: None,
750                    avatar_decoration_data: None,
751                    banner: None,
752                    bot: false,
753                    discriminator: 1111,
754                    email: None,
755                    flags: None,
756                    global_name: Some("test".into()),
757                    id: Id::new(600),
758                    locale: None,
759                    mfa_enabled: None,
760                    name: "username".into(),
761                    premium_type: None,
762                    primary_guild: None,
763                    public_flags: None,
764                    system: None,
765                    verified: None,
766                }),
767            }),
768            message: None,
769            token: "interaction token".into(),
770            user: None,
771        };
772
773        // TODO: switch the `assert_tokens` see #2190
774        serde_test::assert_ser_tokens(
775            &value,
776            &[
777                Token::Struct {
778                    name: "Interaction",
779                    len: 15,
780                },
781                Token::Str("app_permissions"),
782                Token::Some,
783                Token::Str("2048"),
784                Token::Str("application_id"),
785                Token::NewtypeStruct { name: "Id" },
786                Token::Str("100"),
787                Token::Str("attachment_size_limit"),
788                Token::U64(8_388_608),
789                Token::Str("authorizing_integration_owners"),
790                Token::Struct {
791                    name: "ApplicationIntegrationMap",
792                    len: 0,
793                },
794                Token::StructEnd,
795                Token::Str("channel"),
796                Token::Some,
797                Token::Struct {
798                    name: "Channel",
799                    len: 2,
800                },
801                Token::Str("id"),
802                Token::NewtypeStruct { name: "Id" },
803                Token::Str("400"),
804                Token::Str("type"),
805                Token::U8(0),
806                Token::StructEnd,
807                Token::Str("channel_id"),
808                Token::Some,
809                Token::NewtypeStruct { name: "Id" },
810                Token::Str("200"),
811                Token::Str("data"),
812                Token::Some,
813                Token::Struct {
814                    name: "CommandData",
815                    len: 5,
816                },
817                Token::Str("id"),
818                Token::NewtypeStruct { name: "Id" },
819                Token::Str("300"),
820                Token::Str("type"),
821                Token::U8(1),
822                Token::Str("name"),
823                Token::Str("command name"),
824                Token::Str("options"),
825                Token::Seq { len: Some(1) },
826                Token::Struct {
827                    name: "CommandDataOption",
828                    len: 3,
829                },
830                Token::Str("name"),
831                Token::Str("member"),
832                Token::Str("type"),
833                Token::U8(CommandOptionType::User as u8),
834                Token::Str("value"),
835                Token::NewtypeStruct { name: "Id" },
836                Token::Str("600"),
837                Token::StructEnd,
838                Token::SeqEnd,
839                Token::Str("resolved"),
840                Token::Some,
841                Token::Struct {
842                    name: "InteractionDataResolved",
843                    len: 2,
844                },
845                Token::Str("members"),
846                Token::Map { len: Some(1) },
847                Token::NewtypeStruct { name: "Id" },
848                Token::Str("600"),
849                Token::Struct {
850                    name: "InteractionMember",
851                    len: 7,
852                },
853                Token::Str("communication_disabled_until"),
854                Token::None,
855                Token::Str("flags"),
856                Token::U64(flags.bits()),
857                Token::Str("joined_at"),
858                Token::Some,
859                Token::Str("2020-01-01T00:00:00.000000+00:00"),
860                Token::Str("nick"),
861                Token::Some,
862                Token::Str("nickname"),
863                Token::Str("pending"),
864                Token::Bool(false),
865                Token::Str("permissions"),
866                Token::Str("0"),
867                Token::Str("roles"),
868                Token::Seq { len: Some(0) },
869                Token::SeqEnd,
870                Token::StructEnd,
871                Token::MapEnd,
872                Token::Str("users"),
873                Token::Map { len: Some(1) },
874                Token::NewtypeStruct { name: "Id" },
875                Token::Str("600"),
876                Token::Struct {
877                    name: "User",
878                    len: 10,
879                },
880                Token::Str("accent_color"),
881                Token::None,
882                Token::Str("avatar"),
883                Token::Some,
884                Token::Str(image_hash::AVATAR_INPUT),
885                Token::Str("avatar_decoration"),
886                Token::None,
887                Token::Str("avatar_decoration_data"),
888                Token::None,
889                Token::Str("banner"),
890                Token::None,
891                Token::Str("bot"),
892                Token::Bool(false),
893                Token::Str("discriminator"),
894                Token::Str("1111"),
895                Token::Str("global_name"),
896                Token::Some,
897                Token::Str("test"),
898                Token::Str("id"),
899                Token::NewtypeStruct { name: "Id" },
900                Token::Str("600"),
901                Token::Str("username"),
902                Token::Str("username"),
903                Token::StructEnd,
904                Token::MapEnd,
905                Token::StructEnd,
906                Token::StructEnd,
907                Token::Str("entitlements"),
908                Token::Seq { len: Some(1) },
909                Token::Struct {
910                    name: "Entitlement",
911                    len: 6,
912                },
913                Token::Str("application_id"),
914                Token::NewtypeStruct { name: "Id" },
915                Token::Str("100"),
916                Token::Str("consumed"),
917                Token::Some,
918                Token::Bool(false),
919                Token::Str("deleted"),
920                Token::Bool(false),
921                Token::Str("id"),
922                Token::NewtypeStruct { name: "Id" },
923                Token::Str("200"),
924                Token::Str("type"),
925                Token::U8(8),
926                Token::Str("sku_id"),
927                Token::NewtypeStruct { name: "Id" },
928                Token::Str("300"),
929                Token::StructEnd,
930                Token::SeqEnd,
931                Token::Str("guild_id"),
932                Token::Some,
933                Token::NewtypeStruct { name: "Id" },
934                Token::Str("400"),
935                Token::Str("guild_locale"),
936                Token::Some,
937                Token::String("de"),
938                Token::Str("id"),
939                Token::NewtypeStruct { name: "Id" },
940                Token::Str("500"),
941                Token::Str("type"),
942                Token::U8(InteractionType::ApplicationCommand as u8),
943                Token::Str("locale"),
944                Token::Some,
945                Token::Str("en-GB"),
946                Token::Str("member"),
947                Token::Some,
948                Token::Struct {
949                    name: "PartialMember",
950                    len: 9,
951                },
952                Token::Str("communication_disabled_until"),
953                Token::None,
954                Token::Str("deaf"),
955                Token::Bool(false),
956                Token::Str("flags"),
957                Token::U64(flags.bits()),
958                Token::Str("joined_at"),
959                Token::Some,
960                Token::Str("2020-01-01T00:00:00.000000+00:00"),
961                Token::Str("mute"),
962                Token::Bool(false),
963                Token::Str("nick"),
964                Token::Some,
965                Token::Str("nickname"),
966                Token::Str("permissions"),
967                Token::Some,
968                Token::Str("0"),
969                Token::Str("roles"),
970                Token::Seq { len: Some(0) },
971                Token::SeqEnd,
972                Token::Str("user"),
973                Token::Some,
974                Token::Struct {
975                    name: "User",
976                    len: 10,
977                },
978                Token::Str("accent_color"),
979                Token::None,
980                Token::Str("avatar"),
981                Token::Some,
982                Token::Str(image_hash::AVATAR_INPUT),
983                Token::Str("avatar_decoration"),
984                Token::None,
985                Token::Str("avatar_decoration_data"),
986                Token::None,
987                Token::Str("banner"),
988                Token::None,
989                Token::Str("bot"),
990                Token::Bool(false),
991                Token::Str("discriminator"),
992                Token::Str("1111"),
993                Token::Str("global_name"),
994                Token::Some,
995                Token::Str("test"),
996                Token::Str("id"),
997                Token::NewtypeStruct { name: "Id" },
998                Token::Str("600"),
999                Token::Str("username"),
1000                Token::Str("username"),
1001                Token::StructEnd,
1002                Token::StructEnd,
1003                Token::Str("token"),
1004                Token::Str("interaction token"),
1005                Token::StructEnd,
1006            ],
1007        );
1008
1009        Ok(())
1010    }
1011}