1mod action_row;
9mod button;
10mod kind;
11mod select_menu;
12mod text_input;
13
14pub use self::{
15 action_row::ActionRow,
16 button::{Button, ButtonStyle},
17 kind::ComponentType,
18 select_menu::{SelectDefaultValue, SelectMenu, SelectMenuOption, SelectMenuType},
19 text_input::{TextInput, TextInputStyle},
20};
21
22use super::EmojiReactionType;
23use crate::{
24 channel::ChannelType,
25 id::{marker::SkuMarker, Id},
26};
27use serde::{
28 de::{Deserializer, Error as DeError, IgnoredAny, MapAccess, Visitor},
29 ser::{Error as SerError, SerializeStruct},
30 Deserialize, Serialize, Serializer,
31};
32use serde_value::{DeserializerError, Value};
33use std::fmt::{Formatter, Result as FmtResult};
34
35#[derive(Clone, Debug, Eq, Hash, PartialEq)]
119pub enum Component {
120 ActionRow(ActionRow),
122 Button(Button),
124 SelectMenu(SelectMenu),
126 TextInput(TextInput),
128 Unknown(u8),
130}
131
132impl Component {
133 pub const fn kind(&self) -> ComponentType {
153 match self {
154 Self::ActionRow(_) => ComponentType::ActionRow,
155 Self::Button(_) => ComponentType::Button,
156 Self::SelectMenu(SelectMenu { kind, .. }) => match kind {
157 SelectMenuType::Text => ComponentType::TextSelectMenu,
158 SelectMenuType::User => ComponentType::UserSelectMenu,
159 SelectMenuType::Role => ComponentType::RoleSelectMenu,
160 SelectMenuType::Mentionable => ComponentType::MentionableSelectMenu,
161 SelectMenuType::Channel => ComponentType::ChannelSelectMenu,
162 },
163 Self::TextInput(_) => ComponentType::TextInput,
164 Component::Unknown(unknown) => ComponentType::Unknown(*unknown),
165 }
166 }
167}
168
169impl From<ActionRow> for Component {
170 fn from(action_row: ActionRow) -> Self {
171 Self::ActionRow(action_row)
172 }
173}
174
175impl From<Button> for Component {
176 fn from(button: Button) -> Self {
177 Self::Button(button)
178 }
179}
180
181impl From<SelectMenu> for Component {
182 fn from(select_menu: SelectMenu) -> Self {
183 Self::SelectMenu(select_menu)
184 }
185}
186
187impl From<TextInput> for Component {
188 fn from(text_input: TextInput) -> Self {
189 Self::TextInput(text_input)
190 }
191}
192
193impl<'de> Deserialize<'de> for Component {
194 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
195 deserializer.deserialize_any(ComponentVisitor)
196 }
197}
198
199#[derive(Debug, Deserialize)]
200#[serde(field_identifier, rename_all = "snake_case")]
201enum Field {
202 ChannelTypes,
203 Components,
204 CustomId,
205 DefaultValues,
206 Disabled,
207 Emoji,
208 Label,
209 MaxLength,
210 MaxValues,
211 MinLength,
212 MinValues,
213 Options,
214 Placeholder,
215 Required,
216 Style,
217 Type,
218 Url,
219 SkuId,
220 Value,
221}
222
223struct ComponentVisitor;
224
225impl<'de> Visitor<'de> for ComponentVisitor {
226 type Value = Component;
227
228 fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
229 f.write_str("struct Component")
230 }
231
232 #[allow(clippy::too_many_lines)]
233 fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Self::Value, V::Error> {
234 let mut components: Option<Vec<Component>> = None;
236 let mut kind: Option<ComponentType> = None;
237 let mut options: Option<Vec<SelectMenuOption>> = None;
238 let mut style: Option<Value> = None;
239
240 let mut custom_id: Option<Option<Value>> = None;
242 let mut label: Option<Option<String>> = None;
243
244 let mut channel_types: Option<Vec<ChannelType>> = None;
246 let mut default_values: Option<Vec<SelectDefaultValue>> = None;
247 let mut disabled: Option<bool> = None;
248 let mut emoji: Option<Option<EmojiReactionType>> = None;
249 let mut max_length: Option<Option<u16>> = None;
250 let mut max_values: Option<Option<u8>> = None;
251 let mut min_length: Option<Option<u16>> = None;
252 let mut min_values: Option<Option<u8>> = None;
253 let mut placeholder: Option<Option<String>> = None;
254 let mut required: Option<Option<bool>> = None;
255 let mut url: Option<Option<String>> = None;
256 let mut sku_id: Option<Id<SkuMarker>> = None;
257 let mut value: Option<Option<String>> = None;
258
259 loop {
260 let key = match map.next_key() {
261 Ok(Some(key)) => key,
262 Ok(None) => break,
263 Err(_) => {
264 map.next_value::<IgnoredAny>()?;
265
266 continue;
267 }
268 };
269
270 match key {
271 Field::ChannelTypes => {
272 if channel_types.is_some() {
273 return Err(DeError::duplicate_field("channel_types"));
274 }
275
276 channel_types = Some(map.next_value()?);
277 }
278 Field::Components => {
279 if components.is_some() {
280 return Err(DeError::duplicate_field("components"));
281 }
282
283 components = Some(map.next_value()?);
284 }
285 Field::CustomId => {
286 if custom_id.is_some() {
287 return Err(DeError::duplicate_field("custom_id"));
288 }
289
290 custom_id = Some(map.next_value()?);
291 }
292 Field::DefaultValues => {
293 if default_values.is_some() {
294 return Err(DeError::duplicate_field("default_values"));
295 }
296
297 default_values = map.next_value()?;
298 }
299 Field::Disabled => {
300 if disabled.is_some() {
301 return Err(DeError::duplicate_field("disabled"));
302 }
303
304 disabled = Some(map.next_value()?);
305 }
306 Field::Emoji => {
307 if emoji.is_some() {
308 return Err(DeError::duplicate_field("emoji"));
309 }
310
311 emoji = Some(map.next_value()?);
312 }
313 Field::Label => {
314 if label.is_some() {
315 return Err(DeError::duplicate_field("label"));
316 }
317
318 label = Some(map.next_value()?);
319 }
320 Field::MaxLength => {
321 if max_length.is_some() {
322 return Err(DeError::duplicate_field("max_length"));
323 }
324
325 max_length = Some(map.next_value()?);
326 }
327 Field::MaxValues => {
328 if max_values.is_some() {
329 return Err(DeError::duplicate_field("max_values"));
330 }
331
332 max_values = Some(map.next_value()?);
333 }
334 Field::MinLength => {
335 if min_length.is_some() {
336 return Err(DeError::duplicate_field("min_length"));
337 }
338
339 min_length = Some(map.next_value()?);
340 }
341 Field::MinValues => {
342 if min_values.is_some() {
343 return Err(DeError::duplicate_field("min_values"));
344 }
345
346 min_values = Some(map.next_value()?);
347 }
348 Field::Options => {
349 if options.is_some() {
350 return Err(DeError::duplicate_field("options"));
351 }
352
353 options = Some(map.next_value()?);
354 }
355 Field::Placeholder => {
356 if placeholder.is_some() {
357 return Err(DeError::duplicate_field("placeholder"));
358 }
359
360 placeholder = Some(map.next_value()?);
361 }
362 Field::Required => {
363 if required.is_some() {
364 return Err(DeError::duplicate_field("required"));
365 }
366
367 required = Some(map.next_value()?);
368 }
369 Field::Style => {
370 if style.is_some() {
371 return Err(DeError::duplicate_field("style"));
372 }
373
374 style = Some(map.next_value()?);
375 }
376 Field::Type => {
377 if kind.is_some() {
378 return Err(DeError::duplicate_field("type"));
379 }
380
381 kind = Some(map.next_value()?);
382 }
383 Field::Url => {
384 if url.is_some() {
385 return Err(DeError::duplicate_field("url"));
386 }
387
388 url = Some(map.next_value()?);
389 }
390 Field::SkuId => {
391 if sku_id.is_some() {
392 return Err(DeError::duplicate_field("sku_id"));
393 }
394
395 sku_id = map.next_value()?;
396 }
397 Field::Value => {
398 if value.is_some() {
399 return Err(DeError::duplicate_field("value"));
400 }
401
402 value = Some(map.next_value()?);
403 }
404 };
405 }
406
407 let kind = kind.ok_or_else(|| DeError::missing_field("type"))?;
408
409 Ok(match kind {
410 ComponentType::ActionRow => {
413 let components = components.ok_or_else(|| DeError::missing_field("components"))?;
414
415 Self::Value::ActionRow(ActionRow { components })
416 }
417 ComponentType::Button => {
428 let style = style
429 .ok_or_else(|| DeError::missing_field("style"))?
430 .deserialize_into()
431 .map_err(DeserializerError::into_error)?;
432
433 let custom_id = custom_id
434 .flatten()
435 .map(Value::deserialize_into)
436 .transpose()
437 .map_err(DeserializerError::into_error)?;
438
439 Self::Value::Button(Button {
440 custom_id,
441 disabled: disabled.unwrap_or_default(),
442 emoji: emoji.unwrap_or_default(),
443 label: label.flatten(),
444 style,
445 url: url.unwrap_or_default(),
446 sku_id,
447 })
448 }
449 kind @ (ComponentType::TextSelectMenu
461 | ComponentType::UserSelectMenu
462 | ComponentType::RoleSelectMenu
463 | ComponentType::MentionableSelectMenu
464 | ComponentType::ChannelSelectMenu) => {
465 if let ComponentType::TextSelectMenu = kind {
467 if options.is_none() {
468 return Err(DeError::missing_field("options"));
469 }
470 }
471
472 let custom_id = custom_id
473 .flatten()
474 .ok_or_else(|| DeError::missing_field("custom_id"))?
475 .deserialize_into()
476 .map_err(DeserializerError::into_error)?;
477
478 Self::Value::SelectMenu(SelectMenu {
479 channel_types,
480 custom_id,
481 default_values,
482 disabled: disabled.unwrap_or_default(),
483 kind: match kind {
484 ComponentType::TextSelectMenu => SelectMenuType::Text,
485 ComponentType::UserSelectMenu => SelectMenuType::User,
486 ComponentType::RoleSelectMenu => SelectMenuType::Role,
487 ComponentType::MentionableSelectMenu => SelectMenuType::Mentionable,
488 ComponentType::ChannelSelectMenu => SelectMenuType::Channel,
489 _ => {
492 unreachable!("select menu component type is only partially implemented")
493 }
494 },
495 max_values: max_values.unwrap_or_default(),
496 min_values: min_values.unwrap_or_default(),
497 options,
498 placeholder: placeholder.unwrap_or_default(),
499 })
500 }
501 ComponentType::TextInput => {
513 let custom_id = custom_id
514 .flatten()
515 .ok_or_else(|| DeError::missing_field("custom_id"))?
516 .deserialize_into()
517 .map_err(DeserializerError::into_error)?;
518
519 let label = label
520 .flatten()
521 .ok_or_else(|| DeError::missing_field("label"))?;
522
523 let style = style
524 .ok_or_else(|| DeError::missing_field("style"))?
525 .deserialize_into()
526 .map_err(DeserializerError::into_error)?;
527
528 Self::Value::TextInput(TextInput {
529 custom_id,
530 label,
531 max_length: max_length.unwrap_or_default(),
532 min_length: min_length.unwrap_or_default(),
533 placeholder: placeholder.unwrap_or_default(),
534 required: required.unwrap_or_default(),
535 style,
536 value: value.unwrap_or_default(),
537 })
538 }
539 ComponentType::Unknown(unknown) => Self::Value::Unknown(unknown),
540 })
541 }
542}
543
544impl Serialize for Component {
545 #[allow(clippy::too_many_lines)]
546 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
547 let len = match self {
548 Component::ActionRow(_) => 2,
552 Component::Button(button) => {
564 2 + usize::from(button.custom_id.is_some())
565 + usize::from(button.disabled)
566 + usize::from(button.emoji.is_some())
567 + usize::from(button.label.is_some())
568 + usize::from(button.url.is_some())
569 + usize::from(button.sku_id.is_some())
570 }
571 Component::SelectMenu(select_menu) => {
584 2 + usize::from(select_menu.channel_types.is_some())
587 + usize::from(select_menu.default_values.is_some())
588 + usize::from(select_menu.disabled)
589 + usize::from(select_menu.max_values.is_some())
590 + usize::from(select_menu.min_values.is_some())
591 + usize::from(select_menu.options.is_some())
592 + usize::from(select_menu.placeholder.is_some())
593 }
594 Component::TextInput(text_input) => {
607 4 + usize::from(text_input.max_length.is_some())
608 + usize::from(text_input.min_length.is_some())
609 + usize::from(text_input.placeholder.is_some())
610 + usize::from(text_input.required.is_some())
611 + usize::from(text_input.value.is_some())
612 }
613 Component::Unknown(_) => 1,
616 };
617
618 let mut state = serializer.serialize_struct("Component", len)?;
619
620 match self {
621 Component::ActionRow(action_row) => {
622 state.serialize_field("type", &ComponentType::ActionRow)?;
623
624 state.serialize_field("components", &action_row.components)?;
625 }
626 Component::Button(button) => {
627 state.serialize_field("type", &ComponentType::Button)?;
628
629 if button.custom_id.is_some() {
630 state.serialize_field("custom_id", &button.custom_id)?;
631 }
632
633 if button.disabled {
634 state.serialize_field("disabled", &button.disabled)?;
635 }
636
637 if button.emoji.is_some() {
638 state.serialize_field("emoji", &button.emoji)?;
639 }
640
641 if button.label.is_some() {
642 state.serialize_field("label", &button.label)?;
643 }
644
645 state.serialize_field("style", &button.style)?;
646
647 if button.url.is_some() {
648 state.serialize_field("url", &button.url)?;
649 }
650
651 if button.sku_id.is_some() {
652 state.serialize_field("sku_id", &button.sku_id)?;
653 }
654 }
655 Component::SelectMenu(select_menu) => {
656 match &select_menu.kind {
657 SelectMenuType::Text => {
658 state.serialize_field("type", &ComponentType::TextSelectMenu)?;
659 state.serialize_field(
660 "options",
661 &select_menu.options.as_ref().ok_or(SerError::custom(
662 "required field \"option\" missing for text select menu",
663 ))?,
664 )?;
665 }
666 SelectMenuType::User => {
667 state.serialize_field("type", &ComponentType::UserSelectMenu)?;
668 }
669 SelectMenuType::Role => {
670 state.serialize_field("type", &ComponentType::RoleSelectMenu)?;
671 }
672 SelectMenuType::Mentionable => {
673 state.serialize_field("type", &ComponentType::MentionableSelectMenu)?;
674 }
675 SelectMenuType::Channel => {
676 state.serialize_field("type", &ComponentType::ChannelSelectMenu)?;
677 if let Some(channel_types) = &select_menu.channel_types {
678 state.serialize_field("channel_types", channel_types)?;
679 }
680 }
681 }
682
683 state.serialize_field("custom_id", &Some(&select_menu.custom_id))?;
686
687 if select_menu.default_values.is_some() {
688 state.serialize_field("default_values", &select_menu.default_values)?;
689 }
690
691 state.serialize_field("disabled", &select_menu.disabled)?;
692
693 if select_menu.max_values.is_some() {
694 state.serialize_field("max_values", &select_menu.max_values)?;
695 }
696
697 if select_menu.min_values.is_some() {
698 state.serialize_field("min_values", &select_menu.min_values)?;
699 }
700
701 if select_menu.placeholder.is_some() {
702 state.serialize_field("placeholder", &select_menu.placeholder)?;
703 }
704 }
705 Component::TextInput(text_input) => {
706 state.serialize_field("type", &ComponentType::TextInput)?;
707
708 state.serialize_field("custom_id", &Some(&text_input.custom_id))?;
711 state.serialize_field("label", &Some(&text_input.label))?;
712
713 if text_input.max_length.is_some() {
714 state.serialize_field("max_length", &text_input.max_length)?;
715 }
716
717 if text_input.min_length.is_some() {
718 state.serialize_field("min_length", &text_input.min_length)?;
719 }
720
721 if text_input.placeholder.is_some() {
722 state.serialize_field("placeholder", &text_input.placeholder)?;
723 }
724
725 if text_input.required.is_some() {
726 state.serialize_field("required", &text_input.required)?;
727 }
728
729 state.serialize_field("style", &text_input.style)?;
730
731 if text_input.value.is_some() {
732 state.serialize_field("value", &text_input.value)?;
733 }
734 }
735 Component::Unknown(unknown) => {
739 state.serialize_field("type", &ComponentType::Unknown(*unknown))?;
740 }
741 }
742
743 state.end()
744 }
745}
746
747#[cfg(test)]
748mod tests {
749 #![allow(clippy::non_ascii_literal)]
751
752 use super::*;
753 use crate::id::Id;
754 use serde_test::Token;
755 use static_assertions::assert_impl_all;
756
757 assert_impl_all!(
758 Component: From<ActionRow>,
759 From<Button>,
760 From<SelectMenu>,
761 From<TextInput>
762 );
763
764 #[allow(clippy::too_many_lines)]
765 #[test]
766 fn component_full() {
767 let component = Component::ActionRow(ActionRow {
768 components: Vec::from([
769 Component::Button(Button {
770 custom_id: Some("test custom id".into()),
771 disabled: true,
772 emoji: None,
773 label: Some("test label".into()),
774 style: ButtonStyle::Primary,
775 url: None,
776 sku_id: None,
777 }),
778 Component::SelectMenu(SelectMenu {
779 channel_types: None,
780 custom_id: "test custom id 2".into(),
781 default_values: None,
782 disabled: false,
783 kind: SelectMenuType::Text,
784 max_values: Some(25),
785 min_values: Some(5),
786 options: Some(Vec::from([SelectMenuOption {
787 label: "test option label".into(),
788 value: "test option value".into(),
789 description: Some("test description".into()),
790 emoji: None,
791 default: false,
792 }])),
793 placeholder: Some("test placeholder".into()),
794 }),
795 ]),
796 });
797
798 serde_test::assert_tokens(
799 &component,
800 &[
801 Token::Struct {
802 name: "Component",
803 len: 2,
804 },
805 Token::Str("type"),
806 Token::U8(ComponentType::ActionRow.into()),
807 Token::Str("components"),
808 Token::Seq { len: Some(2) },
809 Token::Struct {
810 name: "Component",
811 len: 5,
812 },
813 Token::Str("type"),
814 Token::U8(ComponentType::Button.into()),
815 Token::Str("custom_id"),
816 Token::Some,
817 Token::Str("test custom id"),
818 Token::Str("disabled"),
819 Token::Bool(true),
820 Token::Str("label"),
821 Token::Some,
822 Token::Str("test label"),
823 Token::Str("style"),
824 Token::U8(ButtonStyle::Primary.into()),
825 Token::StructEnd,
826 Token::Struct {
827 name: "Component",
828 len: 6,
829 },
830 Token::Str("type"),
831 Token::U8(ComponentType::TextSelectMenu.into()),
832 Token::Str("options"),
833 Token::Seq { len: Some(1) },
834 Token::Struct {
835 name: "SelectMenuOption",
836 len: 4,
837 },
838 Token::Str("default"),
839 Token::Bool(false),
840 Token::Str("description"),
841 Token::Some,
842 Token::Str("test description"),
843 Token::Str("label"),
844 Token::Str("test option label"),
845 Token::Str("value"),
846 Token::Str("test option value"),
847 Token::StructEnd,
848 Token::SeqEnd,
849 Token::Str("custom_id"),
850 Token::Some,
851 Token::Str("test custom id 2"),
852 Token::Str("disabled"),
853 Token::Bool(false),
854 Token::Str("max_values"),
855 Token::Some,
856 Token::U8(25),
857 Token::Str("min_values"),
858 Token::Some,
859 Token::U8(5),
860 Token::Str("placeholder"),
861 Token::Some,
862 Token::Str("test placeholder"),
863 Token::StructEnd,
864 Token::SeqEnd,
865 Token::StructEnd,
866 ],
867 );
868 }
869
870 #[test]
871 fn action_row() {
872 let value = Component::ActionRow(ActionRow {
873 components: Vec::from([Component::Button(Button {
874 custom_id: Some("button-1".to_owned()),
875 disabled: false,
876 emoji: None,
877 style: ButtonStyle::Primary,
878 label: Some("Button".to_owned()),
879 url: None,
880 sku_id: None,
881 })]),
882 });
883
884 serde_test::assert_tokens(
885 &value,
886 &[
887 Token::Struct {
888 name: "Component",
889 len: 2,
890 },
891 Token::String("type"),
892 Token::U8(ComponentType::ActionRow.into()),
893 Token::String("components"),
894 Token::Seq { len: Some(1) },
895 Token::Struct {
896 name: "Component",
897 len: 4,
898 },
899 Token::String("type"),
900 Token::U8(2),
901 Token::String("custom_id"),
902 Token::Some,
903 Token::String("button-1"),
904 Token::String("label"),
905 Token::Some,
906 Token::String("Button"),
907 Token::String("style"),
908 Token::U8(1),
909 Token::StructEnd,
910 Token::SeqEnd,
911 Token::StructEnd,
912 ],
913 );
914 }
915
916 #[test]
917 fn button() {
918 const FLAG: &str = "🇵🇸";
922
923 let value = Component::Button(Button {
924 custom_id: Some("test".to_owned()),
925 disabled: false,
926 emoji: Some(EmojiReactionType::Unicode {
927 name: FLAG.to_owned(),
928 }),
929 label: Some("Test".to_owned()),
930 style: ButtonStyle::Link,
931 url: Some("https://twilight.rs".to_owned()),
932 sku_id: None,
933 });
934
935 serde_test::assert_tokens(
936 &value,
937 &[
938 Token::Struct {
939 name: "Component",
940 len: 6,
941 },
942 Token::String("type"),
943 Token::U8(ComponentType::Button.into()),
944 Token::String("custom_id"),
945 Token::Some,
946 Token::String("test"),
947 Token::String("emoji"),
948 Token::Some,
949 Token::Struct {
950 name: "EmojiReactionType",
951 len: 1,
952 },
953 Token::String("name"),
954 Token::String(FLAG),
955 Token::StructEnd,
956 Token::String("label"),
957 Token::Some,
958 Token::String("Test"),
959 Token::String("style"),
960 Token::U8(ButtonStyle::Link.into()),
961 Token::String("url"),
962 Token::Some,
963 Token::String("https://twilight.rs"),
964 Token::StructEnd,
965 ],
966 );
967 }
968
969 #[test]
970 fn select_menu() {
971 fn check_select(default_values: Option<Vec<(SelectDefaultValue, &'static str)>>) {
972 let select_menu = Component::SelectMenu(SelectMenu {
973 channel_types: None,
974 custom_id: String::from("my_select"),
975 default_values: default_values
976 .clone()
977 .map(|values| values.into_iter().map(|pair| pair.0).collect()),
978 disabled: false,
979 kind: SelectMenuType::User,
980 max_values: None,
981 min_values: None,
982 options: None,
983 placeholder: None,
984 });
985 let mut tokens = vec![
986 Token::Struct {
987 name: "Component",
988 len: 2 + usize::from(default_values.is_some()),
989 },
990 Token::String("type"),
991 Token::U8(ComponentType::UserSelectMenu.into()),
992 Token::Str("custom_id"),
993 Token::Some,
994 Token::Str("my_select"),
995 ];
996 if let Some(default_values) = default_values {
997 tokens.extend_from_slice(&[
998 Token::Str("default_values"),
999 Token::Some,
1000 Token::Seq {
1001 len: Some(default_values.len()),
1002 },
1003 ]);
1004 for (_, id) in default_values {
1005 tokens.extend_from_slice(&[
1006 Token::Struct {
1007 name: "SelectDefaultValue",
1008 len: 2,
1009 },
1010 Token::Str("type"),
1011 Token::UnitVariant {
1012 name: "SelectDefaultValue",
1013 variant: "user",
1014 },
1015 Token::Str("id"),
1016 Token::NewtypeStruct { name: "Id" },
1017 Token::Str(id),
1018 Token::StructEnd,
1019 ])
1020 }
1021 tokens.push(Token::SeqEnd);
1022 }
1023 tokens.extend_from_slice(&[
1024 Token::Str("disabled"),
1025 Token::Bool(false),
1026 Token::StructEnd,
1027 ]);
1028 serde_test::assert_tokens(&select_menu, &tokens);
1029 }
1030
1031 check_select(None);
1032 check_select(Some(vec![(
1033 SelectDefaultValue::User(Id::new(1234)),
1034 "1234",
1035 )]));
1036 check_select(Some(vec![
1037 (SelectDefaultValue::User(Id::new(1234)), "1234"),
1038 (SelectDefaultValue::User(Id::new(5432)), "5432"),
1039 ]));
1040 }
1041
1042 #[test]
1043 fn text_input() {
1044 let value = Component::TextInput(TextInput {
1045 custom_id: "test".to_owned(),
1046 label: "The label".to_owned(),
1047 max_length: Some(100),
1048 min_length: Some(1),
1049 placeholder: Some("Taking this place".to_owned()),
1050 required: Some(true),
1051 style: TextInputStyle::Short,
1052 value: Some("Hello World!".to_owned()),
1053 });
1054
1055 serde_test::assert_tokens(
1056 &value,
1057 &[
1058 Token::Struct {
1059 name: "Component",
1060 len: 9,
1061 },
1062 Token::String("type"),
1063 Token::U8(ComponentType::TextInput.into()),
1064 Token::String("custom_id"),
1065 Token::Some,
1066 Token::String("test"),
1067 Token::String("label"),
1068 Token::Some,
1069 Token::String("The label"),
1070 Token::String("max_length"),
1071 Token::Some,
1072 Token::U16(100),
1073 Token::String("min_length"),
1074 Token::Some,
1075 Token::U16(1),
1076 Token::String("placeholder"),
1077 Token::Some,
1078 Token::String("Taking this place"),
1079 Token::String("required"),
1080 Token::Some,
1081 Token::Bool(true),
1082 Token::String("style"),
1083 Token::U8(TextInputStyle::Short as u8),
1084 Token::String("value"),
1085 Token::Some,
1086 Token::String("Hello World!"),
1087 Token::StructEnd,
1088 ],
1089 );
1090 }
1091
1092 #[test]
1093 fn premium_button() {
1094 let value = Component::Button(Button {
1095 custom_id: None,
1096 disabled: false,
1097 emoji: None,
1098 label: None,
1099 style: ButtonStyle::Premium,
1100 url: None,
1101 sku_id: Some(Id::new(114_941_315_417_899_012)),
1102 });
1103
1104 serde_test::assert_tokens(
1105 &value,
1106 &[
1107 Token::Struct {
1108 name: "Component",
1109 len: 3,
1110 },
1111 Token::String("type"),
1112 Token::U8(ComponentType::Button.into()),
1113 Token::String("style"),
1114 Token::U8(ButtonStyle::Premium.into()),
1115 Token::String("sku_id"),
1116 Token::Some,
1117 Token::NewtypeStruct { name: "Id" },
1118 Token::Str("114941315417899012"),
1119 Token::StructEnd,
1120 ],
1121 );
1122 }
1123}