1pub mod application_command;
8pub mod message_component;
9pub mod modal;
10
11mod context_type;
12mod interaction_type;
13mod metadata;
14mod resolved;
15
16pub use self::{
17 context_type::InteractionContextType,
18 interaction_type::InteractionType,
19 metadata::InteractionMetadata,
20 resolved::{InteractionChannel, InteractionDataResolved, InteractionMember},
21};
22
23use self::{
24 application_command::CommandData, message_component::MessageComponentInteractionData,
25 modal::ModalInteractionData,
26};
27use crate::{
28 channel::{Channel, Message},
29 guild::{GuildFeature, PartialMember, Permissions},
30 id::{
31 marker::{ApplicationMarker, ChannelMarker, GuildMarker, InteractionMarker, UserMarker},
32 AnonymizableId, Id,
33 },
34 oauth::ApplicationIntegrationMap,
35 user::User,
36};
37use serde::{
38 de::{Error as DeError, IgnoredAny, MapAccess, Visitor},
39 Deserialize, Deserializer, Serialize,
40};
41use serde_value::{DeserializerError, Value};
42use std::fmt::{Formatter, Result as FmtResult};
43
44use super::monetization::Entitlement;
45
46#[derive(Clone, Debug, PartialEq, Serialize)]
52pub struct Interaction {
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub app_permissions: Option<Permissions>,
56 pub application_id: Id<ApplicationMarker>,
58 pub authorizing_integration_owners:
61 ApplicationIntegrationMap<AnonymizableId<GuildMarker>, Id<UserMarker>>,
62 #[serde(skip_serializing_if = "Option::is_none")]
68 pub channel: Option<Channel>,
69 #[serde(skip_serializing_if = "Option::is_none")]
75 #[deprecated(
76 note = "channel_id is deprecated in the discord API and will no be sent in the future, users should use the channel field instead."
77 )]
78 pub channel_id: Option<Id<ChannelMarker>>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub context: Option<InteractionContextType>,
82 #[serde(skip_serializing_if = "Option::is_none")]
93 pub data: Option<InteractionData>,
94 pub entitlements: Vec<Entitlement>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub guild: Option<InteractionPartialGuild>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub guild_id: Option<Id<GuildMarker>>,
102 #[serde(skip_serializing_if = "Option::is_none")]
106 pub guild_locale: Option<String>,
107 pub id: Id<InteractionMarker>,
109 #[serde(rename = "type")]
111 pub kind: InteractionType,
112 #[serde(skip_serializing_if = "Option::is_none")]
118 pub locale: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
123 pub member: Option<PartialMember>,
124 #[serde(skip_serializing_if = "Option::is_none")]
130 pub message: Option<Message>,
131 pub token: String,
133 #[serde(skip_serializing_if = "Option::is_none")]
137 pub user: Option<User>,
138}
139
140impl Interaction {
141 pub const fn author_id(&self) -> Option<Id<UserMarker>> {
150 if let Some(user) = self.author() {
151 Some(user.id)
152 } else {
153 None
154 }
155 }
156
157 pub const fn author(&self) -> Option<&User> {
166 match self.member.as_ref() {
167 Some(member) if member.user.is_some() => member.user.as_ref(),
168 _ => self.user.as_ref(),
169 }
170 }
171
172 pub const fn is_dm(&self) -> bool {
174 self.user.is_some()
175 }
176
177 pub const fn is_guild(&self) -> bool {
179 self.member.is_some()
180 }
181}
182
183impl<'de> Deserialize<'de> for Interaction {
184 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
185 deserializer.deserialize_map(InteractionVisitor)
186 }
187}
188
189#[derive(Debug, Deserialize)]
190#[serde(field_identifier, rename_all = "snake_case")]
191enum InteractionField {
192 AppPermissions,
193 ApplicationId,
194 Context,
195 Channel,
196 ChannelId,
197 Data,
198 Entitlements,
199 Guild,
200 GuildId,
201 GuildLocale,
202 Id,
203 Locale,
204 Member,
205 Message,
206 Token,
207 Type,
208 User,
209 Version,
210 AuthorizingIntegrationOwners,
211}
212
213struct InteractionVisitor;
214
215impl<'de> Visitor<'de> for InteractionVisitor {
216 type Value = Interaction;
217
218 fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
219 f.write_str("enum Interaction")
220 }
221
222 #[allow(clippy::too_many_lines, deprecated)]
223 fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Self::Value, V::Error> {
224 let mut app_permissions: Option<Permissions> = None;
225 let mut application_id: Option<Id<ApplicationMarker>> = None;
226 let mut channel: Option<Channel> = None;
227 let mut channel_id: Option<Id<ChannelMarker>> = None;
228 let mut context: Option<InteractionContextType> = None;
229 let mut data: Option<Value> = None;
230 let mut entitlements: Option<Vec<Entitlement>> = None;
231 let mut guild: Option<InteractionPartialGuild> = None;
232 let mut guild_id: Option<Id<GuildMarker>> = None;
233 let mut guild_locale: Option<String> = None;
234 let mut id: Option<Id<InteractionMarker>> = None;
235 let mut kind: Option<InteractionType> = None;
236 let mut locale: Option<String> = None;
237 let mut member: Option<PartialMember> = None;
238 let mut message: Option<Message> = None;
239 let mut token: Option<String> = None;
240 let mut user: Option<User> = None;
241 let mut authorizing_integration_owners: Option<
242 ApplicationIntegrationMap<AnonymizableId<GuildMarker>, Id<UserMarker>>,
243 > = None;
244
245 loop {
246 let key = match map.next_key() {
247 Ok(Some(key)) => key,
248 Ok(None) => break,
249 Err(_) => {
250 map.next_value::<IgnoredAny>()?;
251
252 continue;
253 }
254 };
255
256 match key {
257 InteractionField::AppPermissions => {
258 if app_permissions.is_some() {
259 return Err(DeError::duplicate_field("app_permissions"));
260 }
261
262 app_permissions = map.next_value()?;
263 }
264 InteractionField::ApplicationId => {
265 if application_id.is_some() {
266 return Err(DeError::duplicate_field("application_id"));
267 }
268
269 application_id = Some(map.next_value()?);
270 }
271 InteractionField::Context => {
272 if context.is_some() {
273 return Err(DeError::duplicate_field("context"));
274 }
275
276 context = map.next_value()?;
277 }
278 InteractionField::Channel => {
279 if channel.is_some() {
280 return Err(DeError::duplicate_field("channel"));
281 }
282
283 channel = map.next_value()?;
284 }
285 InteractionField::ChannelId => {
286 if channel_id.is_some() {
287 return Err(DeError::duplicate_field("channel_id"));
288 }
289
290 channel_id = map.next_value()?;
291 }
292 InteractionField::Data => {
293 if data.is_some() {
294 return Err(DeError::duplicate_field("data"));
295 }
296
297 data = map.next_value()?;
298 }
299 InteractionField::Entitlements => {
300 if entitlements.is_some() {
301 return Err(DeError::duplicate_field("entitlements"));
302 }
303
304 entitlements = map.next_value()?;
305 }
306 InteractionField::Guild => {
307 if guild.is_some() {
308 return Err(DeError::duplicate_field("guild"));
309 }
310
311 guild = map.next_value()?;
312 }
313 InteractionField::GuildId => {
314 if guild_id.is_some() {
315 return Err(DeError::duplicate_field("guild_id"));
316 }
317
318 guild_id = map.next_value()?;
319 }
320 InteractionField::GuildLocale => {
321 if guild_locale.is_some() {
322 return Err(DeError::duplicate_field("guild_locale"));
323 }
324
325 guild_locale = map.next_value()?;
326 }
327 InteractionField::Id => {
328 if id.is_some() {
329 return Err(DeError::duplicate_field("id"));
330 }
331
332 id = Some(map.next_value()?);
333 }
334 InteractionField::Locale => {
335 if locale.is_some() {
336 return Err(DeError::duplicate_field("locale"));
337 }
338
339 locale = map.next_value()?;
340 }
341 InteractionField::Member => {
342 if member.is_some() {
343 return Err(DeError::duplicate_field("member"));
344 }
345
346 member = map.next_value()?;
347 }
348 InteractionField::Message => {
349 if message.is_some() {
350 return Err(DeError::duplicate_field("message"));
351 }
352
353 message = map.next_value()?;
354 }
355 InteractionField::Token => {
356 if token.is_some() {
357 return Err(DeError::duplicate_field("token"));
358 }
359
360 token = Some(map.next_value()?);
361 }
362 InteractionField::Type => {
363 if kind.is_some() {
364 return Err(DeError::duplicate_field("kind"));
365 }
366
367 kind = Some(map.next_value()?);
368 }
369 InteractionField::User => {
370 if user.is_some() {
371 return Err(DeError::duplicate_field("user"));
372 }
373
374 user = map.next_value()?;
375 }
376 InteractionField::Version => {
377 map.next_value::<IgnoredAny>()?;
379 }
380 InteractionField::AuthorizingIntegrationOwners => {
381 if authorizing_integration_owners.is_some() {
382 return Err(DeError::duplicate_field("authorizing_integration_owners"));
383 }
384
385 authorizing_integration_owners = map.next_value()?;
386 }
387 }
388 }
389
390 let application_id =
391 application_id.ok_or_else(|| DeError::missing_field("application_id"))?;
392 let authorizing_integration_owners = authorizing_integration_owners
393 .ok_or_else(|| DeError::missing_field("authorizing_integration_owners"))?;
394 let id = id.ok_or_else(|| DeError::missing_field("id"))?;
395 let token = token.ok_or_else(|| DeError::missing_field("token"))?;
396 let kind = kind.ok_or_else(|| DeError::missing_field("kind"))?;
397
398 let data = match kind {
399 InteractionType::Ping => None,
400 InteractionType::ApplicationCommand => {
401 let data = data
402 .ok_or_else(|| DeError::missing_field("data"))?
403 .deserialize_into()
404 .map_err(DeserializerError::into_error)?;
405
406 Some(InteractionData::ApplicationCommand(data))
407 }
408 InteractionType::MessageComponent => {
409 let data = data
410 .ok_or_else(|| DeError::missing_field("data"))?
411 .deserialize_into()
412 .map_err(DeserializerError::into_error)?;
413
414 Some(InteractionData::MessageComponent(data))
415 }
416 InteractionType::ApplicationCommandAutocomplete => {
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::ModalSubmit => {
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::ModalSubmit(data))
431 }
432 };
433
434 let entitlements = entitlements.unwrap_or_default();
435
436 Ok(Self::Value {
437 app_permissions,
438 application_id,
439 authorizing_integration_owners,
440 channel,
441 channel_id,
442 context,
443 data,
444 entitlements,
445 guild,
446 guild_id,
447 guild_locale,
448 id,
449 kind,
450 locale,
451 member,
452 message,
453 token,
454 user,
455 })
456 }
457}
458
459#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
461#[non_exhaustive]
462#[serde(untagged)]
463pub enum InteractionData {
464 ApplicationCommand(Box<CommandData>),
470 MessageComponent(Box<MessageComponentInteractionData>),
474 ModalSubmit(ModalInteractionData),
478}
479
480#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Hash)]
488pub struct InteractionPartialGuild {
489 pub id: Option<Id<GuildMarker>>,
491 pub features: Option<Vec<GuildFeature>>,
493 pub locale: Option<String>,
494}
495
496#[cfg(test)]
497mod tests {
498 use super::{
499 application_command::{CommandData, CommandDataOption, CommandOptionValue},
500 Interaction, InteractionData, InteractionDataResolved, InteractionMember, InteractionType,
501 };
502 use crate::{
503 application::{
504 command::{CommandOptionType, CommandType},
505 monetization::{entitlement::Entitlement, EntitlementType},
506 },
507 channel::Channel,
508 guild::{MemberFlags, PartialMember, Permissions},
509 id::Id,
510 oauth::ApplicationIntegrationMap,
511 test::image_hash,
512 user::User,
513 util::datetime::{Timestamp, TimestampParseError},
514 };
515 use serde_test::Token;
516 use std::{collections::HashMap, str::FromStr};
517
518 #[test]
519 #[allow(clippy::too_many_lines, deprecated)]
520 fn test_interaction_full() -> Result<(), TimestampParseError> {
521 let joined_at = Some(Timestamp::from_str("2020-01-01T00:00:00.000000+00:00")?);
522 let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
523
524 let value = Interaction {
525 app_permissions: Some(Permissions::SEND_MESSAGES),
526 application_id: Id::new(100),
527 authorizing_integration_owners: ApplicationIntegrationMap {
528 guild: None,
529 user: None,
530 },
531 channel: Some(Channel {
532 bitrate: None,
533 guild_id: None,
534 id: Id::new(400),
535 kind: crate::channel::ChannelType::GuildText,
536 last_message_id: None,
537 last_pin_timestamp: None,
538 name: None,
539 nsfw: None,
540 owner_id: None,
541 parent_id: None,
542 permission_overwrites: None,
543 position: None,
544 rate_limit_per_user: None,
545 recipients: None,
546 rtc_region: None,
547 topic: None,
548 user_limit: None,
549 application_id: None,
550 applied_tags: None,
551 available_tags: None,
552 default_auto_archive_duration: None,
553 default_forum_layout: None,
554 default_reaction_emoji: None,
555 default_sort_order: None,
556 default_thread_rate_limit_per_user: None,
557 flags: None,
558 icon: None,
559 invitable: None,
560 managed: None,
561 member: None,
562 member_count: None,
563 message_count: None,
564 newly_created: None,
565 thread_metadata: None,
566 video_quality_mode: None,
567 }),
568 channel_id: Some(Id::new(200)),
569 context: None,
570 data: Some(InteractionData::ApplicationCommand(Box::new(CommandData {
571 guild_id: None,
572 id: Id::new(300),
573 name: "command name".into(),
574 kind: CommandType::ChatInput,
575 options: Vec::from([CommandDataOption {
576 name: "member".into(),
577 value: CommandOptionValue::User(Id::new(600)),
578 }]),
579 resolved: Some(InteractionDataResolved {
580 attachments: HashMap::new(),
581 channels: HashMap::new(),
582 members: IntoIterator::into_iter([(
583 Id::new(600),
584 InteractionMember {
585 avatar: None,
586 communication_disabled_until: None,
587 flags,
588 joined_at,
589 nick: Some("nickname".into()),
590 pending: false,
591 permissions: Permissions::empty(),
592 premium_since: None,
593 roles: Vec::new(),
594 },
595 )])
596 .collect(),
597 messages: HashMap::new(),
598 roles: HashMap::new(),
599 users: IntoIterator::into_iter([(
600 Id::new(600),
601 User {
602 accent_color: None,
603 avatar: Some(image_hash::AVATAR),
604 avatar_decoration: None,
605 avatar_decoration_data: None,
606 banner: None,
607 bot: false,
608 discriminator: 1111,
609 email: None,
610 flags: None,
611 global_name: Some("test".into()),
612 id: Id::new(600),
613 locale: None,
614 mfa_enabled: None,
615 name: "username".into(),
616 premium_type: None,
617 public_flags: None,
618 system: None,
619 verified: None,
620 },
621 )])
622 .collect(),
623 }),
624 target_id: None,
625 }))),
626 entitlements: vec![Entitlement {
627 application_id: Id::new(100),
628 consumed: Some(false),
629 deleted: false,
630 ends_at: None,
631 guild_id: None,
632 id: Id::new(200),
633 kind: EntitlementType::ApplicationSubscription,
634 sku_id: Id::new(300),
635 starts_at: None,
636 user_id: None,
637 }],
638 guild: None,
639 guild_id: Some(Id::new(400)),
640 guild_locale: Some("de".to_owned()),
641 id: Id::new(500),
642 kind: InteractionType::ApplicationCommand,
643 locale: Some("en-GB".to_owned()),
644 member: Some(PartialMember {
645 avatar: None,
646 communication_disabled_until: None,
647 deaf: false,
648 flags,
649 joined_at,
650 mute: false,
651 nick: Some("nickname".into()),
652 permissions: Some(Permissions::empty()),
653 premium_since: None,
654 roles: Vec::new(),
655 user: Some(User {
656 accent_color: None,
657 avatar: Some(image_hash::AVATAR),
658 avatar_decoration: None,
659 avatar_decoration_data: None,
660 banner: None,
661 bot: false,
662 discriminator: 1111,
663 email: None,
664 flags: None,
665 global_name: Some("test".into()),
666 id: Id::new(600),
667 locale: None,
668 mfa_enabled: None,
669 name: "username".into(),
670 premium_type: None,
671 public_flags: None,
672 system: None,
673 verified: None,
674 }),
675 }),
676 message: None,
677 token: "interaction token".into(),
678 user: None,
679 };
680
681 serde_test::assert_ser_tokens(
683 &value,
684 &[
685 Token::Struct {
686 name: "Interaction",
687 len: 14,
688 },
689 Token::Str("app_permissions"),
690 Token::Some,
691 Token::Str("2048"),
692 Token::Str("application_id"),
693 Token::NewtypeStruct { name: "Id" },
694 Token::Str("100"),
695 Token::Str("authorizing_integration_owners"),
696 Token::Struct {
697 name: "ApplicationIntegrationMap",
698 len: 0,
699 },
700 Token::StructEnd,
701 Token::Str("channel"),
702 Token::Some,
703 Token::Struct {
704 name: "Channel",
705 len: 2,
706 },
707 Token::Str("id"),
708 Token::NewtypeStruct { name: "Id" },
709 Token::Str("400"),
710 Token::Str("type"),
711 Token::U8(0),
712 Token::StructEnd,
713 Token::Str("channel_id"),
714 Token::Some,
715 Token::NewtypeStruct { name: "Id" },
716 Token::Str("200"),
717 Token::Str("data"),
718 Token::Some,
719 Token::Struct {
720 name: "CommandData",
721 len: 5,
722 },
723 Token::Str("id"),
724 Token::NewtypeStruct { name: "Id" },
725 Token::Str("300"),
726 Token::Str("name"),
727 Token::Str("command name"),
728 Token::Str("type"),
729 Token::U8(1),
730 Token::Str("options"),
731 Token::Seq { len: Some(1) },
732 Token::Struct {
733 name: "CommandDataOption",
734 len: 3,
735 },
736 Token::Str("name"),
737 Token::Str("member"),
738 Token::Str("type"),
739 Token::U8(CommandOptionType::User as u8),
740 Token::Str("value"),
741 Token::NewtypeStruct { name: "Id" },
742 Token::Str("600"),
743 Token::StructEnd,
744 Token::SeqEnd,
745 Token::Str("resolved"),
746 Token::Some,
747 Token::Struct {
748 name: "InteractionDataResolved",
749 len: 2,
750 },
751 Token::Str("members"),
752 Token::Map { len: Some(1) },
753 Token::NewtypeStruct { name: "Id" },
754 Token::Str("600"),
755 Token::Struct {
756 name: "InteractionMember",
757 len: 7,
758 },
759 Token::Str("communication_disabled_until"),
760 Token::None,
761 Token::Str("flags"),
762 Token::U64(flags.bits()),
763 Token::Str("joined_at"),
764 Token::Some,
765 Token::Str("2020-01-01T00:00:00.000000+00:00"),
766 Token::Str("nick"),
767 Token::Some,
768 Token::Str("nickname"),
769 Token::Str("pending"),
770 Token::Bool(false),
771 Token::Str("permissions"),
772 Token::Str("0"),
773 Token::Str("roles"),
774 Token::Seq { len: Some(0) },
775 Token::SeqEnd,
776 Token::StructEnd,
777 Token::MapEnd,
778 Token::Str("users"),
779 Token::Map { len: Some(1) },
780 Token::NewtypeStruct { name: "Id" },
781 Token::Str("600"),
782 Token::Struct {
783 name: "User",
784 len: 10,
785 },
786 Token::Str("accent_color"),
787 Token::None,
788 Token::Str("avatar"),
789 Token::Some,
790 Token::Str(image_hash::AVATAR_INPUT),
791 Token::Str("avatar_decoration"),
792 Token::None,
793 Token::Str("avatar_decoration_data"),
794 Token::None,
795 Token::Str("banner"),
796 Token::None,
797 Token::Str("bot"),
798 Token::Bool(false),
799 Token::Str("discriminator"),
800 Token::Str("1111"),
801 Token::Str("global_name"),
802 Token::Some,
803 Token::Str("test"),
804 Token::Str("id"),
805 Token::NewtypeStruct { name: "Id" },
806 Token::Str("600"),
807 Token::Str("username"),
808 Token::Str("username"),
809 Token::StructEnd,
810 Token::MapEnd,
811 Token::StructEnd,
812 Token::StructEnd,
813 Token::Str("entitlements"),
814 Token::Seq { len: Some(1) },
815 Token::Struct {
816 name: "Entitlement",
817 len: 6,
818 },
819 Token::Str("application_id"),
820 Token::NewtypeStruct { name: "Id" },
821 Token::Str("100"),
822 Token::Str("consumed"),
823 Token::Some,
824 Token::Bool(false),
825 Token::Str("deleted"),
826 Token::Bool(false),
827 Token::Str("id"),
828 Token::NewtypeStruct { name: "Id" },
829 Token::Str("200"),
830 Token::Str("type"),
831 Token::U8(8),
832 Token::Str("sku_id"),
833 Token::NewtypeStruct { name: "Id" },
834 Token::Str("300"),
835 Token::StructEnd,
836 Token::SeqEnd,
837 Token::Str("guild_id"),
838 Token::Some,
839 Token::NewtypeStruct { name: "Id" },
840 Token::Str("400"),
841 Token::Str("guild_locale"),
842 Token::Some,
843 Token::String("de"),
844 Token::Str("id"),
845 Token::NewtypeStruct { name: "Id" },
846 Token::Str("500"),
847 Token::Str("type"),
848 Token::U8(InteractionType::ApplicationCommand as u8),
849 Token::Str("locale"),
850 Token::Some,
851 Token::Str("en-GB"),
852 Token::Str("member"),
853 Token::Some,
854 Token::Struct {
855 name: "PartialMember",
856 len: 9,
857 },
858 Token::Str("communication_disabled_until"),
859 Token::None,
860 Token::Str("deaf"),
861 Token::Bool(false),
862 Token::Str("flags"),
863 Token::U64(flags.bits()),
864 Token::Str("joined_at"),
865 Token::Some,
866 Token::Str("2020-01-01T00:00:00.000000+00:00"),
867 Token::Str("mute"),
868 Token::Bool(false),
869 Token::Str("nick"),
870 Token::Some,
871 Token::Str("nickname"),
872 Token::Str("permissions"),
873 Token::Some,
874 Token::Str("0"),
875 Token::Str("roles"),
876 Token::Seq { len: Some(0) },
877 Token::SeqEnd,
878 Token::Str("user"),
879 Token::Some,
880 Token::Struct {
881 name: "User",
882 len: 10,
883 },
884 Token::Str("accent_color"),
885 Token::None,
886 Token::Str("avatar"),
887 Token::Some,
888 Token::Str(image_hash::AVATAR_INPUT),
889 Token::Str("avatar_decoration"),
890 Token::None,
891 Token::Str("avatar_decoration_data"),
892 Token::None,
893 Token::Str("banner"),
894 Token::None,
895 Token::Str("bot"),
896 Token::Bool(false),
897 Token::Str("discriminator"),
898 Token::Str("1111"),
899 Token::Str("global_name"),
900 Token::Some,
901 Token::Str("test"),
902 Token::Str("id"),
903 Token::NewtypeStruct { name: "Id" },
904 Token::Str("600"),
905 Token::Str("username"),
906 Token::Str("username"),
907 Token::StructEnd,
908 Token::StructEnd,
909 Token::Str("token"),
910 Token::Str("interaction token"),
911 Token::StructEnd,
912 ],
913 );
914
915 Ok(())
916 }
917}