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    Id,
533    Items,
534    Label,
535    MaxLength,
536    MaxValues,
537    Media,
538    MinLength,
539    MinValues,
540    Options,
541    Placeholder,
542    Required,
543    SkuId,
544    Spacing,
545    Spoiler,
546    Style,
547    Type,
548    Url,
549    Value,
550}
551
552struct ComponentVisitor;
553
554impl<'de> Visitor<'de> for ComponentVisitor {
555    type Value = Component;
556
557    fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
558        f.write_str("struct Component")
559    }
560
561    #[allow(clippy::too_many_lines)]
562    fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Self::Value, V::Error> {
563        // Required fields.
564        let mut components: Option<Vec<Component>> = None;
565        let mut kind: Option<ComponentType> = None;
566        let mut options: Option<Value> = None;
567        let mut style: Option<Value> = None;
568
569        // Liminal fields.
570        let mut custom_id: Option<Option<Value>> = None;
571        let mut label: Option<Option<String>> = None;
572
573        // Optional fields.
574        let mut channel_types: Option<Vec<ChannelType>> = None;
575        let mut default_values: Option<Vec<SelectDefaultValue>> = None;
576        let mut disabled: Option<bool> = None;
577        let mut emoji: Option<Option<EmojiReactionType>> = None;
578        let mut max_length: Option<Option<u16>> = None;
579        let mut max_values: Option<Option<u8>> = None;
580        let mut min_length: Option<Option<u16>> = None;
581        let mut min_values: Option<Option<u8>> = None;
582        let mut placeholder: Option<Option<String>> = None;
583        let mut required: Option<Option<bool>> = None;
584        let mut url: Option<Option<String>> = None;
585        let mut sku_id: Option<Id<SkuMarker>> = None;
586        let mut value: Option<Option<String>> = None;
587        let mut default: Option<bool> = None;
588
589        let mut id: Option<i32> = None;
590        let mut content: Option<String> = None;
591        let mut items: Option<Vec<MediaGalleryItem>> = None;
592        let mut divider: Option<bool> = None;
593        let mut spacing: Option<SeparatorSpacingSize> = None;
594        let mut file: Option<UnfurledMediaItem> = None;
595        let mut spoiler: Option<bool> = None;
596        let mut accessory: Option<Component> = None;
597        let mut media: Option<UnfurledMediaItem> = None;
598        let mut description: Option<Option<String>> = None;
599        let mut accent_color: Option<Option<u32>> = None;
600        let mut component: Option<Component> = None;
601
602        loop {
603            let key = match map.next_key() {
604                Ok(Some(key)) => key,
605                Ok(None) => break,
606                Err(_) => {
607                    map.next_value::<IgnoredAny>()?;
608
609                    continue;
610                }
611            };
612
613            match key {
614                Field::ChannelTypes => {
615                    if channel_types.is_some() {
616                        return Err(DeError::duplicate_field("channel_types"));
617                    }
618
619                    channel_types = Some(map.next_value()?);
620                }
621                Field::Components => {
622                    if components.is_some() {
623                        return Err(DeError::duplicate_field("components"));
624                    }
625
626                    components = Some(map.next_value()?);
627                }
628                Field::CustomId => {
629                    if custom_id.is_some() {
630                        return Err(DeError::duplicate_field("custom_id"));
631                    }
632
633                    custom_id = Some(map.next_value()?);
634                }
635                Field::Default => {
636                    if default.is_some() {
637                        return Err(DeError::duplicate_field("default"));
638                    }
639
640                    default = Some(map.next_value()?)
641                }
642                Field::DefaultValues => {
643                    if default_values.is_some() {
644                        return Err(DeError::duplicate_field("default_values"));
645                    }
646
647                    default_values = map.next_value()?;
648                }
649                Field::Disabled => {
650                    if disabled.is_some() {
651                        return Err(DeError::duplicate_field("disabled"));
652                    }
653
654                    disabled = Some(map.next_value()?);
655                }
656                Field::Emoji => {
657                    if emoji.is_some() {
658                        return Err(DeError::duplicate_field("emoji"));
659                    }
660
661                    emoji = Some(map.next_value()?);
662                }
663                Field::Label => {
664                    if label.is_some() {
665                        return Err(DeError::duplicate_field("label"));
666                    }
667
668                    label = Some(map.next_value()?);
669                }
670                Field::MaxLength => {
671                    if max_length.is_some() {
672                        return Err(DeError::duplicate_field("max_length"));
673                    }
674
675                    max_length = Some(map.next_value()?);
676                }
677                Field::MaxValues => {
678                    if max_values.is_some() {
679                        return Err(DeError::duplicate_field("max_values"));
680                    }
681
682                    max_values = Some(map.next_value()?);
683                }
684                Field::MinLength => {
685                    if min_length.is_some() {
686                        return Err(DeError::duplicate_field("min_length"));
687                    }
688
689                    min_length = Some(map.next_value()?);
690                }
691                Field::MinValues => {
692                    if min_values.is_some() {
693                        return Err(DeError::duplicate_field("min_values"));
694                    }
695
696                    min_values = Some(map.next_value()?);
697                }
698                Field::Options => {
699                    if options.is_some() {
700                        return Err(DeError::duplicate_field("options"));
701                    }
702
703                    options = Some(map.next_value()?);
704                }
705                Field::Placeholder => {
706                    if placeholder.is_some() {
707                        return Err(DeError::duplicate_field("placeholder"));
708                    }
709
710                    placeholder = Some(map.next_value()?);
711                }
712                Field::Required => {
713                    if required.is_some() {
714                        return Err(DeError::duplicate_field("required"));
715                    }
716
717                    required = Some(map.next_value()?);
718                }
719                Field::Style => {
720                    if style.is_some() {
721                        return Err(DeError::duplicate_field("style"));
722                    }
723
724                    style = Some(map.next_value()?);
725                }
726                Field::Type => {
727                    if kind.is_some() {
728                        return Err(DeError::duplicate_field("type"));
729                    }
730
731                    kind = Some(map.next_value()?);
732                }
733                Field::Url => {
734                    if url.is_some() {
735                        return Err(DeError::duplicate_field("url"));
736                    }
737
738                    url = Some(map.next_value()?);
739                }
740                Field::SkuId => {
741                    if sku_id.is_some() {
742                        return Err(DeError::duplicate_field("sku_id"));
743                    }
744
745                    sku_id = map.next_value()?;
746                }
747                Field::Value => {
748                    if value.is_some() {
749                        return Err(DeError::duplicate_field("value"));
750                    }
751
752                    value = Some(map.next_value()?);
753                }
754                Field::Id => {
755                    if id.is_some() {
756                        return Err(DeError::duplicate_field("id"));
757                    }
758
759                    id = Some(map.next_value()?);
760                }
761                Field::Content => {
762                    if content.is_some() {
763                        return Err(DeError::duplicate_field("content"));
764                    }
765
766                    content = Some(map.next_value()?);
767                }
768                Field::Items => {
769                    if items.is_some() {
770                        return Err(DeError::duplicate_field("items"));
771                    }
772
773                    items = Some(map.next_value()?);
774                }
775                Field::Divider => {
776                    if divider.is_some() {
777                        return Err(DeError::duplicate_field("divider"));
778                    }
779
780                    divider = Some(map.next_value()?);
781                }
782                Field::Spacing => {
783                    if spacing.is_some() {
784                        return Err(DeError::duplicate_field("spacing"));
785                    }
786
787                    spacing = Some(map.next_value()?);
788                }
789                Field::File => {
790                    if file.is_some() {
791                        return Err(DeError::duplicate_field("file"));
792                    }
793
794                    file = Some(map.next_value()?);
795                }
796                Field::Spoiler => {
797                    if spoiler.is_some() {
798                        return Err(DeError::duplicate_field("spoiler"));
799                    }
800
801                    spoiler = Some(map.next_value()?);
802                }
803                Field::Accessory => {
804                    if accessory.is_some() {
805                        return Err(DeError::duplicate_field("accessory"));
806                    }
807
808                    accessory = Some(map.next_value()?);
809                }
810                Field::Media => {
811                    if media.is_some() {
812                        return Err(DeError::duplicate_field("media"));
813                    }
814
815                    media = Some(map.next_value()?);
816                }
817                Field::Description => {
818                    if description.is_some() {
819                        return Err(DeError::duplicate_field("description"));
820                    }
821
822                    description = Some(map.next_value()?);
823                }
824                Field::AccentColor => {
825                    if accent_color.is_some() {
826                        return Err(DeError::duplicate_field("accent_color"));
827                    }
828
829                    accent_color = Some(map.next_value()?);
830                }
831                Field::Component => {
832                    if component.is_some() {
833                        return Err(DeError::duplicate_field("component"));
834                    }
835
836                    component = Some(map.next_value()?);
837                }
838            }
839        }
840
841        let kind = kind.ok_or_else(|| DeError::missing_field("type"))?;
842
843        Ok(match kind {
844            // Required fields:
845            // - components
846            ComponentType::ActionRow => {
847                let components = components.ok_or_else(|| DeError::missing_field("components"))?;
848
849                Self::Value::ActionRow(ActionRow { components, id })
850            }
851            // Required fields:
852            // - style
853            //
854            // Optional fields:
855            // - custom_id
856            // - disabled
857            // - emoji
858            // - label
859            // - url
860            // - sku_id
861            ComponentType::Button => {
862                let style = style
863                    .ok_or_else(|| DeError::missing_field("style"))?
864                    .deserialize_into()
865                    .map_err(DeserializerError::into_error)?;
866
867                let custom_id = custom_id
868                    .flatten()
869                    .map(Value::deserialize_into)
870                    .transpose()
871                    .map_err(DeserializerError::into_error)?;
872
873                Self::Value::Button(Button {
874                    custom_id,
875                    disabled: disabled.unwrap_or_default(),
876                    emoji: emoji.unwrap_or_default(),
877                    label: label.flatten(),
878                    style,
879                    url: url.unwrap_or_default(),
880                    sku_id,
881                    id,
882                })
883            }
884            // Required fields:
885            // - custom_id
886            // - options (if this is a text select menu)
887            //
888            // Optional fields:
889            // - default_values
890            // - disabled
891            // - max_values
892            // - min_values
893            // - placeholder
894            // - channel_types (if this is a channel select menu)
895            // - required
896            kind @ (ComponentType::TextSelectMenu
897            | ComponentType::UserSelectMenu
898            | ComponentType::RoleSelectMenu
899            | ComponentType::MentionableSelectMenu
900            | ComponentType::ChannelSelectMenu) => {
901                // Verify the individual variants' required fields
902                if kind == ComponentType::TextSelectMenu && options.is_none() {
903                    return Err(DeError::missing_field("options"));
904                }
905
906                let custom_id = custom_id
907                    .flatten()
908                    .ok_or_else(|| DeError::missing_field("custom_id"))?
909                    .deserialize_into()
910                    .map_err(DeserializerError::into_error)?;
911
912                Self::Value::SelectMenu(SelectMenu {
913                    channel_types,
914                    custom_id,
915                    default_values,
916                    disabled: disabled.unwrap_or_default(),
917                    kind: match kind {
918                        ComponentType::TextSelectMenu => SelectMenuType::Text,
919                        ComponentType::UserSelectMenu => SelectMenuType::User,
920                        ComponentType::RoleSelectMenu => SelectMenuType::Role,
921                        ComponentType::MentionableSelectMenu => SelectMenuType::Mentionable,
922                        ComponentType::ChannelSelectMenu => SelectMenuType::Channel,
923                        // This branch is unreachable unless we add a new type above and forget to
924                        // also add it here
925                        _ => {
926                            unreachable!("select menu component type is only partially implemented")
927                        }
928                    },
929                    max_values: max_values.unwrap_or_default(),
930                    min_values: min_values.unwrap_or_default(),
931                    options: options
932                        .map(Value::deserialize_into)
933                        .transpose()
934                        .map_err(DeserializerError::into_error)?,
935                    placeholder: placeholder.unwrap_or_default(),
936                    id,
937                    required: required.unwrap_or_default(),
938                })
939            }
940            // Required fields:
941            // - custom_id
942            // - style
943            //
944            // Optional fields:
945            // - label
946            // - max_length
947            // - min_length
948            // - placeholder
949            // - required
950            // - value
951            ComponentType::TextInput => {
952                let custom_id = custom_id
953                    .flatten()
954                    .ok_or_else(|| DeError::missing_field("custom_id"))?
955                    .deserialize_into()
956                    .map_err(DeserializerError::into_error)?;
957
958                let style = style
959                    .ok_or_else(|| DeError::missing_field("style"))?
960                    .deserialize_into()
961                    .map_err(DeserializerError::into_error)?;
962
963                #[allow(deprecated)]
964                Self::Value::TextInput(TextInput {
965                    custom_id,
966                    label: label.unwrap_or_default(),
967                    max_length: max_length.unwrap_or_default(),
968                    min_length: min_length.unwrap_or_default(),
969                    placeholder: placeholder.unwrap_or_default(),
970                    required: required.unwrap_or_default(),
971                    style,
972                    value: value.unwrap_or_default(),
973                    id,
974                })
975            }
976            ComponentType::TextDisplay => {
977                let content = content.ok_or_else(|| DeError::missing_field("content"))?;
978
979                Self::Value::TextDisplay(TextDisplay { content, id })
980            }
981            ComponentType::MediaGallery => {
982                let items = items.ok_or_else(|| DeError::missing_field("items"))?;
983
984                Self::Value::MediaGallery(MediaGallery { id, items })
985            }
986            ComponentType::Separator => Self::Value::Separator(Separator {
987                divider,
988                id,
989                spacing,
990            }),
991            ComponentType::File => {
992                let file = file.ok_or_else(|| DeError::missing_field("file"))?;
993
994                Self::Value::File(FileDisplay { file, id, spoiler })
995            }
996            ComponentType::Unknown(unknown) => Self::Value::Unknown(unknown),
997            ComponentType::Section => {
998                let components = components.ok_or_else(|| DeError::missing_field("components"))?;
999                let accessory = accessory.ok_or_else(|| DeError::missing_field("accessory"))?;
1000                Self::Value::Section(Section {
1001                    id,
1002                    components,
1003                    accessory: Box::new(accessory),
1004                })
1005            }
1006            ComponentType::Thumbnail => {
1007                let media = media.ok_or_else(|| DeError::missing_field("media"))?;
1008                Self::Value::Thumbnail(Thumbnail {
1009                    description,
1010                    id,
1011                    media,
1012                    spoiler,
1013                })
1014            }
1015            ComponentType::Container => {
1016                let components = components.ok_or_else(|| DeError::missing_field("components"))?;
1017                Self::Value::Container(Container {
1018                    accent_color,
1019                    components,
1020                    id,
1021                    spoiler,
1022                })
1023            }
1024            ComponentType::Label => {
1025                let label = label
1026                    .flatten()
1027                    .ok_or_else(|| DeError::missing_field("label"))?;
1028                let component = component.ok_or_else(|| DeError::missing_field("component"))?;
1029                Self::Value::Label(Label {
1030                    id,
1031                    label,
1032                    description: description.unwrap_or_default(),
1033                    component: Box::new(component),
1034                })
1035            }
1036            ComponentType::FileUpload => {
1037                let custom_id = custom_id
1038                    .flatten()
1039                    .ok_or_else(|| DeError::missing_field("custom_id"))?
1040                    .deserialize_into()
1041                    .map_err(DeserializerError::into_error)?;
1042
1043                Self::Value::FileUpload(FileUpload {
1044                    id,
1045                    custom_id,
1046                    max_values: max_values.unwrap_or_default(),
1047                    min_values: min_values.unwrap_or_default(),
1048                    required: required.unwrap_or_default(),
1049                })
1050            }
1051            ComponentType::CheckboxGroup => {
1052                let custom_id = custom_id
1053                    .flatten()
1054                    .ok_or_else(|| DeError::missing_field("custom_id"))?
1055                    .deserialize_into()
1056                    .map_err(DeserializerError::into_error)?;
1057
1058                let options = options
1059                    .ok_or_else(|| DeError::missing_field("options"))?
1060                    .deserialize_into()
1061                    .map_err(DeserializerError::into_error)?;
1062
1063                Self::Value::CheckboxGroup(CheckboxGroup {
1064                    id,
1065                    custom_id,
1066                    options,
1067                    min_values: min_values.unwrap_or_default(),
1068                    max_values: max_values.unwrap_or_default(),
1069                    required: required.unwrap_or_default(),
1070                })
1071            }
1072            ComponentType::Checkbox => {
1073                let custom_id = custom_id
1074                    .flatten()
1075                    .ok_or_else(|| DeError::missing_field("custom_id"))?
1076                    .deserialize_into()
1077                    .map_err(DeserializerError::into_error)?;
1078                Self::Value::Checkbox(Checkbox {
1079                    custom_id,
1080                    default,
1081                    id,
1082                })
1083            }
1084        })
1085    }
1086}
1087
1088impl Serialize for Component {
1089    #[allow(clippy::too_many_lines)]
1090    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1091        let len = match self {
1092            // Required fields:
1093            // - type
1094            // - components
1095            //
1096            // Optional fields:
1097            // - id
1098            Component::ActionRow(row) => 2 + usize::from(row.id.is_some()),
1099            // Required fields:
1100            // - type
1101            // - style
1102            //
1103            // Optional fields:
1104            // - id
1105            // - custom_id
1106            // - disabled
1107            // - emoji
1108            // - label
1109            // - url
1110            // - sku_id
1111            Component::Button(button) => {
1112                2 + usize::from(button.custom_id.is_some())
1113                    + usize::from(button.disabled)
1114                    + usize::from(button.emoji.is_some())
1115                    + usize::from(button.label.is_some())
1116                    + usize::from(button.url.is_some())
1117                    + usize::from(button.sku_id.is_some())
1118                    + usize::from(button.id.is_some())
1119            }
1120            // Required fields:
1121            // - custom_id
1122            // - options (for text select menus)
1123            // - type
1124            //
1125            // Optional fields:
1126            // - id
1127            // - channel_types (for channel select menus)
1128            // - default_values
1129            // - disabled
1130            // - max_values
1131            // - min_values
1132            // - placeholder
1133            // - required
1134            Component::SelectMenu(select_menu) => {
1135                // We ignore text menus that don't include the `options` field, as those are
1136                // detected later in the serialization process
1137                2 + usize::from(select_menu.channel_types.is_some())
1138                    + usize::from(select_menu.default_values.is_some())
1139                    + usize::from(select_menu.disabled)
1140                    + usize::from(select_menu.max_values.is_some())
1141                    + usize::from(select_menu.min_values.is_some())
1142                    + usize::from(select_menu.options.is_some())
1143                    + usize::from(select_menu.placeholder.is_some())
1144                    + usize::from(select_menu.id.is_some())
1145                    + usize::from(select_menu.required.is_some())
1146            }
1147            // Required fields:
1148            // - custom_id
1149            // - style
1150            // - type
1151            //
1152            // Optional fields:
1153            // - id
1154            // - label
1155            // - max_length
1156            // - min_length
1157            // - placeholder
1158            // - required
1159            // - value
1160            #[allow(deprecated)]
1161            Component::TextInput(text_input) => {
1162                3 + usize::from(text_input.label.is_some())
1163                    + usize::from(text_input.max_length.is_some())
1164                    + usize::from(text_input.min_length.is_some())
1165                    + usize::from(text_input.placeholder.is_some())
1166                    + usize::from(text_input.required.is_some())
1167                    + usize::from(text_input.value.is_some())
1168                    + usize::from(text_input.id.is_some())
1169            }
1170            // Required fields:
1171            // - type
1172            // - content
1173            // Optional fields:
1174            // - id
1175            Component::TextDisplay(text_display) => 2 + usize::from(text_display.id.is_some()),
1176            // Required fields:
1177            // - type
1178            // - items
1179            // Optional fields:
1180            // - id
1181            Component::MediaGallery(media_gallery) => 2 + usize::from(media_gallery.id.is_some()),
1182            // Required fields:
1183            // - type
1184            // Optional fields:
1185            // - id
1186            // - divider
1187            // - spacing
1188            Component::Separator(separator) => {
1189                1 + usize::from(separator.divider.is_some())
1190                    + usize::from(separator.spacing.is_some())
1191                    + usize::from(separator.id.is_some())
1192            }
1193            // Required fields:
1194            // - type
1195            // - file
1196            // Optional fields:
1197            // - id
1198            // - spoiler
1199            Component::File(file) => {
1200                2 + usize::from(file.spoiler.is_some()) + usize::from(file.id.is_some())
1201            }
1202            // Required fields:
1203            // - type
1204            // - components
1205            // - accessory
1206            // Optional fields:
1207            // - id
1208            Component::Section(section) => 3 + usize::from(section.id.is_some()),
1209            // Required fields:
1210            // - type
1211            // - components
1212            // Optional fields:
1213            // - id
1214            // - accent_color
1215            // - spoiler
1216            Component::Container(container) => {
1217                2 + usize::from(container.accent_color.is_some())
1218                    + usize::from(container.spoiler.is_some())
1219                    + usize::from(container.id.is_some())
1220            }
1221            // Required fields:
1222            // - type
1223            // - media
1224            // Optional fields:
1225            // - id
1226            // - description
1227            // - spoiler
1228            Component::Thumbnail(thumbnail) => {
1229                2 + usize::from(thumbnail.spoiler.is_some())
1230                    + usize::from(thumbnail.description.is_some())
1231                    + usize::from(thumbnail.id.is_some())
1232            }
1233            // Required fields:
1234            // - type
1235            // - label
1236            // - component
1237            // Optional fields:
1238            // - id
1239            // - description
1240            Component::Label(label) => {
1241                3 + usize::from(label.description.is_some()) + usize::from(label.id.is_some())
1242            }
1243            // Required fields:
1244            // - type
1245            // - custom_id
1246            // Optional fields:
1247            // - id
1248            // - min_values
1249            // - max_values
1250            // - required
1251            Component::FileUpload(file_upload) => {
1252                2 + usize::from(file_upload.min_values.is_some())
1253                    + usize::from(file_upload.max_values.is_some())
1254                    + usize::from(file_upload.required.is_some())
1255                    + usize::from(file_upload.id.is_some())
1256            }
1257            Component::CheckboxGroup(checkbox_group) => {
1258                3 + usize::from(checkbox_group.id.is_some())
1259                    + usize::from(checkbox_group.min_values.is_some())
1260                    + usize::from(checkbox_group.max_values.is_some())
1261                    + usize::from(checkbox_group.required.is_some())
1262            }
1263            Component::Checkbox(checkbox) => {
1264                2 + usize::from(checkbox.id.is_some()) + usize::from(checkbox.default.is_some())
1265            }
1266            // We are dropping fields here but nothing we can do about that for
1267            // the time being.
1268            Component::Unknown(_) => 1,
1269        };
1270
1271        let mut state = serializer.serialize_struct("Component", len)?;
1272
1273        match self {
1274            Component::ActionRow(action_row) => {
1275                state.serialize_field("type", &ComponentType::ActionRow)?;
1276                if let Some(id) = action_row.id {
1277                    state.serialize_field("id", &id)?;
1278                }
1279
1280                state.serialize_field("components", &action_row.components)?;
1281            }
1282            Component::Button(button) => {
1283                state.serialize_field("type", &ComponentType::Button)?;
1284                if let Some(id) = button.id {
1285                    state.serialize_field("id", &id)?;
1286                }
1287
1288                if button.custom_id.is_some() {
1289                    state.serialize_field("custom_id", &button.custom_id)?;
1290                }
1291
1292                if button.disabled {
1293                    state.serialize_field("disabled", &button.disabled)?;
1294                }
1295
1296                if button.emoji.is_some() {
1297                    state.serialize_field("emoji", &button.emoji)?;
1298                }
1299
1300                if button.label.is_some() {
1301                    state.serialize_field("label", &button.label)?;
1302                }
1303
1304                state.serialize_field("style", &button.style)?;
1305
1306                if button.url.is_some() {
1307                    state.serialize_field("url", &button.url)?;
1308                }
1309
1310                if button.sku_id.is_some() {
1311                    state.serialize_field("sku_id", &button.sku_id)?;
1312                }
1313            }
1314            Component::SelectMenu(select_menu) => {
1315                match &select_menu.kind {
1316                    SelectMenuType::Text => {
1317                        state.serialize_field("type", &ComponentType::TextSelectMenu)?;
1318                        if let Some(id) = select_menu.id {
1319                            state.serialize_field("id", &id)?;
1320                        }
1321
1322                        state.serialize_field(
1323                            "options",
1324                            &select_menu.options.as_ref().ok_or(SerError::custom(
1325                                "required field \"option\" missing for text select menu",
1326                            ))?,
1327                        )?;
1328                    }
1329                    SelectMenuType::User => {
1330                        state.serialize_field("type", &ComponentType::UserSelectMenu)?;
1331                        if let Some(id) = select_menu.id {
1332                            state.serialize_field("id", &id)?;
1333                        }
1334                    }
1335                    SelectMenuType::Role => {
1336                        state.serialize_field("type", &ComponentType::RoleSelectMenu)?;
1337                        if let Some(id) = select_menu.id {
1338                            state.serialize_field("id", &id)?;
1339                        }
1340                    }
1341                    SelectMenuType::Mentionable => {
1342                        state.serialize_field("type", &ComponentType::MentionableSelectMenu)?;
1343                        if let Some(id) = select_menu.id {
1344                            state.serialize_field("id", &id)?;
1345                        }
1346                    }
1347                    SelectMenuType::Channel => {
1348                        state.serialize_field("type", &ComponentType::ChannelSelectMenu)?;
1349                        if let Some(id) = select_menu.id {
1350                            state.serialize_field("id", &id)?;
1351                        }
1352
1353                        if let Some(channel_types) = &select_menu.channel_types {
1354                            state.serialize_field("channel_types", channel_types)?;
1355                        }
1356                    }
1357                }
1358
1359                // Due to `custom_id` being required in some variants and
1360                // optional in others, serialize as an Option.
1361                state.serialize_field("custom_id", &Some(&select_menu.custom_id))?;
1362
1363                if select_menu.default_values.is_some() {
1364                    state.serialize_field("default_values", &select_menu.default_values)?;
1365                }
1366
1367                state.serialize_field("disabled", &select_menu.disabled)?;
1368
1369                if select_menu.max_values.is_some() {
1370                    state.serialize_field("max_values", &select_menu.max_values)?;
1371                }
1372
1373                if select_menu.min_values.is_some() {
1374                    state.serialize_field("min_values", &select_menu.min_values)?;
1375                }
1376
1377                if select_menu.placeholder.is_some() {
1378                    state.serialize_field("placeholder", &select_menu.placeholder)?;
1379                }
1380
1381                if select_menu.required.is_some() {
1382                    state.serialize_field("required", &select_menu.required)?;
1383                }
1384            }
1385            Component::TextInput(text_input) => {
1386                state.serialize_field("type", &ComponentType::TextInput)?;
1387                if let Some(id) = text_input.id {
1388                    state.serialize_field("id", &id)?;
1389                }
1390
1391                // Due to `custom_id` being required in some
1392                // variants and optional in others, serialize as an Option.
1393                state.serialize_field("custom_id", &Some(&text_input.custom_id))?;
1394
1395                #[allow(deprecated)]
1396                if text_input.label.is_some() {
1397                    state.serialize_field("label", &text_input.label)?;
1398                }
1399
1400                if text_input.max_length.is_some() {
1401                    state.serialize_field("max_length", &text_input.max_length)?;
1402                }
1403
1404                if text_input.min_length.is_some() {
1405                    state.serialize_field("min_length", &text_input.min_length)?;
1406                }
1407
1408                if text_input.placeholder.is_some() {
1409                    state.serialize_field("placeholder", &text_input.placeholder)?;
1410                }
1411
1412                if text_input.required.is_some() {
1413                    state.serialize_field("required", &text_input.required)?;
1414                }
1415
1416                state.serialize_field("style", &text_input.style)?;
1417
1418                if text_input.value.is_some() {
1419                    state.serialize_field("value", &text_input.value)?;
1420                }
1421            }
1422            Component::TextDisplay(text_display) => {
1423                state.serialize_field("type", &ComponentType::TextDisplay)?;
1424                if let Some(id) = text_display.id {
1425                    state.serialize_field("id", &id)?;
1426                }
1427
1428                state.serialize_field("content", &text_display.content)?;
1429            }
1430            Component::MediaGallery(media_gallery) => {
1431                state.serialize_field("type", &ComponentType::MediaGallery)?;
1432                if let Some(id) = media_gallery.id {
1433                    state.serialize_field("id", &id)?;
1434                }
1435
1436                state.serialize_field("items", &media_gallery.items)?;
1437            }
1438            Component::Separator(separator) => {
1439                state.serialize_field("type", &ComponentType::Separator)?;
1440                if let Some(id) = separator.id {
1441                    state.serialize_field("id", &id)?;
1442                }
1443                if let Some(divider) = separator.divider {
1444                    state.serialize_field("divider", &divider)?;
1445                }
1446                if let Some(spacing) = &separator.spacing {
1447                    state.serialize_field("spacing", spacing)?;
1448                }
1449            }
1450            Component::File(file) => {
1451                state.serialize_field("type", &ComponentType::File)?;
1452                if let Some(id) = file.id {
1453                    state.serialize_field("id", &id)?;
1454                }
1455
1456                state.serialize_field("file", &file.file)?;
1457                if let Some(spoiler) = file.spoiler {
1458                    state.serialize_field("spoiler", &spoiler)?;
1459                }
1460            }
1461            Component::Section(section) => {
1462                state.serialize_field("type", &ComponentType::Section)?;
1463                if let Some(id) = section.id {
1464                    state.serialize_field("id", &id)?;
1465                }
1466
1467                state.serialize_field("components", &section.components)?;
1468                state.serialize_field("accessory", &section.accessory)?;
1469            }
1470            Component::Container(container) => {
1471                state.serialize_field("type", &ComponentType::Container)?;
1472                if let Some(id) = container.id {
1473                    state.serialize_field("id", &id)?;
1474                }
1475
1476                if let Some(accent_color) = container.accent_color {
1477                    state.serialize_field("accent_color", &accent_color)?;
1478                }
1479                if let Some(spoiler) = container.spoiler {
1480                    state.serialize_field("spoiler", &spoiler)?;
1481                }
1482                state.serialize_field("components", &container.components)?;
1483            }
1484            Component::Thumbnail(thumbnail) => {
1485                state.serialize_field("type", &ComponentType::Thumbnail)?;
1486                if let Some(id) = thumbnail.id {
1487                    state.serialize_field("id", &id)?;
1488                }
1489
1490                state.serialize_field("media", &thumbnail.media)?;
1491                if let Some(description) = &thumbnail.description {
1492                    state.serialize_field("description", description)?;
1493                }
1494                if let Some(spoiler) = thumbnail.spoiler {
1495                    state.serialize_field("spoiler", &spoiler)?;
1496                }
1497            }
1498            Component::Label(label) => {
1499                state.serialize_field("type", &ComponentType::Label)?;
1500                if label.id.is_some() {
1501                    state.serialize_field("id", &label.id)?;
1502                }
1503                // Due to `label` being required in some
1504                // variants and optional in others, serialize as an Option.
1505                state.serialize_field("label", &Some(&label.label))?;
1506                if label.description.is_some() {
1507                    state.serialize_field("description", &label.description)?;
1508                }
1509                state.serialize_field("component", &label.component)?;
1510            }
1511            Component::FileUpload(file_upload) => {
1512                state.serialize_field("type", &ComponentType::FileUpload)?;
1513                if file_upload.id.is_some() {
1514                    state.serialize_field("id", &file_upload.id)?;
1515                }
1516
1517                // Due to `custom_id` being required in some variants and
1518                // optional in others, serialize as an Option.
1519                state.serialize_field("custom_id", &Some(&file_upload.custom_id))?;
1520                if file_upload.min_values.is_some() {
1521                    state.serialize_field("min_values", &file_upload.min_values)?;
1522                }
1523                if file_upload.max_values.is_some() {
1524                    state.serialize_field("max_values", &file_upload.max_values)?;
1525                }
1526                if file_upload.required.is_some() {
1527                    state.serialize_field("required", &file_upload.required)?;
1528                }
1529            }
1530            Component::CheckboxGroup(checkbox_group) => {
1531                state.serialize_field("type", &ComponentType::CheckboxGroup)?;
1532                if checkbox_group.id.is_some() {
1533                    state.serialize_field("id", &checkbox_group.id)?;
1534                }
1535                state.serialize_field("custom_id", &Some(&checkbox_group.custom_id))?;
1536                state.serialize_field("options", &checkbox_group.options)?;
1537                if checkbox_group.min_values.is_some() {
1538                    state.serialize_field("min_values", &checkbox_group.min_values)?;
1539                }
1540                if checkbox_group.max_values.is_some() {
1541                    state.serialize_field("max_values", &checkbox_group.max_values)?;
1542                }
1543                if checkbox_group.required.is_some() {
1544                    state.serialize_field("required", &checkbox_group.required)?;
1545                }
1546            }
1547            Component::Checkbox(checkbox) => {
1548                state.serialize_field("type", &ComponentType::Checkbox)?;
1549                if checkbox.id.is_some() {
1550                    state.serialize_field("id", &checkbox.id)?;
1551                }
1552                state.serialize_field("custom_id", &Some(&checkbox.custom_id))?;
1553                if checkbox.default.is_some() {
1554                    state.serialize_field("default", &checkbox.default)?;
1555                }
1556            }
1557
1558            // We are not serializing all fields so this will fail to
1559            // deserialize. But it is all that can be done to avoid losing
1560            // incoming messages at this time.
1561            Component::Unknown(unknown) => {
1562                state.serialize_field("type", &ComponentType::Unknown(*unknown))?;
1563            }
1564        }
1565
1566        state.end()
1567    }
1568}
1569
1570#[cfg(test)]
1571mod tests {
1572    // Required due to the use of a unicode emoji in a constant.
1573    #![allow(clippy::non_ascii_literal)]
1574
1575    use super::*;
1576    use crate::id::Id;
1577    use serde_test::Token;
1578    use static_assertions::assert_impl_all;
1579
1580    assert_impl_all!(
1581        Component: From<ActionRow>,
1582        From<Button>,
1583        From<SelectMenu>,
1584        From<TextInput>
1585    );
1586
1587    #[allow(clippy::too_many_lines)]
1588    #[test]
1589    fn component_full() {
1590        let component = Component::ActionRow(ActionRow {
1591            components: Vec::from([
1592                Component::Button(Button {
1593                    custom_id: Some("test custom id".into()),
1594                    disabled: true,
1595                    emoji: None,
1596                    label: Some("test label".into()),
1597                    style: ButtonStyle::Primary,
1598                    url: None,
1599                    sku_id: None,
1600                    id: None,
1601                }),
1602                Component::SelectMenu(SelectMenu {
1603                    channel_types: None,
1604                    custom_id: "test custom id 2".into(),
1605                    default_values: None,
1606                    disabled: false,
1607                    kind: SelectMenuType::Text,
1608                    max_values: Some(25),
1609                    min_values: Some(5),
1610                    options: Some(Vec::from([SelectMenuOption {
1611                        label: "test option label".into(),
1612                        value: "test option value".into(),
1613                        description: Some("test description".into()),
1614                        emoji: None,
1615                        default: false,
1616                    }])),
1617                    placeholder: Some("test placeholder".into()),
1618                    id: None,
1619                    required: Some(true),
1620                }),
1621            ]),
1622            id: None,
1623        });
1624
1625        serde_test::assert_tokens(
1626            &component,
1627            &[
1628                Token::Struct {
1629                    name: "Component",
1630                    len: 2,
1631                },
1632                Token::Str("type"),
1633                Token::U8(ComponentType::ActionRow.into()),
1634                Token::Str("components"),
1635                Token::Seq { len: Some(2) },
1636                Token::Struct {
1637                    name: "Component",
1638                    len: 5,
1639                },
1640                Token::Str("type"),
1641                Token::U8(ComponentType::Button.into()),
1642                Token::Str("custom_id"),
1643                Token::Some,
1644                Token::Str("test custom id"),
1645                Token::Str("disabled"),
1646                Token::Bool(true),
1647                Token::Str("label"),
1648                Token::Some,
1649                Token::Str("test label"),
1650                Token::Str("style"),
1651                Token::U8(ButtonStyle::Primary.into()),
1652                Token::StructEnd,
1653                Token::Struct {
1654                    name: "Component",
1655                    len: 7,
1656                },
1657                Token::Str("type"),
1658                Token::U8(ComponentType::TextSelectMenu.into()),
1659                Token::Str("options"),
1660                Token::Seq { len: Some(1) },
1661                Token::Struct {
1662                    name: "SelectMenuOption",
1663                    len: 4,
1664                },
1665                Token::Str("default"),
1666                Token::Bool(false),
1667                Token::Str("description"),
1668                Token::Some,
1669                Token::Str("test description"),
1670                Token::Str("label"),
1671                Token::Str("test option label"),
1672                Token::Str("value"),
1673                Token::Str("test option value"),
1674                Token::StructEnd,
1675                Token::SeqEnd,
1676                Token::Str("custom_id"),
1677                Token::Some,
1678                Token::Str("test custom id 2"),
1679                Token::Str("disabled"),
1680                Token::Bool(false),
1681                Token::Str("max_values"),
1682                Token::Some,
1683                Token::U8(25),
1684                Token::Str("min_values"),
1685                Token::Some,
1686                Token::U8(5),
1687                Token::Str("placeholder"),
1688                Token::Some,
1689                Token::Str("test placeholder"),
1690                Token::Str("required"),
1691                Token::Some,
1692                Token::Bool(true),
1693                Token::StructEnd,
1694                Token::SeqEnd,
1695                Token::StructEnd,
1696            ],
1697        );
1698    }
1699
1700    #[test]
1701    fn action_row() {
1702        let value = Component::ActionRow(ActionRow {
1703            components: Vec::from([Component::Button(Button {
1704                custom_id: Some("button-1".to_owned()),
1705                disabled: false,
1706                emoji: None,
1707                style: ButtonStyle::Primary,
1708                label: Some("Button".to_owned()),
1709                url: None,
1710                sku_id: None,
1711                id: None,
1712            })]),
1713            id: None,
1714        });
1715
1716        serde_test::assert_tokens(
1717            &value,
1718            &[
1719                Token::Struct {
1720                    name: "Component",
1721                    len: 2,
1722                },
1723                Token::String("type"),
1724                Token::U8(ComponentType::ActionRow.into()),
1725                Token::String("components"),
1726                Token::Seq { len: Some(1) },
1727                Token::Struct {
1728                    name: "Component",
1729                    len: 4,
1730                },
1731                Token::String("type"),
1732                Token::U8(2),
1733                Token::String("custom_id"),
1734                Token::Some,
1735                Token::String("button-1"),
1736                Token::String("label"),
1737                Token::Some,
1738                Token::String("Button"),
1739                Token::String("style"),
1740                Token::U8(1),
1741                Token::StructEnd,
1742                Token::SeqEnd,
1743                Token::StructEnd,
1744            ],
1745        );
1746    }
1747
1748    #[test]
1749    fn button() {
1750        // Free Palestine.
1751        //
1752        // Palestinian Flag.
1753        const FLAG: &str = "🇵🇸";
1754
1755        let value = Component::Button(Button {
1756            custom_id: Some("test".to_owned()),
1757            disabled: false,
1758            emoji: Some(EmojiReactionType::Unicode {
1759                name: FLAG.to_owned(),
1760            }),
1761            label: Some("Test".to_owned()),
1762            style: ButtonStyle::Link,
1763            url: Some("https://twilight.rs".to_owned()),
1764            sku_id: None,
1765            id: None,
1766        });
1767
1768        serde_test::assert_tokens(
1769            &value,
1770            &[
1771                Token::Struct {
1772                    name: "Component",
1773                    len: 6,
1774                },
1775                Token::String("type"),
1776                Token::U8(ComponentType::Button.into()),
1777                Token::String("custom_id"),
1778                Token::Some,
1779                Token::String("test"),
1780                Token::String("emoji"),
1781                Token::Some,
1782                Token::Struct {
1783                    name: "EmojiReactionType",
1784                    len: 1,
1785                },
1786                Token::String("name"),
1787                Token::String(FLAG),
1788                Token::StructEnd,
1789                Token::String("label"),
1790                Token::Some,
1791                Token::String("Test"),
1792                Token::String("style"),
1793                Token::U8(ButtonStyle::Link.into()),
1794                Token::String("url"),
1795                Token::Some,
1796                Token::String("https://twilight.rs"),
1797                Token::StructEnd,
1798            ],
1799        );
1800    }
1801
1802    #[test]
1803    fn select_menu() {
1804        fn check_select(default_values: Option<Vec<(SelectDefaultValue, &'static str)>>) {
1805            let select_menu = Component::SelectMenu(SelectMenu {
1806                channel_types: None,
1807                custom_id: String::from("my_select"),
1808                default_values: default_values
1809                    .clone()
1810                    .map(|values| values.into_iter().map(|pair| pair.0).collect()),
1811                disabled: false,
1812                kind: SelectMenuType::User,
1813                max_values: None,
1814                min_values: None,
1815                options: None,
1816                placeholder: None,
1817                id: None,
1818                required: None,
1819            });
1820            let mut tokens = vec![
1821                Token::Struct {
1822                    name: "Component",
1823                    len: 2 + usize::from(default_values.is_some()),
1824                },
1825                Token::String("type"),
1826                Token::U8(ComponentType::UserSelectMenu.into()),
1827                Token::Str("custom_id"),
1828                Token::Some,
1829                Token::Str("my_select"),
1830            ];
1831            if let Some(default_values) = default_values {
1832                tokens.extend_from_slice(&[
1833                    Token::Str("default_values"),
1834                    Token::Some,
1835                    Token::Seq {
1836                        len: Some(default_values.len()),
1837                    },
1838                ]);
1839                for (_, id) in default_values {
1840                    tokens.extend_from_slice(&[
1841                        Token::Struct {
1842                            name: "SelectDefaultValue",
1843                            len: 2,
1844                        },
1845                        Token::Str("type"),
1846                        Token::UnitVariant {
1847                            name: "SelectDefaultValue",
1848                            variant: "user",
1849                        },
1850                        Token::Str("id"),
1851                        Token::NewtypeStruct { name: "Id" },
1852                        Token::Str(id),
1853                        Token::StructEnd,
1854                    ])
1855                }
1856                tokens.push(Token::SeqEnd);
1857            }
1858            tokens.extend_from_slice(&[
1859                Token::Str("disabled"),
1860                Token::Bool(false),
1861                Token::StructEnd,
1862            ]);
1863            serde_test::assert_tokens(&select_menu, &tokens);
1864        }
1865
1866        check_select(None);
1867        check_select(Some(vec![(
1868            SelectDefaultValue::User(Id::new(1234)),
1869            "1234",
1870        )]));
1871        check_select(Some(vec![
1872            (SelectDefaultValue::User(Id::new(1234)), "1234"),
1873            (SelectDefaultValue::User(Id::new(5432)), "5432"),
1874        ]));
1875    }
1876
1877    #[test]
1878    fn text_input() {
1879        #[allow(deprecated)]
1880        let value = Component::TextInput(TextInput {
1881            custom_id: "test".to_owned(),
1882            label: Some("The label".to_owned()),
1883            max_length: Some(100),
1884            min_length: Some(1),
1885            placeholder: Some("Taking this place".to_owned()),
1886            required: Some(true),
1887            style: TextInputStyle::Short,
1888            value: Some("Hello World!".to_owned()),
1889            id: None,
1890        });
1891
1892        serde_test::assert_tokens(
1893            &value,
1894            &[
1895                Token::Struct {
1896                    name: "Component",
1897                    len: 9,
1898                },
1899                Token::String("type"),
1900                Token::U8(ComponentType::TextInput.into()),
1901                Token::String("custom_id"),
1902                Token::Some,
1903                Token::String("test"),
1904                Token::String("label"),
1905                Token::Some,
1906                Token::String("The label"),
1907                Token::String("max_length"),
1908                Token::Some,
1909                Token::U16(100),
1910                Token::String("min_length"),
1911                Token::Some,
1912                Token::U16(1),
1913                Token::String("placeholder"),
1914                Token::Some,
1915                Token::String("Taking this place"),
1916                Token::String("required"),
1917                Token::Some,
1918                Token::Bool(true),
1919                Token::String("style"),
1920                Token::U8(TextInputStyle::Short as u8),
1921                Token::String("value"),
1922                Token::Some,
1923                Token::String("Hello World!"),
1924                Token::StructEnd,
1925            ],
1926        );
1927    }
1928
1929    #[test]
1930    fn premium_button() {
1931        let value = Component::Button(Button {
1932            custom_id: None,
1933            disabled: false,
1934            emoji: None,
1935            label: None,
1936            style: ButtonStyle::Premium,
1937            url: None,
1938            sku_id: Some(Id::new(114_941_315_417_899_012)),
1939            id: None,
1940        });
1941
1942        serde_test::assert_tokens(
1943            &value,
1944            &[
1945                Token::Struct {
1946                    name: "Component",
1947                    len: 3,
1948                },
1949                Token::String("type"),
1950                Token::U8(ComponentType::Button.into()),
1951                Token::String("style"),
1952                Token::U8(ButtonStyle::Premium.into()),
1953                Token::String("sku_id"),
1954                Token::Some,
1955                Token::NewtypeStruct { name: "Id" },
1956                Token::Str("114941315417899012"),
1957                Token::StructEnd,
1958            ],
1959        );
1960    }
1961
1962    #[test]
1963    fn label() {
1964        #[allow(deprecated)]
1965        let value = Component::Label(Label {
1966            id: None,
1967            label: "The label".to_owned(),
1968            description: Some("The description".to_owned()),
1969            component: Box::new(Component::TextInput(TextInput {
1970                id: None,
1971                custom_id: "The custom id".to_owned(),
1972                label: None,
1973                max_length: None,
1974                min_length: None,
1975                placeholder: None,
1976                required: None,
1977                style: TextInputStyle::Paragraph,
1978                value: None,
1979            })),
1980        });
1981
1982        serde_test::assert_tokens(
1983            &value,
1984            &[
1985                Token::Struct {
1986                    name: "Component",
1987                    len: 4,
1988                },
1989                Token::String("type"),
1990                Token::U8(ComponentType::Label.into()),
1991                Token::String("label"),
1992                Token::Some,
1993                Token::String("The label"),
1994                Token::String("description"),
1995                Token::Some,
1996                Token::String("The description"),
1997                Token::String("component"),
1998                Token::Struct {
1999                    name: "Component",
2000                    len: 3,
2001                },
2002                Token::String("type"),
2003                Token::U8(ComponentType::TextInput.into()),
2004                Token::String("custom_id"),
2005                Token::Some,
2006                Token::String("The custom id"),
2007                Token::String("style"),
2008                Token::U8(TextInputStyle::Paragraph as u8),
2009                Token::StructEnd,
2010                Token::StructEnd,
2011            ],
2012        );
2013    }
2014
2015    #[test]
2016    fn file_upload() {
2017        let value = Component::FileUpload(FileUpload {
2018            id: None,
2019            custom_id: "test".to_owned(),
2020            max_values: None,
2021            min_values: None,
2022            required: Some(true),
2023        });
2024
2025        serde_test::assert_tokens(
2026            &value,
2027            &[
2028                Token::Struct {
2029                    name: "Component",
2030                    len: 3,
2031                },
2032                Token::String("type"),
2033                Token::U8(ComponentType::FileUpload.into()),
2034                Token::String("custom_id"),
2035                Token::Some,
2036                Token::String("test"),
2037                Token::String("required"),
2038                Token::Some,
2039                Token::Bool(true),
2040                Token::StructEnd,
2041            ],
2042        )
2043    }
2044
2045    #[test]
2046    fn checkbox_group() {
2047        let value = Component::CheckboxGroup(CheckboxGroup {
2048            id: None,
2049            custom_id: "group".to_owned(),
2050            options: vec![CheckboxGroupOption {
2051                default: None,
2052                description: None,
2053                label: "Option A".to_owned(),
2054                value: "a".to_owned(),
2055            }],
2056            min_values: None,
2057            max_values: None,
2058            required: None,
2059        });
2060
2061        serde_test::assert_tokens(
2062            &value,
2063            &[
2064                Token::Struct {
2065                    name: "Component",
2066                    len: 3, // type, custom_id, options
2067                },
2068                Token::Str("type"),
2069                Token::U8(ComponentType::CheckboxGroup.into()),
2070                Token::Str("custom_id"),
2071                Token::Some,
2072                Token::Str("group"),
2073                Token::Str("options"),
2074                Token::Seq { len: Some(1) },
2075                Token::Struct {
2076                    name: "CheckboxGroupOption",
2077                    len: 2, // value, label
2078                },
2079                Token::Str("label"),
2080                Token::Str("Option A"),
2081                Token::Str("value"),
2082                Token::Str("a"),
2083                Token::StructEnd,
2084                Token::SeqEnd,
2085                Token::StructEnd,
2086            ],
2087        );
2088    }
2089    #[test]
2090    fn checkbox() {
2091        let value = Component::Checkbox(Checkbox {
2092            custom_id: "test".to_owned(),
2093            default: None,
2094            id: None,
2095        });
2096
2097        serde_test::assert_tokens(
2098            &value,
2099            &[
2100                Token::Struct {
2101                    name: "Component",
2102                    len: 2,
2103                },
2104                Token::Str("type"),
2105                Token::U8(ComponentType::Checkbox.into()),
2106                Token::Str("custom_id"),
2107                Token::Some,
2108                Token::Str("test"),
2109                Token::StructEnd,
2110            ],
2111        )
2112    }
2113}