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