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