Skip to main content

twilight_model/channel/message/component/
mod.rs

1//! Interactive message elements for use with [`Interaction`]s.
2//!
3//! Refer to [Discord Docs/Message Components] for additional information.
4//!
5//! [`Interaction`]: crate::application::interaction::Interaction
6//! [Discord Docs/Message Components]: https://discord.com/developers/docs/interactions/message-components
7
8mod action_row;
9mod button;
10mod checkbox;
11mod checkbox_group;
12mod container;
13mod file_display;
14mod file_upload;
15mod kind;
16mod label;
17mod media_gallery;
18mod section;
19mod select_menu;
20mod separator;
21mod text_display;
22mod text_input;
23mod thumbnail;
24mod unfurled_media;
25
26pub use self::{
27    action_row::ActionRow,
28    button::{Button, ButtonStyle},
29    checkbox::Checkbox,
30    checkbox_group::{CheckboxGroup, CheckboxGroupOption},
31    container::Container,
32    file_display::FileDisplay,
33    file_upload::FileUpload,
34    kind::ComponentType,
35    label::Label,
36    media_gallery::{MediaGallery, MediaGalleryItem},
37    section::Section,
38    select_menu::{SelectDefaultValue, SelectMenu, SelectMenuOption, SelectMenuType},
39    separator::{Separator, SeparatorSpacingSize},
40    text_display::TextDisplay,
41    text_input::{TextInput, TextInputStyle},
42    thumbnail::Thumbnail,
43    unfurled_media::UnfurledMediaItem,
44};
45
46use super::EmojiReactionType;
47use crate::{
48    channel::ChannelType,
49    id::{Id, marker::SkuMarker},
50};
51use serde::{
52    Deserialize, Serialize, Serializer,
53    de::{Deserializer, Error as DeError, IgnoredAny, MapAccess, Visitor},
54    ser::{Error as SerError, SerializeStruct},
55};
56use serde_value::{DeserializerError, Value};
57use std::fmt::{Formatter, Result as FmtResult};
58
59/// Interactive message element.
60///
61/// Must be either a top level [`ActionRow`] or nested inside one.
62///
63/// # Examples
64///
65/// ## Button
66///
67/// ```
68/// use twilight_model::channel::message::component::{ActionRow, Button, ButtonStyle, Component};
69///
70/// Component::ActionRow(ActionRow {
71///     id: None,
72///     components: Vec::from([Component::Button(Button {
73///         id: None,
74///         custom_id: Some("click_one".to_owned()),
75///         disabled: false,
76///         emoji: None,
77///         label: Some("Click me!".to_owned()),
78///         style: ButtonStyle::Primary,
79///         url: None,
80///         sku_id: None,
81///     })]),
82/// });
83/// ```
84///
85/// ## Select menu
86///
87/// ```
88/// use twilight_model::{
89///     channel::message::{
90///         EmojiReactionType,
91///         component::{ActionRow, Component, SelectMenu, SelectMenuOption, SelectMenuType},
92///     },
93///     id::Id,
94/// };
95///
96/// Component::ActionRow(ActionRow {
97///     id: None,
98///     components: vec![Component::SelectMenu(SelectMenu {
99///         id: None,
100///         channel_types: None,
101///         custom_id: "class_select_1".to_owned(),
102///         default_values: None,
103///         disabled: false,
104///         kind: SelectMenuType::Text,
105///         max_values: Some(3),
106///         min_values: Some(1),
107///         options: Some(Vec::from([
108///             SelectMenuOption {
109///                 default: false,
110///                 emoji: Some(EmojiReactionType::Custom {
111///                     animated: false,
112///                     id: Id::new(625891304148303894),
113///                     name: Some("rogue".to_owned()),
114///                 }),
115///                 description: Some("Sneak n stab".to_owned()),
116///                 label: "Rogue".to_owned(),
117///                 value: "rogue".to_owned(),
118///             },
119///             SelectMenuOption {
120///                 default: false,
121///                 emoji: Some(EmojiReactionType::Custom {
122///                     animated: false,
123///                     id: Id::new(625891304081063986),
124///                     name: Some("mage".to_owned()),
125///                 }),
126///                 description: Some("Turn 'em into a sheep".to_owned()),
127///                 label: "Mage".to_owned(),
128///                 value: "mage".to_owned(),
129///             },
130///             SelectMenuOption {
131///                 default: false,
132///                 emoji: Some(EmojiReactionType::Custom {
133///                     animated: false,
134///                     id: Id::new(625891303795982337),
135///                     name: Some("priest".to_owned()),
136///                 }),
137///                 description: Some("You get heals when I'm done doing damage".to_owned()),
138///                 label: "Priest".to_owned(),
139///                 value: "priest".to_owned(),
140///             },
141///         ])),
142///         placeholder: Some("Choose a class".to_owned()),
143///         required: None,
144///     })],
145/// });
146/// ```
147#[derive(Clone, Debug, Eq, Hash, PartialEq)]
148pub enum Component {
149    /// Top level, non-interactive container of other (non action row) components.
150    ActionRow(ActionRow),
151    /// Clickable item that renders below messages.
152    Button(Button),
153    /// A selectable checkbox in a modal
154    Checkbox(Checkbox),
155    /// A group of selectable checkboxes in a modal
156    CheckboxGroup(CheckboxGroup),
157    /// Container that visually groups a set of components.
158    Container(Container),
159    /// Displays an attached file.
160    File(FileDisplay),
161    /// Allows uploading files in a modal.
162    FileUpload(FileUpload),
163    /// Wrapper for modal components providing a label and an optional description.
164    Label(Label),
165    /// Display images and other media.
166    MediaGallery(MediaGallery),
167    /// Container to display text alongside an accessory component.
168    Section(Section),
169    /// Dropdown-style item that renders below messages.
170    SelectMenu(SelectMenu),
171    /// Component to add vertical padding between other components.
172    Separator(Separator),
173    /// Markdown text.
174    TextDisplay(TextDisplay),
175    /// Pop-up item that renders on modals.
176    TextInput(TextInput),
177    /// Small image that can be used as an accessory.
178    Thumbnail(Thumbnail),
179    /// Variant value is unknown to the library.
180    Unknown(u8),
181}
182
183impl Component {
184    /// Type of component that this is.
185    ///
186    /// ```
187    /// use twilight_model::channel::message::component::{
188    ///     Button, ButtonStyle, Component, ComponentType,
189    /// };
190    ///
191    /// let component = Component::Button(Button {
192    ///     id: None,
193    ///     custom_id: None,
194    ///     disabled: false,
195    ///     emoji: None,
196    ///     label: Some("ping".to_owned()),
197    ///     style: ButtonStyle::Primary,
198    ///     url: None,
199    ///     sku_id: None,
200    /// });
201    ///
202    /// assert_eq!(ComponentType::Button, component.kind());
203    /// ```
204    pub const fn kind(&self) -> ComponentType {
205        match self {
206            Component::ActionRow(_) => ComponentType::ActionRow,
207            Component::Button(_) => ComponentType::Button,
208            Component::Checkbox(_) => ComponentType::Checkbox,
209            Component::CheckboxGroup(_) => ComponentType::CheckboxGroup,
210            Component::Container(_) => ComponentType::Container,
211            Component::File(_) => ComponentType::File,
212            Component::FileUpload(_) => ComponentType::FileUpload,
213            Component::Label(_) => ComponentType::Label,
214            Component::MediaGallery(_) => ComponentType::MediaGallery,
215            Component::Section(_) => ComponentType::Section,
216            Component::SelectMenu(SelectMenu { kind, .. }) => match kind {
217                SelectMenuType::Channel => ComponentType::ChannelSelectMenu,
218                SelectMenuType::Mentionable => ComponentType::MentionableSelectMenu,
219                SelectMenuType::Role => ComponentType::RoleSelectMenu,
220                SelectMenuType::Text => ComponentType::TextSelectMenu,
221                SelectMenuType::User => ComponentType::UserSelectMenu,
222            },
223            Component::Separator(_) => ComponentType::Separator,
224            Component::TextDisplay(_) => ComponentType::TextDisplay,
225            Component::TextInput(_) => ComponentType::TextInput,
226            Component::Thumbnail(_) => ComponentType::Thumbnail,
227            Component::Unknown(unknown) => ComponentType::Unknown(*unknown),
228        }
229    }
230
231    /// Get the amount of components a component should count as.
232    pub const fn component_count(&self) -> usize {
233        match self {
234            Component::ActionRow(action_row) => 1 + action_row.components.len(),
235            Component::Button(_)
236            | Component::Checkbox(_)
237            | Component::CheckboxGroup(_)
238            | Component::File(_)
239            | Component::FileUpload(_)
240            | Component::MediaGallery(_)
241            | Component::SelectMenu(_)
242            | Component::Separator(_)
243            | Component::TextDisplay(_)
244            | Component::TextInput(_)
245            | Component::Thumbnail(_)
246            | Component::Unknown(_) => 1,
247            Component::Container(container) => 1 + container.components.len(),
248            Component::Label(_) => 2,
249            Component::Section(section) => 1 + section.components.len(),
250        }
251    }
252}
253
254impl From<ActionRow> for Component {
255    fn from(action_row: ActionRow) -> Self {
256        Self::ActionRow(action_row)
257    }
258}
259
260impl From<Button> for Component {
261    fn from(button: Button) -> Self {
262        Self::Button(button)
263    }
264}
265
266impl From<Checkbox> for Component {
267    fn from(checkbox: Checkbox) -> Self {
268        Self::Checkbox(checkbox)
269    }
270}
271
272impl From<CheckboxGroup> for Component {
273    fn from(checkbox_group: CheckboxGroup) -> Self {
274        Self::CheckboxGroup(checkbox_group)
275    }
276}
277
278impl From<Container> for Component {
279    fn from(container: Container) -> Self {
280        Self::Container(container)
281    }
282}
283
284impl From<FileDisplay> for Component {
285    fn from(file_display: FileDisplay) -> Self {
286        Self::File(file_display)
287    }
288}
289
290impl From<FileUpload> for Component {
291    fn from(file_upload: FileUpload) -> Self {
292        Self::FileUpload(file_upload)
293    }
294}
295
296impl From<Label> for Component {
297    fn from(label: Label) -> Self {
298        Self::Label(label)
299    }
300}
301
302impl From<MediaGallery> for Component {
303    fn from(media_gallery: MediaGallery) -> Self {
304        Self::MediaGallery(media_gallery)
305    }
306}
307
308impl From<Section> for Component {
309    fn from(section: Section) -> Self {
310        Self::Section(section)
311    }
312}
313
314impl From<SelectMenu> for Component {
315    fn from(select_menu: SelectMenu) -> Self {
316        Self::SelectMenu(select_menu)
317    }
318}
319
320impl From<Separator> for Component {
321    fn from(separator: Separator) -> Self {
322        Self::Separator(separator)
323    }
324}
325
326impl From<TextDisplay> for Component {
327    fn from(text_display: TextDisplay) -> Self {
328        Self::TextDisplay(text_display)
329    }
330}
331
332impl From<TextInput> for Component {
333    fn from(text_input: TextInput) -> Self {
334        Self::TextInput(text_input)
335    }
336}
337
338impl From<Thumbnail> for Component {
339    fn from(thumbnail: Thumbnail) -> Self {
340        Self::Thumbnail(thumbnail)
341    }
342}
343
344impl TryFrom<Component> for ActionRow {
345    type Error = Component;
346
347    fn try_from(value: Component) -> Result<Self, Self::Error> {
348        match value {
349            Component::ActionRow(inner) => Ok(inner),
350            _ => Err(value),
351        }
352    }
353}
354
355impl TryFrom<Component> for Button {
356    type Error = Component;
357
358    fn try_from(value: Component) -> Result<Self, Self::Error> {
359        match value {
360            Component::Button(inner) => Ok(inner),
361            _ => Err(value),
362        }
363    }
364}
365
366impl TryFrom<Component> for Checkbox {
367    type Error = Component;
368
369    fn try_from(value: Component) -> Result<Self, Self::Error> {
370        match value {
371            Component::Checkbox(inner) => Ok(inner),
372            _ => Err(value),
373        }
374    }
375}
376
377impl TryFrom<Component> for CheckboxGroup {
378    type Error = Component;
379
380    fn try_from(value: Component) -> Result<Self, Self::Error> {
381        match value {
382            Component::CheckboxGroup(inner) => Ok(inner),
383            _ => Err(value),
384        }
385    }
386}
387
388impl TryFrom<Component> for Container {
389    type Error = Component;
390
391    fn try_from(value: Component) -> Result<Self, Self::Error> {
392        match value {
393            Component::Container(inner) => Ok(inner),
394            _ => Err(value),
395        }
396    }
397}
398
399impl TryFrom<Component> for FileDisplay {
400    type Error = Component;
401
402    fn try_from(value: Component) -> Result<Self, Self::Error> {
403        match value {
404            Component::File(inner) => Ok(inner),
405            _ => Err(value),
406        }
407    }
408}
409
410impl TryFrom<Component> for FileUpload {
411    type Error = Component;
412
413    fn try_from(value: Component) -> Result<Self, Self::Error> {
414        match value {
415            Component::FileUpload(inner) => Ok(inner),
416            _ => Err(value),
417        }
418    }
419}
420
421impl TryFrom<Component> for Label {
422    type Error = Component;
423
424    fn try_from(value: Component) -> Result<Self, Self::Error> {
425        match value {
426            Component::Label(inner) => Ok(inner),
427            _ => Err(value),
428        }
429    }
430}
431
432impl TryFrom<Component> for MediaGallery {
433    type Error = Component;
434
435    fn try_from(value: Component) -> Result<Self, Self::Error> {
436        match value {
437            Component::MediaGallery(inner) => Ok(inner),
438            _ => Err(value),
439        }
440    }
441}
442
443impl TryFrom<Component> for Section {
444    type Error = Component;
445
446    fn try_from(value: Component) -> Result<Self, Self::Error> {
447        match value {
448            Component::Section(inner) => Ok(inner),
449            _ => Err(value),
450        }
451    }
452}
453
454impl TryFrom<Component> for SelectMenu {
455    type Error = Component;
456
457    fn try_from(value: Component) -> Result<Self, Self::Error> {
458        match value {
459            Component::SelectMenu(inner) => Ok(inner),
460            _ => Err(value),
461        }
462    }
463}
464
465impl TryFrom<Component> for Separator {
466    type Error = Component;
467
468    fn try_from(value: Component) -> Result<Self, Self::Error> {
469        match value {
470            Component::Separator(inner) => Ok(inner),
471            _ => Err(value),
472        }
473    }
474}
475
476impl TryFrom<Component> for TextDisplay {
477    type Error = Component;
478
479    fn try_from(value: Component) -> Result<Self, Self::Error> {
480        match value {
481            Component::TextDisplay(inner) => Ok(inner),
482            _ => Err(value),
483        }
484    }
485}
486
487impl TryFrom<Component> for TextInput {
488    type Error = Component;
489
490    fn try_from(value: Component) -> Result<Self, Self::Error> {
491        match value {
492            Component::TextInput(inner) => Ok(inner),
493            _ => Err(value),
494        }
495    }
496}
497
498impl TryFrom<Component> for Thumbnail {
499    type Error = Component;
500
501    fn try_from(value: Component) -> Result<Self, Self::Error> {
502        match value {
503            Component::Thumbnail(inner) => Ok(inner),
504            _ => Err(value),
505        }
506    }
507}
508
509impl<'de> Deserialize<'de> for Component {
510    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
511        deserializer.deserialize_any(ComponentVisitor)
512    }
513}
514
515#[derive(Debug, Deserialize)]
516#[serde(field_identifier, rename_all = "snake_case")]
517enum Field {
518    AccentColor,
519    Accessory,
520    ChannelTypes,
521    Component,
522    Components,
523    Content,
524    CustomId,
525    Default,
526    DefaultValues,
527    Description,
528    Disabled,
529    Divider,
530    Emoji,
531    File,
532    FileTypes,
533    Id,
534    Items,
535    Label,
536    MaxLength,
537    MaxValues,
538    Media,
539    MinLength,
540    MinValues,
541    Options,
542    Placeholder,
543    Required,
544    SkuId,
545    Spacing,
546    Spoiler,
547    Style,
548    Type,
549    Url,
550    Value,
551}
552
553struct ComponentVisitor;
554
555impl<'de> Visitor<'de> for ComponentVisitor {
556    type Value = Component;
557
558    fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
559        f.write_str("struct Component")
560    }
561
562    #[allow(clippy::too_many_lines)]
563    fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Self::Value, V::Error> {
564        // Required fields.
565        let mut components: Option<Vec<Component>> = None;
566        let mut kind: Option<ComponentType> = None;
567        let mut options: Option<Value> = None;
568        let mut style: Option<Value> = None;
569
570        // Liminal fields.
571        let mut custom_id: Option<Option<Value>> = None;
572        let mut label: Option<Option<String>> = None;
573
574        // Optional fields.
575        let mut channel_types: Option<Vec<ChannelType>> = None;
576        let mut default_values: Option<Vec<SelectDefaultValue>> = None;
577        let mut disabled: Option<bool> = None;
578        let mut emoji: Option<Option<EmojiReactionType>> = None;
579        let mut max_length: Option<Option<u16>> = None;
580        let mut max_values: Option<Option<u8>> = None;
581        let mut min_length: Option<Option<u16>> = None;
582        let mut min_values: Option<Option<u8>> = None;
583        let mut placeholder: Option<Option<String>> = None;
584        let mut required: Option<Option<bool>> = None;
585        let mut url: Option<Option<String>> = None;
586        let mut sku_id: Option<Id<SkuMarker>> = None;
587        let mut value: Option<Option<String>> = None;
588        let mut default: Option<bool> = None;
589
590        let mut id: Option<i32> = None;
591        let mut content: Option<String> = None;
592        let mut items: Option<Vec<MediaGalleryItem>> = None;
593        let mut divider: Option<bool> = None;
594        let mut spacing: Option<SeparatorSpacingSize> = None;
595        let mut file: Option<UnfurledMediaItem> = None;
596        let mut spoiler: Option<bool> = None;
597        let mut accessory: Option<Component> = None;
598        let mut media: Option<UnfurledMediaItem> = None;
599        let mut description: Option<Option<String>> = None;
600        let mut accent_color: Option<Option<u32>> = None;
601        let mut component: Option<Component> = None;
602        let mut file_types: Option<Option<Vec<String>>> = None;
603
604        loop {
605            let key = match map.next_key() {
606                Ok(Some(key)) => key,
607                Ok(None) => break,
608                Err(_) => {
609                    map.next_value::<IgnoredAny>()?;
610
611                    continue;
612                }
613            };
614
615            match key {
616                Field::ChannelTypes => {
617                    if channel_types.is_some() {
618                        return Err(DeError::duplicate_field("channel_types"));
619                    }
620
621                    channel_types = Some(map.next_value()?);
622                }
623                Field::Components => {
624                    if components.is_some() {
625                        return Err(DeError::duplicate_field("components"));
626                    }
627
628                    components = Some(map.next_value()?);
629                }
630                Field::CustomId => {
631                    if custom_id.is_some() {
632                        return Err(DeError::duplicate_field("custom_id"));
633                    }
634
635                    custom_id = Some(map.next_value()?);
636                }
637                Field::Default => {
638                    if default.is_some() {
639                        return Err(DeError::duplicate_field("default"));
640                    }
641
642                    default = Some(map.next_value()?)
643                }
644                Field::DefaultValues => {
645                    if default_values.is_some() {
646                        return Err(DeError::duplicate_field("default_values"));
647                    }
648
649                    default_values = map.next_value()?;
650                }
651                Field::Disabled => {
652                    if disabled.is_some() {
653                        return Err(DeError::duplicate_field("disabled"));
654                    }
655
656                    disabled = Some(map.next_value()?);
657                }
658                Field::Emoji => {
659                    if emoji.is_some() {
660                        return Err(DeError::duplicate_field("emoji"));
661                    }
662
663                    emoji = Some(map.next_value()?);
664                }
665                Field::FileTypes => {
666                    if file_types.is_some() {
667                        return Err(DeError::duplicate_field("file_types"));
668                    }
669
670                    file_types = Some(map.next_value()?);
671                }
672                Field::Label => {
673                    if label.is_some() {
674                        return Err(DeError::duplicate_field("label"));
675                    }
676
677                    label = Some(map.next_value()?);
678                }
679                Field::MaxLength => {
680                    if max_length.is_some() {
681                        return Err(DeError::duplicate_field("max_length"));
682                    }
683
684                    max_length = Some(map.next_value()?);
685                }
686                Field::MaxValues => {
687                    if max_values.is_some() {
688                        return Err(DeError::duplicate_field("max_values"));
689                    }
690
691                    max_values = Some(map.next_value()?);
692                }
693                Field::MinLength => {
694                    if min_length.is_some() {
695                        return Err(DeError::duplicate_field("min_length"));
696                    }
697
698                    min_length = Some(map.next_value()?);
699                }
700                Field::MinValues => {
701                    if min_values.is_some() {
702                        return Err(DeError::duplicate_field("min_values"));
703                    }
704
705                    min_values = Some(map.next_value()?);
706                }
707                Field::Options => {
708                    if options.is_some() {
709                        return Err(DeError::duplicate_field("options"));
710                    }
711
712                    options = Some(map.next_value()?);
713                }
714                Field::Placeholder => {
715                    if placeholder.is_some() {
716                        return Err(DeError::duplicate_field("placeholder"));
717                    }
718
719                    placeholder = Some(map.next_value()?);
720                }
721                Field::Required => {
722                    if required.is_some() {
723                        return Err(DeError::duplicate_field("required"));
724                    }
725
726                    required = Some(map.next_value()?);
727                }
728                Field::Style => {
729                    if style.is_some() {
730                        return Err(DeError::duplicate_field("style"));
731                    }
732
733                    style = Some(map.next_value()?);
734                }
735                Field::Type => {
736                    if kind.is_some() {
737                        return Err(DeError::duplicate_field("type"));
738                    }
739
740                    kind = Some(map.next_value()?);
741                }
742                Field::Url => {
743                    if url.is_some() {
744                        return Err(DeError::duplicate_field("url"));
745                    }
746
747                    url = Some(map.next_value()?);
748                }
749                Field::SkuId => {
750                    if sku_id.is_some() {
751                        return Err(DeError::duplicate_field("sku_id"));
752                    }
753
754                    sku_id = map.next_value()?;
755                }
756                Field::Value => {
757                    if value.is_some() {
758                        return Err(DeError::duplicate_field("value"));
759                    }
760
761                    value = Some(map.next_value()?);
762                }
763                Field::Id => {
764                    if id.is_some() {
765                        return Err(DeError::duplicate_field("id"));
766                    }
767
768                    id = Some(map.next_value()?);
769                }
770                Field::Content => {
771                    if content.is_some() {
772                        return Err(DeError::duplicate_field("content"));
773                    }
774
775                    content = Some(map.next_value()?);
776                }
777                Field::Items => {
778                    if items.is_some() {
779                        return Err(DeError::duplicate_field("items"));
780                    }
781
782                    items = Some(map.next_value()?);
783                }
784                Field::Divider => {
785                    if divider.is_some() {
786                        return Err(DeError::duplicate_field("divider"));
787                    }
788
789                    divider = Some(map.next_value()?);
790                }
791                Field::Spacing => {
792                    if spacing.is_some() {
793                        return Err(DeError::duplicate_field("spacing"));
794                    }
795
796                    spacing = Some(map.next_value()?);
797                }
798                Field::File => {
799                    if file.is_some() {
800                        return Err(DeError::duplicate_field("file"));
801                    }
802
803                    file = Some(map.next_value()?);
804                }
805                Field::Spoiler => {
806                    if spoiler.is_some() {
807                        return Err(DeError::duplicate_field("spoiler"));
808                    }
809
810                    spoiler = Some(map.next_value()?);
811                }
812                Field::Accessory => {
813                    if accessory.is_some() {
814                        return Err(DeError::duplicate_field("accessory"));
815                    }
816
817                    accessory = Some(map.next_value()?);
818                }
819                Field::Media => {
820                    if media.is_some() {
821                        return Err(DeError::duplicate_field("media"));
822                    }
823
824                    media = Some(map.next_value()?);
825                }
826                Field::Description => {
827                    if description.is_some() {
828                        return Err(DeError::duplicate_field("description"));
829                    }
830
831                    description = Some(map.next_value()?);
832                }
833                Field::AccentColor => {
834                    if accent_color.is_some() {
835                        return Err(DeError::duplicate_field("accent_color"));
836                    }
837
838                    accent_color = Some(map.next_value()?);
839                }
840                Field::Component => {
841                    if component.is_some() {
842                        return Err(DeError::duplicate_field("component"));
843                    }
844
845                    component = Some(map.next_value()?);
846                }
847            }
848        }
849
850        let kind = kind.ok_or_else(|| DeError::missing_field("type"))?;
851
852        Ok(match kind {
853            // Required fields:
854            // - components
855            ComponentType::ActionRow => {
856                let components = components.ok_or_else(|| DeError::missing_field("components"))?;
857
858                Self::Value::ActionRow(ActionRow { components, id })
859            }
860            // Required fields:
861            // - style
862            //
863            // Optional fields:
864            // - custom_id
865            // - disabled
866            // - emoji
867            // - label
868            // - url
869            // - sku_id
870            ComponentType::Button => {
871                let style = style
872                    .ok_or_else(|| DeError::missing_field("style"))?
873                    .deserialize_into()
874                    .map_err(DeserializerError::into_error)?;
875
876                let custom_id = custom_id
877                    .flatten()
878                    .map(Value::deserialize_into)
879                    .transpose()
880                    .map_err(DeserializerError::into_error)?;
881
882                Self::Value::Button(Button {
883                    custom_id,
884                    disabled: disabled.unwrap_or_default(),
885                    emoji: emoji.unwrap_or_default(),
886                    label: label.flatten(),
887                    style,
888                    url: url.unwrap_or_default(),
889                    sku_id,
890                    id,
891                })
892            }
893            // Required fields:
894            // - custom_id
895            // - options (if this is a text select menu)
896            //
897            // Optional fields:
898            // - default_values
899            // - disabled
900            // - max_values
901            // - min_values
902            // - placeholder
903            // - channel_types (if this is a channel select menu)
904            // - required
905            kind @ (ComponentType::TextSelectMenu
906            | ComponentType::UserSelectMenu
907            | ComponentType::RoleSelectMenu
908            | ComponentType::MentionableSelectMenu
909            | ComponentType::ChannelSelectMenu) => {
910                // Verify the individual variants' required fields
911                if kind == ComponentType::TextSelectMenu && options.is_none() {
912                    return Err(DeError::missing_field("options"));
913                }
914
915                let custom_id = custom_id
916                    .flatten()
917                    .ok_or_else(|| DeError::missing_field("custom_id"))?
918                    .deserialize_into()
919                    .map_err(DeserializerError::into_error)?;
920
921                Self::Value::SelectMenu(SelectMenu {
922                    channel_types,
923                    custom_id,
924                    default_values,
925                    disabled: disabled.unwrap_or_default(),
926                    kind: match kind {
927                        ComponentType::TextSelectMenu => SelectMenuType::Text,
928                        ComponentType::UserSelectMenu => SelectMenuType::User,
929                        ComponentType::RoleSelectMenu => SelectMenuType::Role,
930                        ComponentType::MentionableSelectMenu => SelectMenuType::Mentionable,
931                        ComponentType::ChannelSelectMenu => SelectMenuType::Channel,
932                        // This branch is unreachable unless we add a new type above and forget to
933                        // also add it here
934                        _ => {
935                            unreachable!("select menu component type is only partially implemented")
936                        }
937                    },
938                    max_values: max_values.unwrap_or_default(),
939                    min_values: min_values.unwrap_or_default(),
940                    options: options
941                        .map(Value::deserialize_into)
942                        .transpose()
943                        .map_err(DeserializerError::into_error)?,
944                    placeholder: placeholder.unwrap_or_default(),
945                    id,
946                    required: required.unwrap_or_default(),
947                })
948            }
949            // Required fields:
950            // - custom_id
951            // - style
952            //
953            // Optional fields:
954            // - label
955            // - max_length
956            // - min_length
957            // - placeholder
958            // - required
959            // - value
960            ComponentType::TextInput => {
961                let custom_id = custom_id
962                    .flatten()
963                    .ok_or_else(|| DeError::missing_field("custom_id"))?
964                    .deserialize_into()
965                    .map_err(DeserializerError::into_error)?;
966
967                let style = style
968                    .ok_or_else(|| DeError::missing_field("style"))?
969                    .deserialize_into()
970                    .map_err(DeserializerError::into_error)?;
971
972                #[allow(deprecated)]
973                Self::Value::TextInput(TextInput {
974                    custom_id,
975                    label: label.unwrap_or_default(),
976                    max_length: max_length.unwrap_or_default(),
977                    min_length: min_length.unwrap_or_default(),
978                    placeholder: placeholder.unwrap_or_default(),
979                    required: required.unwrap_or_default(),
980                    style,
981                    value: value.unwrap_or_default(),
982                    id,
983                })
984            }
985            ComponentType::TextDisplay => {
986                let content = content.ok_or_else(|| DeError::missing_field("content"))?;
987
988                Self::Value::TextDisplay(TextDisplay { content, id })
989            }
990            ComponentType::MediaGallery => {
991                let items = items.ok_or_else(|| DeError::missing_field("items"))?;
992
993                Self::Value::MediaGallery(MediaGallery { id, items })
994            }
995            ComponentType::Separator => Self::Value::Separator(Separator {
996                divider,
997                id,
998                spacing,
999            }),
1000            ComponentType::File => {
1001                let file = file.ok_or_else(|| DeError::missing_field("file"))?;
1002
1003                Self::Value::File(FileDisplay { file, id, spoiler })
1004            }
1005            ComponentType::Unknown(unknown) => Self::Value::Unknown(unknown),
1006            ComponentType::Section => {
1007                let components = components.ok_or_else(|| DeError::missing_field("components"))?;
1008                let accessory = accessory.ok_or_else(|| DeError::missing_field("accessory"))?;
1009                Self::Value::Section(Section {
1010                    id,
1011                    components,
1012                    accessory: Box::new(accessory),
1013                })
1014            }
1015            ComponentType::Thumbnail => {
1016                let media = media.ok_or_else(|| DeError::missing_field("media"))?;
1017                Self::Value::Thumbnail(Thumbnail {
1018                    description,
1019                    id,
1020                    media,
1021                    spoiler,
1022                })
1023            }
1024            ComponentType::Container => {
1025                let components = components.ok_or_else(|| DeError::missing_field("components"))?;
1026                Self::Value::Container(Container {
1027                    accent_color,
1028                    components,
1029                    id,
1030                    spoiler,
1031                })
1032            }
1033            ComponentType::Label => {
1034                let label = label
1035                    .flatten()
1036                    .ok_or_else(|| DeError::missing_field("label"))?;
1037                let component = component.ok_or_else(|| DeError::missing_field("component"))?;
1038                Self::Value::Label(Label {
1039                    id,
1040                    label,
1041                    description: description.unwrap_or_default(),
1042                    component: Box::new(component),
1043                })
1044            }
1045            ComponentType::FileUpload => {
1046                let custom_id = custom_id
1047                    .flatten()
1048                    .ok_or_else(|| DeError::missing_field("custom_id"))?
1049                    .deserialize_into()
1050                    .map_err(DeserializerError::into_error)?;
1051
1052                Self::Value::FileUpload(FileUpload {
1053                    id,
1054                    custom_id,
1055                    max_values: max_values.unwrap_or_default(),
1056                    min_values: min_values.unwrap_or_default(),
1057                    required: required.unwrap_or_default(),
1058                    file_types: file_types.unwrap_or_default(),
1059                })
1060            }
1061            ComponentType::CheckboxGroup => {
1062                let custom_id = custom_id
1063                    .flatten()
1064                    .ok_or_else(|| DeError::missing_field("custom_id"))?
1065                    .deserialize_into()
1066                    .map_err(DeserializerError::into_error)?;
1067
1068                let options = options
1069                    .ok_or_else(|| DeError::missing_field("options"))?
1070                    .deserialize_into()
1071                    .map_err(DeserializerError::into_error)?;
1072
1073                Self::Value::CheckboxGroup(CheckboxGroup {
1074                    id,
1075                    custom_id,
1076                    options,
1077                    min_values: min_values.unwrap_or_default(),
1078                    max_values: max_values.unwrap_or_default(),
1079                    required: required.unwrap_or_default(),
1080                })
1081            }
1082            ComponentType::Checkbox => {
1083                let custom_id = custom_id
1084                    .flatten()
1085                    .ok_or_else(|| DeError::missing_field("custom_id"))?
1086                    .deserialize_into()
1087                    .map_err(DeserializerError::into_error)?;
1088                Self::Value::Checkbox(Checkbox {
1089                    custom_id,
1090                    default,
1091                    id,
1092                })
1093            }
1094        })
1095    }
1096}
1097
1098impl Serialize for Component {
1099    #[allow(clippy::too_many_lines)]
1100    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1101        let len = match self {
1102            // Required fields:
1103            // - type
1104            // - components
1105            //
1106            // Optional fields:
1107            // - id
1108            Component::ActionRow(row) => 2 + usize::from(row.id.is_some()),
1109            // Required fields:
1110            // - type
1111            // - style
1112            //
1113            // Optional fields:
1114            // - id
1115            // - custom_id
1116            // - disabled
1117            // - emoji
1118            // - label
1119            // - url
1120            // - sku_id
1121            Component::Button(button) => {
1122                2 + usize::from(button.custom_id.is_some())
1123                    + usize::from(button.disabled)
1124                    + usize::from(button.emoji.is_some())
1125                    + usize::from(button.label.is_some())
1126                    + usize::from(button.url.is_some())
1127                    + usize::from(button.sku_id.is_some())
1128                    + usize::from(button.id.is_some())
1129            }
1130            // Required fields:
1131            // - custom_id
1132            // - options (for text select menus)
1133            // - type
1134            //
1135            // Optional fields:
1136            // - id
1137            // - channel_types (for channel select menus)
1138            // - default_values
1139            // - disabled
1140            // - max_values
1141            // - min_values
1142            // - placeholder
1143            // - required
1144            Component::SelectMenu(select_menu) => {
1145                // We ignore text menus that don't include the `options` field, as those are
1146                // detected later in the serialization process
1147                2 + usize::from(select_menu.channel_types.is_some())
1148                    + usize::from(select_menu.default_values.is_some())
1149                    + usize::from(select_menu.disabled)
1150                    + usize::from(select_menu.max_values.is_some())
1151                    + usize::from(select_menu.min_values.is_some())
1152                    + usize::from(select_menu.options.is_some())
1153                    + usize::from(select_menu.placeholder.is_some())
1154                    + usize::from(select_menu.id.is_some())
1155                    + usize::from(select_menu.required.is_some())
1156            }
1157            // Required fields:
1158            // - custom_id
1159            // - style
1160            // - type
1161            //
1162            // Optional fields:
1163            // - id
1164            // - label
1165            // - max_length
1166            // - min_length
1167            // - placeholder
1168            // - required
1169            // - value
1170            #[allow(deprecated)]
1171            Component::TextInput(text_input) => {
1172                3 + usize::from(text_input.label.is_some())
1173                    + usize::from(text_input.max_length.is_some())
1174                    + usize::from(text_input.min_length.is_some())
1175                    + usize::from(text_input.placeholder.is_some())
1176                    + usize::from(text_input.required.is_some())
1177                    + usize::from(text_input.value.is_some())
1178                    + usize::from(text_input.id.is_some())
1179            }
1180            // Required fields:
1181            // - type
1182            // - content
1183            // Optional fields:
1184            // - id
1185            Component::TextDisplay(text_display) => 2 + usize::from(text_display.id.is_some()),
1186            // Required fields:
1187            // - type
1188            // - items
1189            // Optional fields:
1190            // - id
1191            Component::MediaGallery(media_gallery) => 2 + usize::from(media_gallery.id.is_some()),
1192            // Required fields:
1193            // - type
1194            // Optional fields:
1195            // - id
1196            // - divider
1197            // - spacing
1198            Component::Separator(separator) => {
1199                1 + usize::from(separator.divider.is_some())
1200                    + usize::from(separator.spacing.is_some())
1201                    + usize::from(separator.id.is_some())
1202            }
1203            // Required fields:
1204            // - type
1205            // - file
1206            // Optional fields:
1207            // - id
1208            // - spoiler
1209            Component::File(file) => {
1210                2 + usize::from(file.spoiler.is_some()) + usize::from(file.id.is_some())
1211            }
1212            // Required fields:
1213            // - type
1214            // - components
1215            // - accessory
1216            // Optional fields:
1217            // - id
1218            Component::Section(section) => 3 + usize::from(section.id.is_some()),
1219            // Required fields:
1220            // - type
1221            // - components
1222            // Optional fields:
1223            // - id
1224            // - accent_color
1225            // - spoiler
1226            Component::Container(container) => {
1227                2 + usize::from(container.accent_color.is_some())
1228                    + usize::from(container.spoiler.is_some())
1229                    + usize::from(container.id.is_some())
1230            }
1231            // Required fields:
1232            // - type
1233            // - media
1234            // Optional fields:
1235            // - id
1236            // - description
1237            // - spoiler
1238            Component::Thumbnail(thumbnail) => {
1239                2 + usize::from(thumbnail.spoiler.is_some())
1240                    + usize::from(thumbnail.description.is_some())
1241                    + usize::from(thumbnail.id.is_some())
1242            }
1243            // Required fields:
1244            // - type
1245            // - label
1246            // - component
1247            // Optional fields:
1248            // - id
1249            // - description
1250            Component::Label(label) => {
1251                3 + usize::from(label.description.is_some()) + usize::from(label.id.is_some())
1252            }
1253            // Required fields:
1254            // - type
1255            // - custom_id
1256            // Optional fields:
1257            // - id
1258            // - min_values
1259            // - max_values
1260            // - file_types
1261            // - required
1262            Component::FileUpload(file_upload) => {
1263                2 + usize::from(file_upload.min_values.is_some())
1264                    + usize::from(file_upload.max_values.is_some())
1265                    + usize::from(file_upload.required.is_some())
1266                    + usize::from(file_upload.id.is_some())
1267                    + usize::from(file_upload.file_types.is_some())
1268            }
1269            Component::CheckboxGroup(checkbox_group) => {
1270                3 + usize::from(checkbox_group.id.is_some())
1271                    + usize::from(checkbox_group.min_values.is_some())
1272                    + usize::from(checkbox_group.max_values.is_some())
1273                    + usize::from(checkbox_group.required.is_some())
1274            }
1275            Component::Checkbox(checkbox) => {
1276                2 + usize::from(checkbox.id.is_some()) + usize::from(checkbox.default.is_some())
1277            }
1278            // We are dropping fields here but nothing we can do about that for
1279            // the time being.
1280            Component::Unknown(_) => 1,
1281        };
1282
1283        let mut state = serializer.serialize_struct("Component", len)?;
1284
1285        match self {
1286            Component::ActionRow(action_row) => {
1287                state.serialize_field("type", &ComponentType::ActionRow)?;
1288                if let Some(id) = action_row.id {
1289                    state.serialize_field("id", &id)?;
1290                }
1291
1292                state.serialize_field("components", &action_row.components)?;
1293            }
1294            Component::Button(button) => {
1295                state.serialize_field("type", &ComponentType::Button)?;
1296                if let Some(id) = button.id {
1297                    state.serialize_field("id", &id)?;
1298                }
1299
1300                if button.custom_id.is_some() {
1301                    state.serialize_field("custom_id", &button.custom_id)?;
1302                }
1303
1304                if button.disabled {
1305                    state.serialize_field("disabled", &button.disabled)?;
1306                }
1307
1308                if button.emoji.is_some() {
1309                    state.serialize_field("emoji", &button.emoji)?;
1310                }
1311
1312                if button.label.is_some() {
1313                    state.serialize_field("label", &button.label)?;
1314                }
1315
1316                state.serialize_field("style", &button.style)?;
1317
1318                if button.url.is_some() {
1319                    state.serialize_field("url", &button.url)?;
1320                }
1321
1322                if button.sku_id.is_some() {
1323                    state.serialize_field("sku_id", &button.sku_id)?;
1324                }
1325            }
1326            Component::SelectMenu(select_menu) => {
1327                match &select_menu.kind {
1328                    SelectMenuType::Text => {
1329                        state.serialize_field("type", &ComponentType::TextSelectMenu)?;
1330                        if let Some(id) = select_menu.id {
1331                            state.serialize_field("id", &id)?;
1332                        }
1333
1334                        state.serialize_field(
1335                            "options",
1336                            &select_menu.options.as_ref().ok_or(SerError::custom(
1337                                "required field \"option\" missing for text select menu",
1338                            ))?,
1339                        )?;
1340                    }
1341                    SelectMenuType::User => {
1342                        state.serialize_field("type", &ComponentType::UserSelectMenu)?;
1343                        if let Some(id) = select_menu.id {
1344                            state.serialize_field("id", &id)?;
1345                        }
1346                    }
1347                    SelectMenuType::Role => {
1348                        state.serialize_field("type", &ComponentType::RoleSelectMenu)?;
1349                        if let Some(id) = select_menu.id {
1350                            state.serialize_field("id", &id)?;
1351                        }
1352                    }
1353                    SelectMenuType::Mentionable => {
1354                        state.serialize_field("type", &ComponentType::MentionableSelectMenu)?;
1355                        if let Some(id) = select_menu.id {
1356                            state.serialize_field("id", &id)?;
1357                        }
1358                    }
1359                    SelectMenuType::Channel => {
1360                        state.serialize_field("type", &ComponentType::ChannelSelectMenu)?;
1361                        if let Some(id) = select_menu.id {
1362                            state.serialize_field("id", &id)?;
1363                        }
1364
1365                        if let Some(channel_types) = &select_menu.channel_types {
1366                            state.serialize_field("channel_types", channel_types)?;
1367                        }
1368                    }
1369                }
1370
1371                // Due to `custom_id` being required in some variants and
1372                // optional in others, serialize as an Option.
1373                state.serialize_field("custom_id", &Some(&select_menu.custom_id))?;
1374
1375                if select_menu.default_values.is_some() {
1376                    state.serialize_field("default_values", &select_menu.default_values)?;
1377                }
1378
1379                state.serialize_field("disabled", &select_menu.disabled)?;
1380
1381                if select_menu.max_values.is_some() {
1382                    state.serialize_field("max_values", &select_menu.max_values)?;
1383                }
1384
1385                if select_menu.min_values.is_some() {
1386                    state.serialize_field("min_values", &select_menu.min_values)?;
1387                }
1388
1389                if select_menu.placeholder.is_some() {
1390                    state.serialize_field("placeholder", &select_menu.placeholder)?;
1391                }
1392
1393                if select_menu.required.is_some() {
1394                    state.serialize_field("required", &select_menu.required)?;
1395                }
1396            }
1397            Component::TextInput(text_input) => {
1398                state.serialize_field("type", &ComponentType::TextInput)?;
1399                if let Some(id) = text_input.id {
1400                    state.serialize_field("id", &id)?;
1401                }
1402
1403                // Due to `custom_id` being required in some
1404                // variants and optional in others, serialize as an Option.
1405                state.serialize_field("custom_id", &Some(&text_input.custom_id))?;
1406
1407                #[allow(deprecated)]
1408                if text_input.label.is_some() {
1409                    state.serialize_field("label", &text_input.label)?;
1410                }
1411
1412                if text_input.max_length.is_some() {
1413                    state.serialize_field("max_length", &text_input.max_length)?;
1414                }
1415
1416                if text_input.min_length.is_some() {
1417                    state.serialize_field("min_length", &text_input.min_length)?;
1418                }
1419
1420                if text_input.placeholder.is_some() {
1421                    state.serialize_field("placeholder", &text_input.placeholder)?;
1422                }
1423
1424                if text_input.required.is_some() {
1425                    state.serialize_field("required", &text_input.required)?;
1426                }
1427
1428                state.serialize_field("style", &text_input.style)?;
1429
1430                if text_input.value.is_some() {
1431                    state.serialize_field("value", &text_input.value)?;
1432                }
1433            }
1434            Component::TextDisplay(text_display) => {
1435                state.serialize_field("type", &ComponentType::TextDisplay)?;
1436                if let Some(id) = text_display.id {
1437                    state.serialize_field("id", &id)?;
1438                }
1439
1440                state.serialize_field("content", &text_display.content)?;
1441            }
1442            Component::MediaGallery(media_gallery) => {
1443                state.serialize_field("type", &ComponentType::MediaGallery)?;
1444                if let Some(id) = media_gallery.id {
1445                    state.serialize_field("id", &id)?;
1446                }
1447
1448                state.serialize_field("items", &media_gallery.items)?;
1449            }
1450            Component::Separator(separator) => {
1451                state.serialize_field("type", &ComponentType::Separator)?;
1452                if let Some(id) = separator.id {
1453                    state.serialize_field("id", &id)?;
1454                }
1455                if let Some(divider) = separator.divider {
1456                    state.serialize_field("divider", &divider)?;
1457                }
1458                if let Some(spacing) = &separator.spacing {
1459                    state.serialize_field("spacing", spacing)?;
1460                }
1461            }
1462            Component::File(file) => {
1463                state.serialize_field("type", &ComponentType::File)?;
1464                if let Some(id) = file.id {
1465                    state.serialize_field("id", &id)?;
1466                }
1467
1468                state.serialize_field("file", &file.file)?;
1469                if let Some(spoiler) = file.spoiler {
1470                    state.serialize_field("spoiler", &spoiler)?;
1471                }
1472            }
1473            Component::Section(section) => {
1474                state.serialize_field("type", &ComponentType::Section)?;
1475                if let Some(id) = section.id {
1476                    state.serialize_field("id", &id)?;
1477                }
1478
1479                state.serialize_field("components", &section.components)?;
1480                state.serialize_field("accessory", &section.accessory)?;
1481            }
1482            Component::Container(container) => {
1483                state.serialize_field("type", &ComponentType::Container)?;
1484                if let Some(id) = container.id {
1485                    state.serialize_field("id", &id)?;
1486                }
1487
1488                if let Some(accent_color) = container.accent_color {
1489                    state.serialize_field("accent_color", &accent_color)?;
1490                }
1491                if let Some(spoiler) = container.spoiler {
1492                    state.serialize_field("spoiler", &spoiler)?;
1493                }
1494                state.serialize_field("components", &container.components)?;
1495            }
1496            Component::Thumbnail(thumbnail) => {
1497                state.serialize_field("type", &ComponentType::Thumbnail)?;
1498                if let Some(id) = thumbnail.id {
1499                    state.serialize_field("id", &id)?;
1500                }
1501
1502                state.serialize_field("media", &thumbnail.media)?;
1503                if let Some(description) = &thumbnail.description {
1504                    state.serialize_field("description", description)?;
1505                }
1506                if let Some(spoiler) = thumbnail.spoiler {
1507                    state.serialize_field("spoiler", &spoiler)?;
1508                }
1509            }
1510            Component::Label(label) => {
1511                state.serialize_field("type", &ComponentType::Label)?;
1512                if label.id.is_some() {
1513                    state.serialize_field("id", &label.id)?;
1514                }
1515                // Due to `label` being required in some
1516                // variants and optional in others, serialize as an Option.
1517                state.serialize_field("label", &Some(&label.label))?;
1518                if label.description.is_some() {
1519                    state.serialize_field("description", &label.description)?;
1520                }
1521                state.serialize_field("component", &label.component)?;
1522            }
1523            Component::FileUpload(file_upload) => {
1524                state.serialize_field("type", &ComponentType::FileUpload)?;
1525                if file_upload.id.is_some() {
1526                    state.serialize_field("id", &file_upload.id)?;
1527                }
1528
1529                // Due to `custom_id` being required in some variants and
1530                // optional in others, serialize as an Option.
1531                state.serialize_field("custom_id", &Some(&file_upload.custom_id))?;
1532                if file_upload.min_values.is_some() {
1533                    state.serialize_field("min_values", &file_upload.min_values)?;
1534                }
1535                if file_upload.max_values.is_some() {
1536                    state.serialize_field("max_values", &file_upload.max_values)?;
1537                }
1538                if file_upload.required.is_some() {
1539                    state.serialize_field("required", &file_upload.required)?;
1540                }
1541                if file_upload.file_types.is_some() {
1542                    state.serialize_field("file_types", &file_upload.file_types)?;
1543                }
1544            }
1545            Component::CheckboxGroup(checkbox_group) => {
1546                state.serialize_field("type", &ComponentType::CheckboxGroup)?;
1547                if checkbox_group.id.is_some() {
1548                    state.serialize_field("id", &checkbox_group.id)?;
1549                }
1550                state.serialize_field("custom_id", &Some(&checkbox_group.custom_id))?;
1551                state.serialize_field("options", &checkbox_group.options)?;
1552                if checkbox_group.min_values.is_some() {
1553                    state.serialize_field("min_values", &checkbox_group.min_values)?;
1554                }
1555                if checkbox_group.max_values.is_some() {
1556                    state.serialize_field("max_values", &checkbox_group.max_values)?;
1557                }
1558                if checkbox_group.required.is_some() {
1559                    state.serialize_field("required", &checkbox_group.required)?;
1560                }
1561            }
1562            Component::Checkbox(checkbox) => {
1563                state.serialize_field("type", &ComponentType::Checkbox)?;
1564                if checkbox.id.is_some() {
1565                    state.serialize_field("id", &checkbox.id)?;
1566                }
1567                state.serialize_field("custom_id", &Some(&checkbox.custom_id))?;
1568                if checkbox.default.is_some() {
1569                    state.serialize_field("default", &checkbox.default)?;
1570                }
1571            }
1572
1573            // We are not serializing all fields so this will fail to
1574            // deserialize. But it is all that can be done to avoid losing
1575            // incoming messages at this time.
1576            Component::Unknown(unknown) => {
1577                state.serialize_field("type", &ComponentType::Unknown(*unknown))?;
1578            }
1579        }
1580
1581        state.end()
1582    }
1583}
1584
1585#[cfg(test)]
1586mod tests {
1587    // Required due to the use of a unicode emoji in a constant.
1588    #![allow(clippy::non_ascii_literal)]
1589
1590    use super::*;
1591    use crate::id::Id;
1592    use serde_test::Token;
1593    use static_assertions::assert_impl_all;
1594
1595    assert_impl_all!(
1596        Component: From<ActionRow>,
1597        From<Button>,
1598        From<SelectMenu>,
1599        From<TextInput>
1600    );
1601
1602    #[allow(clippy::too_many_lines)]
1603    #[test]
1604    fn component_full() {
1605        let component = Component::ActionRow(ActionRow {
1606            components: Vec::from([
1607                Component::Button(Button {
1608                    custom_id: Some("test custom id".into()),
1609                    disabled: true,
1610                    emoji: None,
1611                    label: Some("test label".into()),
1612                    style: ButtonStyle::Primary,
1613                    url: None,
1614                    sku_id: None,
1615                    id: None,
1616                }),
1617                Component::SelectMenu(SelectMenu {
1618                    channel_types: None,
1619                    custom_id: "test custom id 2".into(),
1620                    default_values: None,
1621                    disabled: false,
1622                    kind: SelectMenuType::Text,
1623                    max_values: Some(25),
1624                    min_values: Some(5),
1625                    options: Some(Vec::from([SelectMenuOption {
1626                        label: "test option label".into(),
1627                        value: "test option value".into(),
1628                        description: Some("test description".into()),
1629                        emoji: None,
1630                        default: false,
1631                    }])),
1632                    placeholder: Some("test placeholder".into()),
1633                    id: None,
1634                    required: Some(true),
1635                }),
1636            ]),
1637            id: None,
1638        });
1639
1640        serde_test::assert_tokens(
1641            &component,
1642            &[
1643                Token::Struct {
1644                    name: "Component",
1645                    len: 2,
1646                },
1647                Token::Str("type"),
1648                Token::U8(ComponentType::ActionRow.into()),
1649                Token::Str("components"),
1650                Token::Seq { len: Some(2) },
1651                Token::Struct {
1652                    name: "Component",
1653                    len: 5,
1654                },
1655                Token::Str("type"),
1656                Token::U8(ComponentType::Button.into()),
1657                Token::Str("custom_id"),
1658                Token::Some,
1659                Token::Str("test custom id"),
1660                Token::Str("disabled"),
1661                Token::Bool(true),
1662                Token::Str("label"),
1663                Token::Some,
1664                Token::Str("test label"),
1665                Token::Str("style"),
1666                Token::U8(ButtonStyle::Primary.into()),
1667                Token::StructEnd,
1668                Token::Struct {
1669                    name: "Component",
1670                    len: 7,
1671                },
1672                Token::Str("type"),
1673                Token::U8(ComponentType::TextSelectMenu.into()),
1674                Token::Str("options"),
1675                Token::Seq { len: Some(1) },
1676                Token::Struct {
1677                    name: "SelectMenuOption",
1678                    len: 4,
1679                },
1680                Token::Str("default"),
1681                Token::Bool(false),
1682                Token::Str("description"),
1683                Token::Some,
1684                Token::Str("test description"),
1685                Token::Str("label"),
1686                Token::Str("test option label"),
1687                Token::Str("value"),
1688                Token::Str("test option value"),
1689                Token::StructEnd,
1690                Token::SeqEnd,
1691                Token::Str("custom_id"),
1692                Token::Some,
1693                Token::Str("test custom id 2"),
1694                Token::Str("disabled"),
1695                Token::Bool(false),
1696                Token::Str("max_values"),
1697                Token::Some,
1698                Token::U8(25),
1699                Token::Str("min_values"),
1700                Token::Some,
1701                Token::U8(5),
1702                Token::Str("placeholder"),
1703                Token::Some,
1704                Token::Str("test placeholder"),
1705                Token::Str("required"),
1706                Token::Some,
1707                Token::Bool(true),
1708                Token::StructEnd,
1709                Token::SeqEnd,
1710                Token::StructEnd,
1711            ],
1712        );
1713    }
1714
1715    #[test]
1716    fn action_row() {
1717        let value = Component::ActionRow(ActionRow {
1718            components: Vec::from([Component::Button(Button {
1719                custom_id: Some("button-1".to_owned()),
1720                disabled: false,
1721                emoji: None,
1722                style: ButtonStyle::Primary,
1723                label: Some("Button".to_owned()),
1724                url: None,
1725                sku_id: None,
1726                id: None,
1727            })]),
1728            id: None,
1729        });
1730
1731        serde_test::assert_tokens(
1732            &value,
1733            &[
1734                Token::Struct {
1735                    name: "Component",
1736                    len: 2,
1737                },
1738                Token::String("type"),
1739                Token::U8(ComponentType::ActionRow.into()),
1740                Token::String("components"),
1741                Token::Seq { len: Some(1) },
1742                Token::Struct {
1743                    name: "Component",
1744                    len: 4,
1745                },
1746                Token::String("type"),
1747                Token::U8(2),
1748                Token::String("custom_id"),
1749                Token::Some,
1750                Token::String("button-1"),
1751                Token::String("label"),
1752                Token::Some,
1753                Token::String("Button"),
1754                Token::String("style"),
1755                Token::U8(1),
1756                Token::StructEnd,
1757                Token::SeqEnd,
1758                Token::StructEnd,
1759            ],
1760        );
1761    }
1762
1763    #[test]
1764    fn button() {
1765        // Free Palestine.
1766        //
1767        // Palestinian Flag.
1768        const FLAG: &str = "🇵🇸";
1769
1770        let value = Component::Button(Button {
1771            custom_id: Some("test".to_owned()),
1772            disabled: false,
1773            emoji: Some(EmojiReactionType::Unicode {
1774                name: FLAG.to_owned(),
1775            }),
1776            label: Some("Test".to_owned()),
1777            style: ButtonStyle::Link,
1778            url: Some("https://twilight.rs".to_owned()),
1779            sku_id: None,
1780            id: None,
1781        });
1782
1783        serde_test::assert_tokens(
1784            &value,
1785            &[
1786                Token::Struct {
1787                    name: "Component",
1788                    len: 6,
1789                },
1790                Token::String("type"),
1791                Token::U8(ComponentType::Button.into()),
1792                Token::String("custom_id"),
1793                Token::Some,
1794                Token::String("test"),
1795                Token::String("emoji"),
1796                Token::Some,
1797                Token::Struct {
1798                    name: "EmojiReactionType",
1799                    len: 1,
1800                },
1801                Token::String("name"),
1802                Token::String(FLAG),
1803                Token::StructEnd,
1804                Token::String("label"),
1805                Token::Some,
1806                Token::String("Test"),
1807                Token::String("style"),
1808                Token::U8(ButtonStyle::Link.into()),
1809                Token::String("url"),
1810                Token::Some,
1811                Token::String("https://twilight.rs"),
1812                Token::StructEnd,
1813            ],
1814        );
1815    }
1816
1817    #[test]
1818    fn select_menu() {
1819        fn check_select(default_values: Option<Vec<(SelectDefaultValue, &'static str)>>) {
1820            let select_menu = Component::SelectMenu(SelectMenu {
1821                channel_types: None,
1822                custom_id: String::from("my_select"),
1823                default_values: default_values
1824                    .clone()
1825                    .map(|values| values.into_iter().map(|pair| pair.0).collect()),
1826                disabled: false,
1827                kind: SelectMenuType::User,
1828                max_values: None,
1829                min_values: None,
1830                options: None,
1831                placeholder: None,
1832                id: None,
1833                required: None,
1834            });
1835            let mut tokens = vec![
1836                Token::Struct {
1837                    name: "Component",
1838                    len: 2 + usize::from(default_values.is_some()),
1839                },
1840                Token::String("type"),
1841                Token::U8(ComponentType::UserSelectMenu.into()),
1842                Token::Str("custom_id"),
1843                Token::Some,
1844                Token::Str("my_select"),
1845            ];
1846            if let Some(default_values) = default_values {
1847                tokens.extend_from_slice(&[
1848                    Token::Str("default_values"),
1849                    Token::Some,
1850                    Token::Seq {
1851                        len: Some(default_values.len()),
1852                    },
1853                ]);
1854                for (_, id) in default_values {
1855                    tokens.extend_from_slice(&[
1856                        Token::Struct {
1857                            name: "SelectDefaultValue",
1858                            len: 2,
1859                        },
1860                        Token::Str("type"),
1861                        Token::UnitVariant {
1862                            name: "SelectDefaultValue",
1863                            variant: "user",
1864                        },
1865                        Token::Str("id"),
1866                        Token::NewtypeStruct { name: "Id" },
1867                        Token::Str(id),
1868                        Token::StructEnd,
1869                    ])
1870                }
1871                tokens.push(Token::SeqEnd);
1872            }
1873            tokens.extend_from_slice(&[
1874                Token::Str("disabled"),
1875                Token::Bool(false),
1876                Token::StructEnd,
1877            ]);
1878            serde_test::assert_tokens(&select_menu, &tokens);
1879        }
1880
1881        check_select(None);
1882        check_select(Some(vec![(
1883            SelectDefaultValue::User(Id::new(1234)),
1884            "1234",
1885        )]));
1886        check_select(Some(vec![
1887            (SelectDefaultValue::User(Id::new(1234)), "1234"),
1888            (SelectDefaultValue::User(Id::new(5432)), "5432"),
1889        ]));
1890    }
1891
1892    #[test]
1893    fn text_input() {
1894        #[allow(deprecated)]
1895        let value = Component::TextInput(TextInput {
1896            custom_id: "test".to_owned(),
1897            label: Some("The label".to_owned()),
1898            max_length: Some(100),
1899            min_length: Some(1),
1900            placeholder: Some("Taking this place".to_owned()),
1901            required: Some(true),
1902            style: TextInputStyle::Short,
1903            value: Some("Hello World!".to_owned()),
1904            id: None,
1905        });
1906
1907        serde_test::assert_tokens(
1908            &value,
1909            &[
1910                Token::Struct {
1911                    name: "Component",
1912                    len: 9,
1913                },
1914                Token::String("type"),
1915                Token::U8(ComponentType::TextInput.into()),
1916                Token::String("custom_id"),
1917                Token::Some,
1918                Token::String("test"),
1919                Token::String("label"),
1920                Token::Some,
1921                Token::String("The label"),
1922                Token::String("max_length"),
1923                Token::Some,
1924                Token::U16(100),
1925                Token::String("min_length"),
1926                Token::Some,
1927                Token::U16(1),
1928                Token::String("placeholder"),
1929                Token::Some,
1930                Token::String("Taking this place"),
1931                Token::String("required"),
1932                Token::Some,
1933                Token::Bool(true),
1934                Token::String("style"),
1935                Token::U8(TextInputStyle::Short as u8),
1936                Token::String("value"),
1937                Token::Some,
1938                Token::String("Hello World!"),
1939                Token::StructEnd,
1940            ],
1941        );
1942    }
1943
1944    #[test]
1945    fn premium_button() {
1946        let value = Component::Button(Button {
1947            custom_id: None,
1948            disabled: false,
1949            emoji: None,
1950            label: None,
1951            style: ButtonStyle::Premium,
1952            url: None,
1953            sku_id: Some(Id::new(114_941_315_417_899_012)),
1954            id: None,
1955        });
1956
1957        serde_test::assert_tokens(
1958            &value,
1959            &[
1960                Token::Struct {
1961                    name: "Component",
1962                    len: 3,
1963                },
1964                Token::String("type"),
1965                Token::U8(ComponentType::Button.into()),
1966                Token::String("style"),
1967                Token::U8(ButtonStyle::Premium.into()),
1968                Token::String("sku_id"),
1969                Token::Some,
1970                Token::NewtypeStruct { name: "Id" },
1971                Token::Str("114941315417899012"),
1972                Token::StructEnd,
1973            ],
1974        );
1975    }
1976
1977    #[test]
1978    fn label() {
1979        #[allow(deprecated)]
1980        let value = Component::Label(Label {
1981            id: None,
1982            label: "The label".to_owned(),
1983            description: Some("The description".to_owned()),
1984            component: Box::new(Component::TextInput(TextInput {
1985                id: None,
1986                custom_id: "The custom id".to_owned(),
1987                label: None,
1988                max_length: None,
1989                min_length: None,
1990                placeholder: None,
1991                required: None,
1992                style: TextInputStyle::Paragraph,
1993                value: None,
1994            })),
1995        });
1996
1997        serde_test::assert_tokens(
1998            &value,
1999            &[
2000                Token::Struct {
2001                    name: "Component",
2002                    len: 4,
2003                },
2004                Token::String("type"),
2005                Token::U8(ComponentType::Label.into()),
2006                Token::String("label"),
2007                Token::Some,
2008                Token::String("The label"),
2009                Token::String("description"),
2010                Token::Some,
2011                Token::String("The description"),
2012                Token::String("component"),
2013                Token::Struct {
2014                    name: "Component",
2015                    len: 3,
2016                },
2017                Token::String("type"),
2018                Token::U8(ComponentType::TextInput.into()),
2019                Token::String("custom_id"),
2020                Token::Some,
2021                Token::String("The custom id"),
2022                Token::String("style"),
2023                Token::U8(TextInputStyle::Paragraph as u8),
2024                Token::StructEnd,
2025                Token::StructEnd,
2026            ],
2027        );
2028    }
2029
2030    #[test]
2031    fn file_upload() {
2032        let value = Component::FileUpload(FileUpload {
2033            id: None,
2034            custom_id: "test".to_owned(),
2035            max_values: None,
2036            min_values: None,
2037            required: Some(true),
2038            file_types: None,
2039        });
2040
2041        serde_test::assert_tokens(
2042            &value,
2043            &[
2044                Token::Struct {
2045                    name: "Component",
2046                    len: 3,
2047                },
2048                Token::String("type"),
2049                Token::U8(ComponentType::FileUpload.into()),
2050                Token::String("custom_id"),
2051                Token::Some,
2052                Token::String("test"),
2053                Token::String("required"),
2054                Token::Some,
2055                Token::Bool(true),
2056                Token::StructEnd,
2057            ],
2058        )
2059    }
2060
2061    #[test]
2062    fn checkbox_group() {
2063        let value = Component::CheckboxGroup(CheckboxGroup {
2064            id: None,
2065            custom_id: "group".to_owned(),
2066            options: vec![CheckboxGroupOption {
2067                default: None,
2068                description: None,
2069                label: "Option A".to_owned(),
2070                value: "a".to_owned(),
2071            }],
2072            min_values: None,
2073            max_values: None,
2074            required: None,
2075        });
2076
2077        serde_test::assert_tokens(
2078            &value,
2079            &[
2080                Token::Struct {
2081                    name: "Component",
2082                    len: 3, // type, custom_id, options
2083                },
2084                Token::Str("type"),
2085                Token::U8(ComponentType::CheckboxGroup.into()),
2086                Token::Str("custom_id"),
2087                Token::Some,
2088                Token::Str("group"),
2089                Token::Str("options"),
2090                Token::Seq { len: Some(1) },
2091                Token::Struct {
2092                    name: "CheckboxGroupOption",
2093                    len: 2, // value, label
2094                },
2095                Token::Str("label"),
2096                Token::Str("Option A"),
2097                Token::Str("value"),
2098                Token::Str("a"),
2099                Token::StructEnd,
2100                Token::SeqEnd,
2101                Token::StructEnd,
2102            ],
2103        );
2104    }
2105    #[test]
2106    fn checkbox() {
2107        let value = Component::Checkbox(Checkbox {
2108            custom_id: "test".to_owned(),
2109            default: None,
2110            id: None,
2111        });
2112
2113        serde_test::assert_tokens(
2114            &value,
2115            &[
2116                Token::Struct {
2117                    name: "Component",
2118                    len: 2,
2119                },
2120                Token::Str("type"),
2121                Token::U8(ComponentType::Checkbox.into()),
2122                Token::Str("custom_id"),
2123                Token::Some,
2124                Token::Str("test"),
2125                Token::StructEnd,
2126            ],
2127        )
2128    }
2129}