twilight_model/application/interaction/
modal.rs

1//! [`ModalSubmit`] interaction.
2//!
3//!
4//! [`ModalSubmit`]: crate::application::interaction::InteractionType::ModalSubmit
5
6use crate::channel::message::component::ComponentType;
7use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
8
9/// Data received when an [`ModalSubmit`] interaction is executed.
10///
11/// See [Discord Docs/Modal Submit Data Structure].
12///
13/// [`ModalSubmit`]: crate::application::interaction::InteractionType::ModalSubmit
14/// [Discord Docs/Modal Submit Data Structure]: https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-modal-submit-data-structure
15#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
16pub struct ModalInteractionData {
17    /// List of user inputs.
18    pub components: Vec<ModalInteractionDataActionRow>,
19    /// User defined identifier for the modal.
20    ///
21    /// See [Discord Docs/Custom ID].
22    ///
23    /// [Discord Docs/Custom ID]: https://discord.com/developers/docs/interactions/message-components#custom-id
24    pub custom_id: String,
25}
26
27/// User filled in [`ActionRow`].
28///
29/// See [Discord Docs/Modal Submit Data Structure].
30///
31/// [`ActionRow`]: crate::application::interaction::modal::ModalInteractionDataActionRow
32/// [Discord Docs/Modal Submit Data Structure]: https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-modal-submit-data-structure
33#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
34pub struct ModalInteractionDataActionRow {
35    /// List of components.
36    pub components: Vec<ModalInteractionDataComponent>,
37}
38
39impl Serialize for ModalInteractionDataActionRow {
40    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
41        let mut state = serializer.serialize_struct("ModalInteractionDataActionRow", 2)?;
42
43        state.serialize_field("type", &ComponentType::ActionRow)?;
44        state.serialize_field("components", &self.components)?;
45
46        state.end()
47    }
48}
49
50/// User filled in modal component.
51///
52/// See [Discord Docs/Message Components].
53///
54/// [Discord Docs/Message Components]: https://discord.com/developers/docs/interactions/message-components
55#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
56pub struct ModalInteractionDataComponent {
57    /// User defined identifier for the component.
58    ///
59    /// See [Discord Docs/Custom ID].
60    ///
61    /// [Discord Docs/Custom ID]: https://discord.com/developers/docs/interactions/message-components#custom-id
62    pub custom_id: String,
63    /// Type of the component.
64    #[serde(rename = "type")]
65    pub kind: ComponentType,
66    /// Value submitted by the user.
67    pub value: Option<String>,
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use serde_test::Token;
74    use static_assertions::{assert_fields, assert_impl_all};
75    use std::fmt::Debug;
76
77    assert_fields!(ModalInteractionData: custom_id, components);
78    assert_impl_all!(
79        ModalInteractionData: Clone,
80        Debug,
81        Deserialize<'static>,
82        Eq,
83        PartialEq,
84        Send,
85        Serialize,
86        Sync
87    );
88
89    assert_fields!(ModalInteractionDataActionRow: components);
90    assert_impl_all!(
91        ModalInteractionDataActionRow: Clone,
92        Debug,
93        Deserialize<'static>,
94        Eq,
95        PartialEq,
96        Send,
97        Serialize,
98        Sync
99    );
100
101    assert_fields!(ModalInteractionDataComponent: custom_id, value);
102    assert_impl_all!(
103        ModalInteractionDataComponent: Clone,
104        Debug,
105        Deserialize<'static>,
106        Eq,
107        PartialEq,
108        Send,
109        Serialize,
110        Sync
111    );
112
113    #[test]
114    fn modal_data() {
115        let value = ModalInteractionData {
116            custom_id: "test-modal".to_owned(),
117            components: Vec::from([ModalInteractionDataActionRow {
118                components: Vec::from([ModalInteractionDataComponent {
119                    custom_id: "the-data-id".to_owned(),
120                    kind: ComponentType::TextInput,
121                    value: Some("input value".into()),
122                }]),
123            }]),
124        };
125
126        serde_test::assert_tokens(
127            &value,
128            &[
129                Token::Struct {
130                    name: "ModalInteractionData",
131                    len: 2,
132                },
133                Token::String("components"),
134                Token::Seq { len: Some(1) },
135                Token::Struct {
136                    name: "ModalInteractionDataActionRow",
137                    len: 2,
138                },
139                Token::String("type"),
140                Token::U8(ComponentType::ActionRow.into()),
141                Token::String("components"),
142                Token::Seq { len: Some(1) },
143                Token::Struct {
144                    name: "ModalInteractionDataComponent",
145                    len: 3,
146                },
147                Token::String("custom_id"),
148                Token::String("the-data-id"),
149                Token::String("type"),
150                Token::U8(ComponentType::TextInput.into()),
151                Token::String("value"),
152                Token::Some,
153                Token::String("input value"),
154                Token::StructEnd,
155                Token::SeqEnd,
156                Token::StructEnd,
157                Token::SeqEnd,
158                Token::String("custom_id"),
159                Token::String("test-modal"),
160                Token::StructEnd,
161            ],
162        );
163    }
164}