1mod 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#[derive(Clone, Debug, Eq, Hash, PartialEq)]
148pub enum Component {
149 ActionRow(ActionRow),
151 Button(Button),
153 Checkbox(Checkbox),
155 CheckboxGroup(CheckboxGroup),
157 Container(Container),
159 File(FileDisplay),
161 FileUpload(FileUpload),
163 Label(Label),
165 MediaGallery(MediaGallery),
167 Section(Section),
169 SelectMenu(SelectMenu),
171 Separator(Separator),
173 TextDisplay(TextDisplay),
175 TextInput(TextInput),
177 Thumbnail(Thumbnail),
179 Unknown(u8),
181}
182
183impl Component {
184 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 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 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 let mut custom_id: Option<Option<Value>> = None;
571 let mut label: Option<Option<String>> = None;
572
573 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 ComponentType::ActionRow => {
847 let components = components.ok_or_else(|| DeError::missing_field("components"))?;
848
849 Self::Value::ActionRow(ActionRow { components, id })
850 }
851 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 kind @ (ComponentType::TextSelectMenu
897 | ComponentType::UserSelectMenu
898 | ComponentType::RoleSelectMenu
899 | ComponentType::MentionableSelectMenu
900 | ComponentType::ChannelSelectMenu) => {
901 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 _ => {
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 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 Component::ActionRow(row) => 2 + usize::from(row.id.is_some()),
1099 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 Component::SelectMenu(select_menu) => {
1135 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 #[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 Component::TextDisplay(text_display) => 2 + usize::from(text_display.id.is_some()),
1176 Component::MediaGallery(media_gallery) => 2 + usize::from(media_gallery.id.is_some()),
1182 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 Component::File(file) => {
1200 2 + usize::from(file.spoiler.is_some()) + usize::from(file.id.is_some())
1201 }
1202 Component::Section(section) => 3 + usize::from(section.id.is_some()),
1209 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 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 Component::Label(label) => {
1241 3 + usize::from(label.description.is_some()) + usize::from(label.id.is_some())
1242 }
1243 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 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 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 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", ÷r)?;
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", §ion.components)?;
1468 state.serialize_field("accessory", §ion.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 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 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 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 #![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 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, },
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, },
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}