Skip to main content

twilight_model/application/interaction/modal/
mod.rs

1//! [`ModalSubmit`] interaction.
2//!
3//!
4//! [`ModalSubmit`]: crate::application::interaction::InteractionType::ModalSubmit
5mod action_row;
6mod checkbox;
7mod checkbox_group;
8mod file_upload;
9mod label;
10mod select_menu;
11mod text_display;
12mod text_input;
13
14pub use self::{
15    action_row::ModalInteractionActionRow,
16    checkbox::ModalInteractionCheckbox,
17    checkbox_group::ModalInteractionCheckboxGroup,
18    file_upload::ModalInteractionFileUpload,
19    label::ModalInteractionLabel,
20    select_menu::{
21        ModalInteractionChannelSelect, ModalInteractionMentionableSelect,
22        ModalInteractionRoleSelect, ModalInteractionStringSelect, ModalInteractionUserSelect,
23    },
24    text_display::ModalInteractionTextDisplay,
25    text_input::ModalInteractionTextInput,
26};
27use crate::application::interaction::InteractionDataResolved;
28use crate::application::interaction::modal::select_menu::ModalInteractionSelectMenu;
29use crate::channel::message::component::ComponentType;
30use crate::id::Id;
31use crate::id::marker::{ChannelMarker, GenericMarker, RoleMarker, UserMarker};
32use serde::{
33    Deserialize, Serialize, Serializer,
34    de::{Deserializer, Error as DeError, IgnoredAny, MapAccess, Visitor},
35    ser::SerializeStruct,
36};
37use serde_value::{DeserializerError, Value};
38use std::fmt::Formatter;
39
40/// Data received when an [`ModalSubmit`] interaction is executed.
41///
42/// See [Discord Docs/Modal Submit Data Structure].
43///
44/// [`ModalSubmit`]: crate::application::interaction::InteractionType::ModalSubmit
45/// [Discord Docs/Modal Submit Data Structure]: https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-modal-submit-data-structure
46#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
47pub struct ModalInteractionData {
48    /// List of modal component responses.
49    pub components: Vec<ModalInteractionComponent>,
50    /// User defined identifier for the modal.
51    ///
52    /// See [Discord Docs/Custom ID].
53    ///
54    /// [Discord Docs/Custom ID]: https://discord.com/developers/docs/components/reference#anatomy-of-a-component-custom-id
55    pub custom_id: String,
56    /// Resolved data from select menus.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub resolved: Option<InteractionDataResolved>,
59}
60
61/// User filled in modal component.
62#[derive(Clone, Debug, Eq, PartialEq)]
63pub enum ModalInteractionComponent {
64    /// Top-level layout component. In modals, layout components are preferred over action rows.
65    ActionRow(ModalInteractionActionRow),
66    /// Dropdown selection component for channels.
67    ChannelSelect(ModalInteractionChannelSelect),
68    /// Checkbox Component
69    Checkbox(ModalInteractionCheckbox),
70    /// Checkbox Group Component.
71    CheckboxGroup(ModalInteractionCheckboxGroup),
72    /// File upload component.
73    FileUpload(ModalInteractionFileUpload),
74    /// Top-level layout component including a string label and optional description.
75    Label(ModalInteractionLabel),
76    /// Dropdown selection component for mentionables.
77    MentionableSelect(ModalInteractionMentionableSelect),
78    /// Dropdown selection component for roles.
79    RoleSelect(ModalInteractionRoleSelect),
80    /// Dropdown selection component for text.
81    StringSelect(ModalInteractionStringSelect),
82    /// Markdown text.
83    TextDisplay(ModalInteractionTextDisplay),
84    /// Text input component.
85    TextInput(ModalInteractionTextInput),
86    /// Variant value is unknown to the library in the context of modals.
87    Unknown(u8),
88    /// Dropdown selection component for users.
89    UserSelect(ModalInteractionUserSelect),
90}
91
92impl ModalInteractionComponent {
93    /// Type of component that this is.
94    pub fn kind(&self) -> ComponentType {
95        match self {
96            ModalInteractionComponent::ActionRow(_) => ComponentType::ActionRow,
97            ModalInteractionComponent::ChannelSelect(_) => ComponentType::ChannelSelectMenu,
98            ModalInteractionComponent::Checkbox(_) => ComponentType::Checkbox,
99            ModalInteractionComponent::CheckboxGroup(_) => ComponentType::CheckboxGroup,
100            ModalInteractionComponent::FileUpload(_) => ComponentType::FileUpload,
101            ModalInteractionComponent::Label(_) => ComponentType::Label,
102            ModalInteractionComponent::MentionableSelect(_) => ComponentType::MentionableSelectMenu,
103            ModalInteractionComponent::RoleSelect(_) => ComponentType::RoleSelectMenu,
104            ModalInteractionComponent::StringSelect(_) => ComponentType::TextSelectMenu,
105            ModalInteractionComponent::TextDisplay(_) => ComponentType::TextDisplay,
106            ModalInteractionComponent::TextInput(_) => ComponentType::TextInput,
107            ModalInteractionComponent::Unknown(unknown) => ComponentType::from(*unknown),
108            ModalInteractionComponent::UserSelect(_) => ComponentType::UserSelectMenu,
109        }
110    }
111}
112
113impl From<ModalInteractionActionRow> for ModalInteractionComponent {
114    fn from(action_row: ModalInteractionActionRow) -> Self {
115        Self::ActionRow(action_row)
116    }
117}
118
119impl From<ModalInteractionChannelSelect> for ModalInteractionComponent {
120    fn from(select: ModalInteractionChannelSelect) -> Self {
121        Self::ChannelSelect(select)
122    }
123}
124
125impl From<ModalInteractionCheckbox> for ModalInteractionComponent {
126    fn from(checkbox: ModalInteractionCheckbox) -> Self {
127        Self::Checkbox(checkbox)
128    }
129}
130
131impl From<ModalInteractionCheckboxGroup> for ModalInteractionComponent {
132    fn from(checkbox_group: ModalInteractionCheckboxGroup) -> Self {
133        Self::CheckboxGroup(checkbox_group)
134    }
135}
136
137impl From<ModalInteractionFileUpload> for ModalInteractionComponent {
138    fn from(file_upload: ModalInteractionFileUpload) -> Self {
139        Self::FileUpload(file_upload)
140    }
141}
142
143impl From<ModalInteractionLabel> for ModalInteractionComponent {
144    fn from(label: ModalInteractionLabel) -> Self {
145        Self::Label(label)
146    }
147}
148
149impl From<ModalInteractionMentionableSelect> for ModalInteractionComponent {
150    fn from(select: ModalInteractionMentionableSelect) -> Self {
151        Self::MentionableSelect(select)
152    }
153}
154
155impl From<ModalInteractionRoleSelect> for ModalInteractionComponent {
156    fn from(select: ModalInteractionRoleSelect) -> Self {
157        Self::RoleSelect(select)
158    }
159}
160
161impl From<ModalInteractionStringSelect> for ModalInteractionComponent {
162    fn from(select: ModalInteractionStringSelect) -> Self {
163        Self::StringSelect(select)
164    }
165}
166
167impl From<ModalInteractionTextDisplay> for ModalInteractionComponent {
168    fn from(text_display: ModalInteractionTextDisplay) -> Self {
169        Self::TextDisplay(text_display)
170    }
171}
172
173impl From<ModalInteractionTextInput> for ModalInteractionComponent {
174    fn from(text_input: ModalInteractionTextInput) -> Self {
175        Self::TextInput(text_input)
176    }
177}
178
179impl From<ModalInteractionUserSelect> for ModalInteractionComponent {
180    fn from(select: ModalInteractionUserSelect) -> Self {
181        Self::UserSelect(select)
182    }
183}
184
185impl TryFrom<ModalInteractionComponent> for ModalInteractionActionRow {
186    type Error = ModalInteractionComponent;
187
188    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
189        match value {
190            ModalInteractionComponent::ActionRow(inner) => Ok(inner),
191            _ => Err(value),
192        }
193    }
194}
195
196impl TryFrom<ModalInteractionComponent> for ModalInteractionChannelSelect {
197    type Error = ModalInteractionComponent;
198
199    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
200        match value {
201            ModalInteractionComponent::ChannelSelect(inner) => Ok(inner),
202            _ => Err(value),
203        }
204    }
205}
206
207impl TryFrom<ModalInteractionComponent> for ModalInteractionCheckbox {
208    type Error = ModalInteractionComponent;
209
210    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
211        match value {
212            ModalInteractionComponent::Checkbox(inner) => Ok(inner),
213            _ => Err(value),
214        }
215    }
216}
217
218impl TryFrom<ModalInteractionComponent> for ModalInteractionCheckboxGroup {
219    type Error = ModalInteractionComponent;
220
221    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
222        match value {
223            ModalInteractionComponent::CheckboxGroup(inner) => Ok(inner),
224            _ => Err(value),
225        }
226    }
227}
228
229impl TryFrom<ModalInteractionComponent> for ModalInteractionFileUpload {
230    type Error = ModalInteractionComponent;
231
232    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
233        match value {
234            ModalInteractionComponent::FileUpload(inner) => Ok(inner),
235            _ => Err(value),
236        }
237    }
238}
239
240impl TryFrom<ModalInteractionComponent> for ModalInteractionLabel {
241    type Error = ModalInteractionComponent;
242
243    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
244        match value {
245            ModalInteractionComponent::Label(inner) => Ok(inner),
246            _ => Err(value),
247        }
248    }
249}
250
251impl TryFrom<ModalInteractionComponent> for ModalInteractionMentionableSelect {
252    type Error = ModalInteractionComponent;
253
254    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
255        match value {
256            ModalInteractionComponent::MentionableSelect(inner) => Ok(inner),
257            _ => Err(value),
258        }
259    }
260}
261
262impl TryFrom<ModalInteractionComponent> for ModalInteractionRoleSelect {
263    type Error = ModalInteractionComponent;
264
265    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
266        match value {
267            ModalInteractionComponent::RoleSelect(inner) => Ok(inner),
268            _ => Err(value),
269        }
270    }
271}
272
273impl TryFrom<ModalInteractionComponent> for ModalInteractionStringSelect {
274    type Error = ModalInteractionComponent;
275
276    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
277        match value {
278            ModalInteractionComponent::StringSelect(inner) => Ok(inner),
279            _ => Err(value),
280        }
281    }
282}
283
284impl TryFrom<ModalInteractionComponent> for ModalInteractionTextDisplay {
285    type Error = ModalInteractionComponent;
286
287    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
288        match value {
289            ModalInteractionComponent::TextDisplay(inner) => Ok(inner),
290            _ => Err(value),
291        }
292    }
293}
294
295impl TryFrom<ModalInteractionComponent> for ModalInteractionTextInput {
296    type Error = ModalInteractionComponent;
297
298    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
299        match value {
300            ModalInteractionComponent::TextInput(inner) => Ok(inner),
301            _ => Err(value),
302        }
303    }
304}
305
306impl TryFrom<ModalInteractionComponent> for ModalInteractionUserSelect {
307    type Error = ModalInteractionComponent;
308
309    fn try_from(value: ModalInteractionComponent) -> Result<Self, Self::Error> {
310        match value {
311            ModalInteractionComponent::UserSelect(inner) => Ok(inner),
312            _ => Err(value),
313        }
314    }
315}
316
317impl<'de> Deserialize<'de> for ModalInteractionComponent {
318    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
319    where
320        D: Deserializer<'de>,
321    {
322        deserializer.deserialize_any(ModalInteractionDataComponentVisitor)
323    }
324}
325
326#[derive(Debug, Deserialize)]
327#[serde(field_identifier, rename_all = "snake_case")]
328enum Field {
329    Component,
330    Components,
331    CustomId,
332    Id,
333    Type,
334    Value,
335    Values,
336}
337
338struct ModalInteractionDataComponentVisitor;
339
340impl<'de> Visitor<'de> for ModalInteractionDataComponentVisitor {
341    type Value = ModalInteractionComponent;
342
343    fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
344        f.write_str("struct ModalInteractionDataComponent")
345    }
346
347    #[allow(clippy::too_many_lines)]
348    fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Self::Value, V::Error> {
349        fn deserialize_select_menu<'de, ValueType: Deserialize<'de>, Error: DeError>(
350            id: i32,
351            custom_id: Option<String>,
352            values: Option<Vec<Value>>,
353        ) -> Result<ModalInteractionSelectMenu<ValueType>, Error> {
354            let custom_id = custom_id.ok_or_else(|| DeError::missing_field("custom_id"))?;
355            let values = values
356                .ok_or_else(|| DeError::missing_field("values"))?
357                .into_iter()
358                .map(Value::deserialize_into)
359                .collect::<Result<_, _>>()
360                .map_err(DeserializerError::into_error)?;
361
362            Ok(ModalInteractionSelectMenu {
363                id,
364                custom_id,
365                values,
366            })
367        }
368
369        // Required fields
370        let mut id: Option<i32> = None;
371        let mut kind: Option<ComponentType> = None;
372        let mut custom_id: Option<String> = None;
373        let mut values: Option<Vec<Value>> = None;
374        let mut components: Option<Vec<ModalInteractionComponent>> = None;
375        let mut component: Option<ModalInteractionComponent> = None;
376        let mut value: Option<Value> = None;
377
378        loop {
379            let key = match map.next_key() {
380                Ok(Some(key)) => key,
381                Ok(None) => break,
382                Err(_) => {
383                    map.next_value::<IgnoredAny>()?;
384
385                    continue;
386                }
387            };
388
389            match key {
390                Field::Component => {
391                    if component.is_some() {
392                        return Err(DeError::duplicate_field("component"));
393                    }
394
395                    component = Some(map.next_value()?);
396                }
397                Field::Components => {
398                    if components.is_some() {
399                        return Err(DeError::duplicate_field("components"));
400                    }
401
402                    components = Some(map.next_value()?);
403                }
404                Field::CustomId => {
405                    if custom_id.is_some() {
406                        return Err(DeError::duplicate_field("custom_id"));
407                    }
408
409                    custom_id = Some(map.next_value()?);
410                }
411                Field::Id => {
412                    if id.is_some() {
413                        return Err(DeError::duplicate_field("id"));
414                    }
415
416                    id = Some(map.next_value()?);
417                }
418                Field::Type => {
419                    if kind.is_some() {
420                        return Err(DeError::duplicate_field("kind"));
421                    }
422
423                    kind = Some(map.next_value()?);
424                }
425                Field::Value => {
426                    if value.is_some() {
427                        return Err(DeError::duplicate_field("value"));
428                    }
429
430                    value = Some(map.next_value()?);
431                }
432                Field::Values => {
433                    if values.is_some() {
434                        return Err(DeError::duplicate_field("values"));
435                    }
436
437                    values = Some(map.next_value()?);
438                }
439            }
440        }
441
442        let kind = kind.ok_or_else(|| DeError::missing_field("type"))?;
443        let id = id.ok_or_else(|| DeError::missing_field("id"))?;
444
445        Ok(match kind {
446            ComponentType::ActionRow => {
447                let components = components.ok_or_else(|| DeError::missing_field("components"))?;
448
449                Self::Value::ActionRow(ModalInteractionActionRow { components, id })
450            }
451            ComponentType::TextSelectMenu => {
452                Self::Value::StringSelect(deserialize_select_menu::<String, _>(
453                    id, custom_id, values,
454                )?)
455            }
456            ComponentType::UserSelectMenu => {
457                Self::Value::UserSelect(deserialize_select_menu::<Id<UserMarker>, _>(
458                    id, custom_id, values,
459                )?)
460            }
461            ComponentType::RoleSelectMenu => {
462                Self::Value::RoleSelect(deserialize_select_menu::<Id<RoleMarker>, _>(
463                    id, custom_id, values,
464                )?)
465            }
466            ComponentType::MentionableSelectMenu => Self::Value::MentionableSelect(
467                deserialize_select_menu::<Id<GenericMarker>, _>(id, custom_id, values)?,
468            ),
469            ComponentType::ChannelSelectMenu => Self::Value::ChannelSelect(
470                deserialize_select_menu::<Id<ChannelMarker>, _>(id, custom_id, values)?,
471            ),
472            ComponentType::TextInput => {
473                let custom_id = custom_id.ok_or_else(|| DeError::missing_field("custom_id"))?;
474                let value = value
475                    .ok_or_else(|| DeError::missing_field("value"))?
476                    .deserialize_into()
477                    .map_err(DeserializerError::into_error)?;
478
479                Self::Value::TextInput(ModalInteractionTextInput {
480                    custom_id,
481                    id,
482                    value,
483                })
484            }
485            ComponentType::TextDisplay => {
486                Self::Value::TextDisplay(ModalInteractionTextDisplay { id })
487            }
488            ComponentType::Label => {
489                let component = component.ok_or_else(|| DeError::missing_field("component"))?;
490
491                Self::Value::Label(ModalInteractionLabel {
492                    id,
493                    component: Box::new(component),
494                })
495            }
496            ComponentType::FileUpload => {
497                let custom_id = custom_id.ok_or_else(|| DeError::missing_field("custom_id"))?;
498                let values = values
499                    .ok_or_else(|| DeError::missing_field("values"))?
500                    .into_iter()
501                    .map(Value::deserialize_into)
502                    .collect::<Result<_, _>>()
503                    .map_err(DeserializerError::into_error)?;
504
505                Self::Value::FileUpload(ModalInteractionFileUpload {
506                    custom_id,
507                    id,
508                    values,
509                })
510            }
511            ComponentType::CheckboxGroup => {
512                let custom_id = custom_id.ok_or_else(|| DeError::missing_field("custom_id"))?;
513                let values = values
514                    .ok_or_else(|| DeError::missing_field("values"))?
515                    .into_iter()
516                    .map(Value::deserialize_into)
517                    .collect::<Result<_, _>>()
518                    .map_err(DeserializerError::into_error)?;
519
520                Self::Value::CheckboxGroup(ModalInteractionCheckboxGroup {
521                    custom_id,
522                    id,
523                    values,
524                })
525            }
526            ComponentType::Checkbox => {
527                let custom_id = custom_id.ok_or_else(|| DeError::missing_field("custom_id"))?;
528                let value = value
529                    .ok_or_else(|| DeError::missing_field("value"))?
530                    .deserialize_into()
531                    .map_err(DeserializerError::into_error)?;
532
533                Self::Value::Checkbox(ModalInteractionCheckbox {
534                    custom_id,
535                    id,
536                    value,
537                })
538            }
539            ComponentType::Button
540            | ComponentType::Section
541            | ComponentType::Thumbnail
542            | ComponentType::MediaGallery
543            | ComponentType::File
544            | ComponentType::Separator
545            | ComponentType::Container
546            | ComponentType::Unknown(_) => Self::Value::Unknown(kind.into()),
547        })
548    }
549}
550
551impl Serialize for ModalInteractionComponent {
552    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
553        fn serialize_select_menu<State: SerializeStruct, ValueType: Serialize>(
554            state: &mut State,
555            select: &ModalInteractionSelectMenu<ValueType>,
556        ) -> Result<(), <State as SerializeStruct>::Error> {
557            state.serialize_field("custom_id", &select.custom_id)?;
558            state.serialize_field("id", &select.id)?;
559            state.serialize_field("values", &select.values)?;
560            Ok(())
561        }
562
563        #[allow(clippy::match_same_arms)]
564        let len = match self {
565            // Required fields:
566            // - type
567            // - id
568            // - components
569            ModalInteractionComponent::ActionRow(_) => 3,
570            // Required fields:
571            // - type
572            // - id
573            // - custom_id
574            // - values
575            ModalInteractionComponent::ChannelSelect(_)
576            | ModalInteractionComponent::MentionableSelect(_)
577            | ModalInteractionComponent::RoleSelect(_)
578            | ModalInteractionComponent::UserSelect(_)
579            | ModalInteractionComponent::StringSelect(_) => 4,
580            // Required fields:
581            // - type
582            // - id
583            // - custom_id
584            // - value
585            ModalInteractionComponent::Checkbox(_) => 4,
586            // Required fields:
587            // - type
588            // - id
589            // - custom_id
590            // - values
591            ModalInteractionComponent::CheckboxGroup(_) => 4,
592            // Required fields:
593            // - type
594            // - id
595            // - custom_id
596            // - values
597            ModalInteractionComponent::FileUpload(_) => 4,
598            // Required fields:
599            // - type
600            // - id
601            // - component
602            ModalInteractionComponent::Label(_) => 3,
603            // Required fields:
604            // - type
605            // - id
606            ModalInteractionComponent::TextDisplay(_) => 2,
607            // Required fields:
608            // - type
609            // - id
610            // - custom_id
611            // - value
612            ModalInteractionComponent::TextInput(_) => 4,
613            // We are dropping all fields but type here but nothing we can do about that for
614            // the time being
615            ModalInteractionComponent::Unknown(_) => 1,
616        };
617
618        let mut state = serializer.serialize_struct("ModalInteractionComponent", len)?;
619        state.serialize_field("type", &self.kind())?;
620
621        match self {
622            ModalInteractionComponent::ActionRow(action_row) => {
623                state.serialize_field("id", &action_row.id)?;
624                state.serialize_field("components", &action_row.components)?;
625            }
626            ModalInteractionComponent::ChannelSelect(select) => {
627                serialize_select_menu(&mut state, select)?;
628            }
629            ModalInteractionComponent::Checkbox(checkbox) => {
630                state.serialize_field("custom_id", &checkbox.custom_id)?;
631                state.serialize_field("id", &checkbox.id)?;
632                state.serialize_field("value", &checkbox.value)?;
633            }
634            ModalInteractionComponent::CheckboxGroup(checkbox_group) => {
635                state.serialize_field("custom_id", &checkbox_group.custom_id)?;
636                state.serialize_field("id", &checkbox_group.id)?;
637                state.serialize_field("values", &checkbox_group.values)?;
638            }
639            ModalInteractionComponent::FileUpload(file_upload) => {
640                state.serialize_field("custom_id", &file_upload.custom_id)?;
641                state.serialize_field("id", &file_upload.id)?;
642                state.serialize_field("values", &file_upload.values)?;
643            }
644            ModalInteractionComponent::Label(label) => {
645                state.serialize_field("id", &label.id)?;
646                state.serialize_field("component", &label.component)?;
647            }
648            ModalInteractionComponent::MentionableSelect(select) => {
649                serialize_select_menu(&mut state, select)?;
650            }
651            ModalInteractionComponent::RoleSelect(select) => {
652                serialize_select_menu(&mut state, select)?;
653            }
654            ModalInteractionComponent::StringSelect(select) => {
655                serialize_select_menu(&mut state, select)?;
656            }
657            ModalInteractionComponent::TextDisplay(text_display) => {
658                state.serialize_field("id", &text_display.id)?;
659            }
660            ModalInteractionComponent::TextInput(text_input) => {
661                state.serialize_field("custom_id", &text_input.custom_id)?;
662                state.serialize_field("id", &text_input.id)?;
663                state.serialize_field("value", &text_input.value)?;
664            }
665            // We are not serializing all fields so this will fail to
666            // deserialize. But it is all that can be done to avoid losing
667            // incoming messages at this time.
668            ModalInteractionComponent::Unknown(_) => {}
669            ModalInteractionComponent::UserSelect(select) => {
670                serialize_select_menu(&mut state, select)?;
671            }
672        }
673
674        state.end()
675    }
676}
677
678#[cfg(test)]
679mod tests {
680    use super::*;
681    use crate::application::interaction::InteractionChannel;
682    use crate::channel::ChannelType;
683    use crate::guild::Permissions;
684    use serde_test::Token;
685    use static_assertions::{assert_fields, assert_impl_all};
686    use std::collections::HashMap;
687    use std::fmt::Debug;
688
689    assert_fields!(ModalInteractionData: custom_id, components);
690    assert_impl_all!(
691        ModalInteractionData: Clone,
692        Debug,
693        Deserialize<'static>,
694        PartialEq,
695        Send,
696        Serialize,
697        Sync
698    );
699
700    assert_impl_all!(
701        ModalInteractionComponent: Clone,
702        Debug,
703        Deserialize<'static>,
704        Eq,
705        PartialEq,
706        Send,
707        Serialize,
708        Sync
709    );
710
711    fn label_tokens(id: i32, component_tokens: &[Token]) -> Vec<Token> {
712        let mut label_tokens = vec![
713            Token::Struct {
714                name: "ModalInteractionComponent",
715                len: 3,
716            },
717            Token::String("type"),
718            Token::U8(ComponentType::Label.into()),
719            Token::String("id"),
720            Token::I32(id),
721            Token::String("component"),
722        ];
723        label_tokens.extend_from_slice(component_tokens);
724        label_tokens.push(Token::StructEnd);
725
726        label_tokens
727    }
728
729    #[test]
730    fn modal_action_rows() {
731        let value = ModalInteractionData {
732            custom_id: "test-modal".to_owned(),
733            components: vec![ModalInteractionComponent::ActionRow(
734                ModalInteractionActionRow {
735                    id: 1,
736                    components: vec![ModalInteractionComponent::TextInput(
737                        ModalInteractionTextInput {
738                            custom_id: "the-data-id".to_owned(),
739                            id: 2,
740                            value: "input value".to_owned(),
741                        },
742                    )],
743                },
744            )],
745            resolved: None,
746        };
747
748        serde_test::assert_tokens(
749            &value,
750            &[
751                Token::Struct {
752                    name: "ModalInteractionData",
753                    len: 2,
754                },
755                Token::String("components"),
756                Token::Seq { len: Some(1) },
757                Token::Struct {
758                    name: "ModalInteractionComponent",
759                    len: 3,
760                },
761                Token::String("type"),
762                Token::U8(ComponentType::ActionRow.into()),
763                Token::String("id"),
764                Token::I32(1),
765                Token::String("components"),
766                Token::Seq { len: Some(1) },
767                Token::Struct {
768                    name: "ModalInteractionComponent",
769                    len: 4,
770                },
771                Token::String("type"),
772                Token::U8(ComponentType::TextInput.into()),
773                Token::String("custom_id"),
774                Token::String("the-data-id"),
775                Token::String("id"),
776                Token::I32(2),
777                Token::String("value"),
778                Token::String("input value"),
779                Token::StructEnd,
780                Token::SeqEnd,
781                Token::StructEnd,
782                Token::SeqEnd,
783                Token::String("custom_id"),
784                Token::String("test-modal"),
785                Token::StructEnd,
786            ],
787        );
788    }
789
790    #[test]
791    #[allow(clippy::too_many_lines)]
792    fn modal_labels() {
793        let value = ModalInteractionData {
794            custom_id: "test-modal".to_owned(),
795            components: vec![
796                ModalInteractionComponent::Label(ModalInteractionLabel {
797                    id: 1,
798                    component: Box::new(ModalInteractionComponent::TextInput(
799                        ModalInteractionTextInput {
800                            custom_id: "the-text-input-id".to_owned(),
801                            id: 2,
802                            value: "input value".to_owned(),
803                        },
804                    )),
805                }),
806                ModalInteractionComponent::Label(ModalInteractionLabel {
807                    id: 3,
808                    component: Box::new(ModalInteractionComponent::TextDisplay(
809                        ModalInteractionTextDisplay { id: 4 },
810                    )),
811                }),
812                ModalInteractionComponent::Label(ModalInteractionLabel {
813                    id: 5,
814                    component: Box::new(ModalInteractionComponent::ChannelSelect(
815                        ModalInteractionChannelSelect {
816                            id: 6,
817                            custom_id: "the-channel-select-id".to_owned(),
818                            values: vec![Id::new(42)],
819                        },
820                    )),
821                }),
822            ],
823            resolved: Some(InteractionDataResolved {
824                attachments: HashMap::new(),
825                channels: HashMap::from([(
826                    Id::new(42),
827                    InteractionChannel {
828                        id: Id::new(42),
829                        kind: ChannelType::GuildText,
830                        name: "the-channel-name".to_owned(),
831                        parent_id: None,
832                        permissions: Permissions::empty(),
833                        app_permissions: None,
834                        thread_metadata: None,
835                    },
836                )]),
837                members: HashMap::new(),
838                messages: HashMap::new(),
839                roles: HashMap::new(),
840                users: HashMap::new(),
841            }),
842        };
843
844        let text_input_tokens = [
845            Token::Struct {
846                name: "ModalInteractionComponent",
847                len: 4,
848            },
849            Token::String("type"),
850            Token::U8(ComponentType::TextInput.into()),
851            Token::String("custom_id"),
852            Token::String("the-text-input-id"),
853            Token::String("id"),
854            Token::I32(2),
855            Token::String("value"),
856            Token::String("input value"),
857            Token::StructEnd,
858        ];
859
860        let text_display_tokens = [
861            Token::Struct {
862                name: "ModalInteractionComponent",
863                len: 2,
864            },
865            Token::String("type"),
866            Token::U8(ComponentType::TextDisplay.into()),
867            Token::String("id"),
868            Token::I32(4),
869            Token::StructEnd,
870        ];
871
872        let channel_select_tokens = [
873            Token::Struct {
874                name: "ModalInteractionComponent",
875                len: 4,
876            },
877            Token::String("type"),
878            Token::U8(ComponentType::ChannelSelectMenu.into()),
879            Token::String("custom_id"),
880            Token::String("the-channel-select-id"),
881            Token::String("id"),
882            Token::I32(6),
883            Token::String("values"),
884            Token::Seq { len: Some(1) },
885            Token::NewtypeStruct { name: "Id" },
886            Token::String("42"),
887            Token::SeqEnd,
888            Token::StructEnd,
889        ];
890
891        let mut all_tokens = vec![
892            Token::Struct {
893                name: "ModalInteractionData",
894                len: 3,
895            },
896            Token::String("components"),
897            Token::Seq { len: Some(3) },
898        ];
899
900        all_tokens.extend_from_slice(&label_tokens(1, &text_input_tokens));
901        all_tokens.extend_from_slice(&label_tokens(3, &text_display_tokens));
902        all_tokens.extend_from_slice(&label_tokens(5, &channel_select_tokens));
903
904        all_tokens.extend_from_slice(&[
905            Token::SeqEnd,
906            Token::String("custom_id"),
907            Token::String("test-modal"),
908            Token::String("resolved"),
909            Token::Some,
910            Token::Struct {
911                name: "InteractionDataResolved",
912                len: 1,
913            },
914            Token::String("channels"),
915            Token::Map { len: Some(1) },
916            Token::NewtypeStruct { name: "Id" },
917            Token::String("42"),
918            Token::Struct {
919                name: "InteractionChannel",
920                len: 4,
921            },
922            Token::String("id"),
923            Token::NewtypeStruct { name: "Id" },
924            Token::String("42"),
925            Token::String("type"),
926            Token::U8(0),
927            Token::String("name"),
928            Token::String("the-channel-name"),
929            Token::String("permissions"),
930            Token::String("0"),
931            Token::StructEnd,
932            Token::MapEnd,
933            Token::StructEnd,
934            Token::StructEnd,
935        ]);
936
937        serde_test::assert_tokens(&value, &all_tokens);
938    }
939
940    #[test]
941    fn modal_file_upload() {
942        let value = ModalInteractionData {
943            custom_id: "test-modal".to_owned(),
944            components: vec![ModalInteractionComponent::FileUpload(
945                ModalInteractionFileUpload {
946                    id: 42,
947                    custom_id: "file-upload".to_owned(),
948                    values: vec![Id::new(1), Id::new(2)],
949                },
950            )],
951            // Having a None resolved data for the file upload response is not realistic,
952            // but (de)serialization of InteractionDataResolved is already tested sufficiently.
953            resolved: None,
954        };
955
956        serde_test::assert_tokens(
957            &value,
958            &[
959                Token::Struct {
960                    name: "ModalInteractionData",
961                    len: 2,
962                },
963                Token::String("components"),
964                Token::Seq { len: Some(1) },
965                Token::Struct {
966                    name: "ModalInteractionComponent",
967                    len: 4,
968                },
969                Token::String("type"),
970                Token::U8(ComponentType::FileUpload.into()),
971                Token::String("custom_id"),
972                Token::String("file-upload"),
973                Token::String("id"),
974                Token::I32(42),
975                Token::String("values"),
976                Token::Seq { len: Some(2) },
977                Token::NewtypeStruct { name: "Id" },
978                Token::Str("1"),
979                Token::NewtypeStruct { name: "Id" },
980                Token::Str("2"),
981                Token::SeqEnd,
982                Token::StructEnd,
983                Token::SeqEnd,
984                Token::String("custom_id"),
985                Token::String("test-modal"),
986                Token::StructEnd,
987            ],
988        )
989    }
990
991    #[test]
992    fn modal_checkbox() {
993        let value = ModalInteractionData {
994            custom_id: "test-modal".to_owned(),
995            components: vec![ModalInteractionComponent::Checkbox(
996                ModalInteractionCheckbox {
997                    id: 10,
998                    custom_id: "checkbox".to_owned(),
999                    value: true,
1000                },
1001            )],
1002            resolved: None,
1003        };
1004
1005        serde_test::assert_tokens(
1006            &value,
1007            &[
1008                Token::Struct {
1009                    name: "ModalInteractionData",
1010                    len: 2,
1011                },
1012                Token::String("components"),
1013                Token::Seq { len: Some(1) },
1014                Token::Struct {
1015                    name: "ModalInteractionComponent",
1016                    len: 4,
1017                },
1018                Token::String("type"),
1019                Token::U8(ComponentType::Checkbox.into()),
1020                Token::String("custom_id"),
1021                Token::String("checkbox"),
1022                Token::String("id"),
1023                Token::I32(10),
1024                Token::String("value"),
1025                Token::Bool(true),
1026                Token::StructEnd,
1027                Token::SeqEnd,
1028                Token::String("custom_id"),
1029                Token::String("test-modal"),
1030                Token::StructEnd,
1031            ],
1032        )
1033    }
1034}