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