1use std::{
4 error::Error,
5 fmt::{Debug, Display, Formatter, Result as FmtResult},
6};
7use twilight_model::channel::message::component::{
8 ActionRow, Button, ButtonStyle, Component, ComponentType, SelectMenu, SelectMenuOption,
9 SelectMenuType, TextInput,
10};
11
12pub const ACTION_ROW_COMPONENT_COUNT: usize = 5;
19
20pub const COMPONENT_COUNT: usize = 5;
27
28pub const COMPONENT_CUSTOM_ID_LENGTH: usize = 100;
38
39pub const COMPONENT_BUTTON_LABEL_LENGTH: usize = 80;
48
49pub const SELECT_MAXIMUM_VALUES_LIMIT: usize = 25;
57
58pub const SELECT_MAXIMUM_VALUES_REQUIREMENT: usize = 1;
66
67pub const SELECT_MINIMUM_VALUES_LIMIT: usize = 25;
75
76pub const SELECT_OPTION_COUNT: usize = 25;
83
84pub const SELECT_OPTION_DESCRIPTION_LENGTH: usize = 100;
91
92pub const SELECT_OPTION_LABEL_LENGTH: usize = 100;
99
100pub const SELECT_OPTION_VALUE_LENGTH: usize = 100;
107
108pub const SELECT_PLACEHOLDER_LENGTH: usize = 150;
115
116pub const TEXT_INPUT_LABEL_MAX: usize = 45;
122
123pub const TEXT_INPUT_LABEL_MIN: usize = 1;
129
130pub const TEXT_INPUT_LENGTH_MAX: usize = 4000;
136
137pub const TEXT_INPUT_LENGTH_MIN: usize = 1;
143
144pub const TEXT_INPUT_PLACEHOLDER_MAX: usize = 100;
150
151#[derive(Debug)]
156pub struct ComponentValidationError {
157 kind: ComponentValidationErrorType,
159}
160
161impl ComponentValidationError {
162 #[must_use = "retrieving the type has no effect if left unused"]
164 pub const fn kind(&self) -> &ComponentValidationErrorType {
165 &self.kind
166 }
167
168 #[allow(clippy::unused_self)]
170 #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
171 pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
172 None
173 }
174
175 #[must_use = "consuming the error into its parts has no effect if left unused"]
177 pub fn into_parts(
178 self,
179 ) -> (
180 ComponentValidationErrorType,
181 Option<Box<dyn Error + Send + Sync>>,
182 ) {
183 (self.kind, None)
184 }
185}
186
187impl Display for ComponentValidationError {
188 #[allow(clippy::too_many_lines)]
189 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
190 match &self.kind {
191 ComponentValidationErrorType::ActionRowComponentCount { count } => {
192 f.write_str("an action row has ")?;
193 Display::fmt(&count, f)?;
194 f.write_str(" children, but the max is ")?;
195
196 Display::fmt(&ACTION_ROW_COMPONENT_COUNT, f)
197 }
198 ComponentValidationErrorType::ButtonConflict => {
199 f.write_str("button has both a custom id and url, which is never valid")
200 }
201 ComponentValidationErrorType::ButtonStyle { style } => {
202 f.write_str("button has a type of ")?;
203 Debug::fmt(style, f)?;
204 f.write_str(", which must have a ")?;
205
206 f.write_str(if *style == ButtonStyle::Link {
207 "url"
208 } else {
209 "custom id"
210 })?;
211
212 f.write_str(" configured")
213 }
214 ComponentValidationErrorType::ComponentCount { count } => {
215 Display::fmt(count, f)?;
216 f.write_str(" components were provided, but the max is ")?;
217
218 Display::fmt(&COMPONENT_COUNT, f)
219 }
220 ComponentValidationErrorType::ComponentCustomIdLength { chars } => {
221 f.write_str("a component's custom id is ")?;
222 Display::fmt(&chars, f)?;
223 f.write_str(" characters long, but the max is ")?;
224
225 Display::fmt(&COMPONENT_CUSTOM_ID_LENGTH, f)
226 }
227 ComponentValidationErrorType::ComponentLabelLength { chars } => {
228 f.write_str("a component's label is ")?;
229 Display::fmt(&chars, f)?;
230 f.write_str(" characters long, but the max is ")?;
231
232 Display::fmt(&COMPONENT_BUTTON_LABEL_LENGTH, f)
233 }
234 ComponentValidationErrorType::InvalidChildComponent { kind } => {
235 f.write_str("a '")?;
236 Display::fmt(&kind, f)?;
237
238 f.write_str(" component was provided, but can not be a child component")
239 }
240 ComponentValidationErrorType::InvalidRootComponent { kind } => {
241 f.write_str("a '")?;
242 Display::fmt(kind, f)?;
243
244 f.write_str("' component was provided, but can not be a root component")
245 }
246 ComponentValidationErrorType::SelectMaximumValuesCount { count } => {
247 f.write_str("maximum number of values that can be chosen is ")?;
248 Display::fmt(count, f)?;
249 f.write_str(", but must be greater than or equal to ")?;
250 Display::fmt(&SELECT_MAXIMUM_VALUES_REQUIREMENT, f)?;
251 f.write_str("and less than or equal to ")?;
252
253 Display::fmt(&SELECT_MAXIMUM_VALUES_LIMIT, f)
254 }
255 ComponentValidationErrorType::SelectMinimumValuesCount { count } => {
256 f.write_str("maximum number of values that must be chosen is ")?;
257 Display::fmt(count, f)?;
258 f.write_str(", but must be less than or equal to ")?;
259
260 Display::fmt(&SELECT_MAXIMUM_VALUES_LIMIT, f)
261 }
262 ComponentValidationErrorType::SelectNotEnoughDefaultValues { provided, min } => {
263 f.write_str("a select menu provided ")?;
264 Display::fmt(provided, f)?;
265 f.write_str(" values, but it requires at least ")?;
266 Display::fmt(min, f)?;
267 f.write_str(" values")
268 }
269 ComponentValidationErrorType::SelectOptionsMissing => {
270 f.write_str("a text select menu doesn't specify the required options field")
271 }
272 ComponentValidationErrorType::SelectOptionDescriptionLength { chars } => {
273 f.write_str("a select menu option's description is ")?;
274 Display::fmt(&chars, f)?;
275 f.write_str(" characters long, but the max is ")?;
276
277 Display::fmt(&SELECT_OPTION_DESCRIPTION_LENGTH, f)
278 }
279 ComponentValidationErrorType::SelectOptionLabelLength { chars } => {
280 f.write_str("a select menu option's label is ")?;
281 Display::fmt(&chars, f)?;
282 f.write_str(" characters long, but the max is ")?;
283
284 Display::fmt(&SELECT_OPTION_LABEL_LENGTH, f)
285 }
286 ComponentValidationErrorType::SelectOptionValueLength { chars } => {
287 f.write_str("a select menu option's value is ")?;
288 Display::fmt(&chars, f)?;
289 f.write_str(" characters long, but the max is ")?;
290
291 Display::fmt(&SELECT_OPTION_VALUE_LENGTH, f)
292 }
293 ComponentValidationErrorType::SelectPlaceholderLength { chars } => {
294 f.write_str("a select menu's placeholder is ")?;
295 Display::fmt(&chars, f)?;
296 f.write_str(" characters long, but the max is ")?;
297
298 Display::fmt(&SELECT_PLACEHOLDER_LENGTH, f)
299 }
300 ComponentValidationErrorType::SelectOptionCount { count } => {
301 f.write_str("a select menu has ")?;
302 Display::fmt(&count, f)?;
303 f.write_str(" options, but the max is ")?;
304
305 Display::fmt(&SELECT_OPTION_COUNT, f)
306 }
307 ComponentValidationErrorType::SelectTooManyDefaultValues { provided, max } => {
308 f.write_str("a select menu provided ")?;
309 Display::fmt(provided, f)?;
310 f.write_str(" values, but it allows at most ")?;
311 Display::fmt(max, f)?;
312 f.write_str(" values")
313 }
314 ComponentValidationErrorType::SelectUnsupportedDefaultValues { kind } => {
315 f.write_str("a select menu has defined default_values, but its type, ")?;
316 Debug::fmt(kind, f)?;
317 f.write_str(", does not support them")
318 }
319 ComponentValidationErrorType::TextInputLabelLength { len: count } => {
320 f.write_str("a text input label length is ")?;
321 Display::fmt(count, f)?;
322 f.write_str(", but it must be at least ")?;
323 Display::fmt(&TEXT_INPUT_LABEL_MIN, f)?;
324 f.write_str(" and at most ")?;
325
326 Display::fmt(&TEXT_INPUT_LABEL_MAX, f)
327 }
328 ComponentValidationErrorType::TextInputMaxLength { len: count } => {
329 f.write_str("a text input max length is ")?;
330 Display::fmt(count, f)?;
331 f.write_str(", but it must be at least ")?;
332 Display::fmt(&TEXT_INPUT_LENGTH_MIN, f)?;
333 f.write_str(" and at most ")?;
334
335 Display::fmt(&TEXT_INPUT_LENGTH_MAX, f)
336 }
337 ComponentValidationErrorType::TextInputMinLength { len: count } => {
338 f.write_str("a text input min length is ")?;
339 Display::fmt(count, f)?;
340 f.write_str(", but it must be at most ")?;
341
342 Display::fmt(&TEXT_INPUT_LENGTH_MAX, f)
343 }
344 ComponentValidationErrorType::TextInputPlaceholderLength { chars } => {
345 f.write_str("a text input's placeholder is ")?;
346 Display::fmt(&chars, f)?;
347 f.write_str(" characters long, but the max is ")?;
348
349 Display::fmt(&TEXT_INPUT_PLACEHOLDER_MAX, f)
350 }
351 ComponentValidationErrorType::TextInputValueLength { chars } => {
352 f.write_str("a text input's value is ")?;
353 Display::fmt(&chars, f)?;
354 f.write_str(" characters long, but the max is ")?;
355
356 Display::fmt(&TEXT_INPUT_PLACEHOLDER_MAX, f)
357 }
358 }
359 }
360}
361
362impl Error for ComponentValidationError {}
363
364#[derive(Debug)]
366#[non_exhaustive]
367pub enum ComponentValidationErrorType {
368 ActionRowComponentCount {
371 count: usize,
373 },
374 ButtonConflict,
376 ButtonStyle {
381 style: ButtonStyle,
383 },
384 ComponentCount {
387 count: usize,
389 },
390 ComponentCustomIdLength {
393 chars: usize,
395 },
396 ComponentLabelLength {
398 chars: usize,
400 },
401 InvalidChildComponent {
403 kind: ComponentType,
405 },
406 InvalidRootComponent {
408 kind: ComponentType,
410 },
411 SelectMaximumValuesCount {
415 count: usize,
417 },
418 SelectMinimumValuesCount {
421 count: usize,
423 },
424 SelectNotEnoughDefaultValues {
426 provided: usize,
428 min: usize,
430 },
431 SelectOptionsMissing,
435 SelectOptionCount {
438 count: usize,
440 },
441 SelectOptionDescriptionLength {
444 chars: usize,
446 },
447 SelectOptionLabelLength {
450 chars: usize,
452 },
453 SelectOptionValueLength {
456 chars: usize,
458 },
459 SelectPlaceholderLength {
462 chars: usize,
464 },
465 SelectTooManyDefaultValues {
467 provided: usize,
469 max: usize,
471 },
472 SelectUnsupportedDefaultValues {
474 kind: SelectMenuType,
476 },
477 TextInputLabelLength {
479 len: usize,
481 },
482 TextInputMaxLength {
484 len: usize,
486 },
487 TextInputMinLength {
489 len: usize,
491 },
492 TextInputPlaceholderLength {
495 chars: usize,
497 },
498 TextInputValueLength {
501 chars: usize,
503 },
504}
505
506pub fn component(component: &Component) -> Result<(), ComponentValidationError> {
524 match component {
525 Component::ActionRow(action_row) => self::action_row(action_row)?,
526 other => {
527 return Err(ComponentValidationError {
528 kind: ComponentValidationErrorType::InvalidRootComponent { kind: other.kind() },
529 });
530 }
531 }
532
533 Ok(())
534}
535
536pub fn action_row(action_row: &ActionRow) -> Result<(), ComponentValidationError> {
559 self::component_action_row_components(&action_row.components)?;
560
561 for component in &action_row.components {
562 match component {
563 Component::ActionRow(_) => {
564 return Err(ComponentValidationError {
565 kind: ComponentValidationErrorType::InvalidChildComponent {
566 kind: ComponentType::ActionRow,
567 },
568 });
569 }
570 Component::Button(button) => self::button(button)?,
571 Component::SelectMenu(select_menu) => self::select_menu(select_menu)?,
572 Component::TextInput(text_input) => self::text_input(text_input)?,
573 Component::Unknown(unknown) => {
574 return Err(ComponentValidationError {
575 kind: ComponentValidationErrorType::InvalidChildComponent {
576 kind: ComponentType::Unknown(*unknown),
577 },
578 })
579 }
580 }
581 }
582
583 Ok(())
584}
585
586pub fn button(button: &Button) -> Result<(), ComponentValidationError> {
608 let has_custom_id = button.custom_id.is_some();
609 let has_url = button.url.is_some();
610
611 if has_custom_id && has_url {
614 return Err(ComponentValidationError {
615 kind: ComponentValidationErrorType::ButtonConflict,
616 });
617 }
618
619 let is_link = button.style == ButtonStyle::Link;
624
625 if (is_link && !has_url) || (!is_link && !has_custom_id) {
626 return Err(ComponentValidationError {
627 kind: ComponentValidationErrorType::ButtonStyle {
628 style: button.style,
629 },
630 });
631 }
632
633 if let Some(custom_id) = button.custom_id.as_ref() {
634 self::component_custom_id(custom_id)?;
635 }
636
637 if let Some(label) = button.label.as_ref() {
638 self::component_button_label(label)?;
639 }
640
641 Ok(())
642}
643
644pub fn select_menu(select_menu: &SelectMenu) -> Result<(), ComponentValidationError> {
694 self::component_custom_id(&select_menu.custom_id)?;
695
696 if let SelectMenuType::Text = &select_menu.kind {
698 let options = select_menu
699 .options
700 .as_ref()
701 .ok_or(ComponentValidationError {
702 kind: ComponentValidationErrorType::SelectOptionsMissing,
703 })?;
704 for option in options {
705 component_select_option_label(&option.label)?;
706 component_select_option_value(&option.value)?;
707
708 if let Some(description) = option.description.as_ref() {
709 component_option_description(description)?;
710 }
711 }
712 component_select_options(options)?;
713 }
714
715 if let Some(placeholder) = select_menu.placeholder.as_ref() {
716 self::component_select_placeholder(placeholder)?;
717 }
718
719 if let Some(max_values) = select_menu.max_values {
720 self::component_select_max_values(usize::from(max_values))?;
721 }
722
723 if let Some(min_values) = select_menu.min_values {
724 self::component_select_min_values(usize::from(min_values))?;
725 }
726
727 if let Some(default_values) = select_menu.default_values.as_ref() {
728 component_select_default_values_supported(select_menu.kind)?;
729 component_select_default_values_count(
730 select_menu.min_values,
731 select_menu.max_values,
732 default_values.len(),
733 )?;
734 }
735
736 Ok(())
737}
738
739pub fn text_input(text_input: &TextInput) -> Result<(), ComponentValidationError> {
765 self::component_custom_id(&text_input.custom_id)?;
766 self::component_text_input_label(&text_input.label)?;
767
768 if let Some(max_length) = text_input.max_length {
769 self::component_text_input_max(max_length)?;
770 }
771
772 if let Some(min_length) = text_input.min_length {
773 self::component_text_input_min(min_length)?;
774 }
775
776 if let Some(placeholder) = text_input.placeholder.as_ref() {
777 self::component_text_input_placeholder(placeholder)?;
778 }
779
780 if let Some(value) = text_input.value.as_ref() {
781 self::component_text_input_value(value)?;
782 }
783
784 Ok(())
785}
786
787const fn component_action_row_components(
800 components: &[Component],
801) -> Result<(), ComponentValidationError> {
802 let count = components.len();
803
804 if count > COMPONENT_COUNT {
805 return Err(ComponentValidationError {
806 kind: ComponentValidationErrorType::ActionRowComponentCount { count },
807 });
808 }
809
810 Ok(())
811}
812
813fn component_button_label(label: impl AsRef<str>) -> Result<(), ComponentValidationError> {
822 let chars = label.as_ref().chars().count();
823
824 if chars > COMPONENT_BUTTON_LABEL_LENGTH {
825 return Err(ComponentValidationError {
826 kind: ComponentValidationErrorType::ComponentLabelLength { chars },
827 });
828 }
829
830 Ok(())
831}
832
833fn component_custom_id(custom_id: impl AsRef<str>) -> Result<(), ComponentValidationError> {
842 let chars = custom_id.as_ref().chars().count();
843
844 if chars > COMPONENT_CUSTOM_ID_LENGTH {
845 return Err(ComponentValidationError {
846 kind: ComponentValidationErrorType::ComponentCustomIdLength { chars },
847 });
848 }
849
850 Ok(())
851}
852
853fn component_option_description(
863 description: impl AsRef<str>,
864) -> Result<(), ComponentValidationError> {
865 let chars = description.as_ref().chars().count();
866
867 if chars > SELECT_OPTION_DESCRIPTION_LENGTH {
868 return Err(ComponentValidationError {
869 kind: ComponentValidationErrorType::SelectOptionDescriptionLength { chars },
870 });
871 }
872
873 Ok(())
874}
875
876const fn component_select_default_values_supported(
883 menu_type: SelectMenuType,
884) -> Result<(), ComponentValidationError> {
885 if !matches!(
886 menu_type,
887 SelectMenuType::User
888 | SelectMenuType::Role
889 | SelectMenuType::Mentionable
890 | SelectMenuType::Channel
891 ) {
892 return Err(ComponentValidationError {
893 kind: ComponentValidationErrorType::SelectUnsupportedDefaultValues { kind: menu_type },
894 });
895 }
896
897 Ok(())
898}
899
900const fn component_select_default_values_count(
910 min_values: Option<u8>,
911 max_values: Option<u8>,
912 default_values: usize,
913) -> Result<(), ComponentValidationError> {
914 if let Some(min) = min_values {
915 let min = min as usize;
916 if default_values < min {
917 return Err(ComponentValidationError {
918 kind: ComponentValidationErrorType::SelectNotEnoughDefaultValues {
919 provided: default_values,
920 min,
921 },
922 });
923 }
924 }
925 if let Some(max) = max_values {
926 let max = max as usize;
927 if default_values > max {
928 return Err(ComponentValidationError {
929 kind: ComponentValidationErrorType::SelectTooManyDefaultValues {
930 provided: default_values,
931 max,
932 },
933 });
934 }
935 }
936
937 Ok(())
938}
939
940const fn component_select_max_values(count: usize) -> Result<(), ComponentValidationError> {
952 if count > SELECT_MAXIMUM_VALUES_LIMIT {
953 return Err(ComponentValidationError {
954 kind: ComponentValidationErrorType::SelectMaximumValuesCount { count },
955 });
956 }
957
958 if count < SELECT_MAXIMUM_VALUES_REQUIREMENT {
959 return Err(ComponentValidationError {
960 kind: ComponentValidationErrorType::SelectMaximumValuesCount { count },
961 });
962 }
963
964 Ok(())
965}
966
967const fn component_select_min_values(count: usize) -> Result<(), ComponentValidationError> {
978 if count > SELECT_MINIMUM_VALUES_LIMIT {
979 return Err(ComponentValidationError {
980 kind: ComponentValidationErrorType::SelectMinimumValuesCount { count },
981 });
982 }
983
984 Ok(())
985}
986
987fn component_select_option_label(label: impl AsRef<str>) -> Result<(), ComponentValidationError> {
997 let chars = label.as_ref().chars().count();
998
999 if chars > SELECT_OPTION_LABEL_LENGTH {
1000 return Err(ComponentValidationError {
1001 kind: ComponentValidationErrorType::SelectOptionLabelLength { chars },
1002 });
1003 }
1004
1005 Ok(())
1006}
1007
1008fn component_select_option_value(value: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1018 let chars = value.as_ref().chars().count();
1019
1020 if chars > SELECT_OPTION_VALUE_LENGTH {
1021 return Err(ComponentValidationError {
1022 kind: ComponentValidationErrorType::SelectOptionValueLength { chars },
1023 });
1024 }
1025
1026 Ok(())
1027}
1028
1029const fn component_select_options(
1044 options: &[SelectMenuOption],
1045) -> Result<(), ComponentValidationError> {
1046 let count = options.len();
1047
1048 if count > SELECT_OPTION_COUNT {
1049 return Err(ComponentValidationError {
1050 kind: ComponentValidationErrorType::SelectOptionCount { count },
1051 });
1052 }
1053
1054 Ok(())
1055}
1056
1057fn component_select_placeholder(
1067 placeholder: impl AsRef<str>,
1068) -> Result<(), ComponentValidationError> {
1069 let chars = placeholder.as_ref().chars().count();
1070
1071 if chars > SELECT_PLACEHOLDER_LENGTH {
1072 return Err(ComponentValidationError {
1073 kind: ComponentValidationErrorType::SelectPlaceholderLength { chars },
1074 });
1075 }
1076
1077 Ok(())
1078}
1079
1080fn component_text_input_label(label: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1092 let len = label.as_ref().len();
1093
1094 if (TEXT_INPUT_LABEL_MIN..=TEXT_INPUT_LABEL_MAX).contains(&len) {
1095 Ok(())
1096 } else {
1097 Err(ComponentValidationError {
1098 kind: ComponentValidationErrorType::TextInputLabelLength { len },
1099 })
1100 }
1101}
1102
1103const fn component_text_input_max(len: u16) -> Result<(), ComponentValidationError> {
1112 let len = len as usize;
1113
1114 if len >= TEXT_INPUT_LENGTH_MIN && len <= TEXT_INPUT_LENGTH_MAX {
1115 Ok(())
1116 } else {
1117 Err(ComponentValidationError {
1118 kind: ComponentValidationErrorType::TextInputMaxLength { len },
1119 })
1120 }
1121}
1122
1123const fn component_text_input_min(len: u16) -> Result<(), ComponentValidationError> {
1132 let len = len as usize;
1133
1134 if len <= TEXT_INPUT_LENGTH_MAX {
1135 Ok(())
1136 } else {
1137 Err(ComponentValidationError {
1138 kind: ComponentValidationErrorType::TextInputMinLength { len },
1139 })
1140 }
1141}
1142
1143fn component_text_input_placeholder(
1155 placeholder: impl AsRef<str>,
1156) -> Result<(), ComponentValidationError> {
1157 let chars = placeholder.as_ref().chars().count();
1158
1159 if chars <= TEXT_INPUT_PLACEHOLDER_MAX {
1160 Ok(())
1161 } else {
1162 Err(ComponentValidationError {
1163 kind: ComponentValidationErrorType::TextInputPlaceholderLength { chars },
1164 })
1165 }
1166}
1167
1168fn component_text_input_value(value: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1177 let chars = value.as_ref().chars().count();
1178
1179 if chars <= TEXT_INPUT_LENGTH_MAX {
1180 Ok(())
1181 } else {
1182 Err(ComponentValidationError {
1183 kind: ComponentValidationErrorType::TextInputValueLength { chars },
1184 })
1185 }
1186}
1187
1188#[allow(clippy::non_ascii_literal)]
1189#[cfg(test)]
1190mod tests {
1191 use super::*;
1192 use static_assertions::{assert_fields, assert_impl_all};
1193 use twilight_model::channel::message::EmojiReactionType;
1194
1195 assert_fields!(ComponentValidationErrorType::ActionRowComponentCount: count);
1196 assert_fields!(ComponentValidationErrorType::ComponentCount: count);
1197 assert_fields!(ComponentValidationErrorType::ComponentCustomIdLength: chars);
1198 assert_fields!(ComponentValidationErrorType::ComponentLabelLength: chars);
1199 assert_fields!(ComponentValidationErrorType::InvalidChildComponent: kind);
1200 assert_fields!(ComponentValidationErrorType::InvalidRootComponent: kind);
1201 assert_fields!(ComponentValidationErrorType::SelectMaximumValuesCount: count);
1202 assert_fields!(ComponentValidationErrorType::SelectMinimumValuesCount: count);
1203 assert_fields!(ComponentValidationErrorType::SelectOptionDescriptionLength: chars);
1204 assert_fields!(ComponentValidationErrorType::SelectOptionLabelLength: chars);
1205 assert_fields!(ComponentValidationErrorType::SelectOptionValueLength: chars);
1206 assert_fields!(ComponentValidationErrorType::SelectPlaceholderLength: chars);
1207 assert_impl_all!(ComponentValidationErrorType: Debug, Send, Sync);
1208 assert_impl_all!(ComponentValidationError: Debug, Send, Sync);
1209
1210 const ALL_BUTTON_STYLES: &[ButtonStyle] = &[
1212 ButtonStyle::Primary,
1213 ButtonStyle::Secondary,
1214 ButtonStyle::Success,
1215 ButtonStyle::Danger,
1216 ButtonStyle::Link,
1217 ButtonStyle::Premium,
1218 ];
1219
1220 #[test]
1221 fn component_action_row() {
1222 let button = Button {
1223 custom_id: None,
1224 disabled: false,
1225 emoji: Some(EmojiReactionType::Unicode {
1226 name: "📚".into()
1227 }),
1228 label: Some("Read".into()),
1229 style: ButtonStyle::Link,
1230 url: Some("https://abebooks.com".into()),
1231 sku_id: None,
1232 };
1233
1234 let select_menu = SelectMenu {
1235 channel_types: None,
1236 custom_id: "custom id 2".into(),
1237 disabled: false,
1238 default_values: None,
1239 kind: SelectMenuType::Text,
1240 max_values: Some(2),
1241 min_values: Some(1),
1242 options: Some(Vec::from([SelectMenuOption {
1243 default: true,
1244 description: Some("Book 1 of the Expanse".into()),
1245 emoji: None,
1246 label: "Leviathan Wakes".into(),
1247 value: "9780316129084".into(),
1248 }])),
1249 placeholder: Some("Choose a book".into()),
1250 };
1251
1252 let action_row = ActionRow {
1253 components: Vec::from([
1254 Component::SelectMenu(select_menu.clone()),
1255 Component::Button(button),
1256 ]),
1257 };
1258
1259 assert!(component(&Component::ActionRow(action_row.clone())).is_ok());
1260
1261 assert!(component(&Component::SelectMenu(select_menu.clone())).is_err());
1262
1263 assert!(super::action_row(&action_row).is_ok());
1264
1265 let invalid_action_row = Component::ActionRow(ActionRow {
1266 components: Vec::from([
1267 Component::SelectMenu(select_menu.clone()),
1268 Component::SelectMenu(select_menu.clone()),
1269 Component::SelectMenu(select_menu.clone()),
1270 Component::SelectMenu(select_menu.clone()),
1271 Component::SelectMenu(select_menu.clone()),
1272 Component::SelectMenu(select_menu),
1273 ]),
1274 });
1275
1276 assert!(component(&invalid_action_row).is_err());
1277 }
1278
1279 #[test]
1282 fn button_conflict() {
1283 let button = Button {
1284 custom_id: Some("a".to_owned()),
1285 disabled: false,
1286 emoji: None,
1287 label: None,
1288 style: ButtonStyle::Primary,
1289 url: Some("https://twilight.rs".to_owned()),
1290 sku_id: None,
1291 };
1292
1293 assert!(matches!(
1294 super::button(&button),
1295 Err(ComponentValidationError {
1296 kind: ComponentValidationErrorType::ButtonConflict,
1297 }),
1298 ));
1299 }
1300
1301 #[test]
1304 fn button_style() {
1305 for style in ALL_BUTTON_STYLES {
1306 let button = Button {
1307 custom_id: None,
1308 disabled: false,
1309 emoji: None,
1310 label: Some("some label".to_owned()),
1311 style: *style,
1312 url: None,
1313 sku_id: None,
1314 };
1315
1316 assert!(matches!(
1317 super::button(&button),
1318 Err(ComponentValidationError {
1319 kind: ComponentValidationErrorType::ButtonStyle {
1320 style: error_style,
1321 }
1322 })
1323 if error_style == *style
1324 ));
1325 }
1326 }
1327
1328 #[test]
1329 fn component_label() {
1330 assert!(component_button_label("").is_ok());
1331 assert!(component_button_label("a").is_ok());
1332 assert!(component_button_label("a".repeat(80)).is_ok());
1333
1334 assert!(component_button_label("a".repeat(81)).is_err());
1335 }
1336
1337 #[test]
1338 fn component_custom_id_length() {
1339 assert!(component_custom_id("").is_ok());
1340 assert!(component_custom_id("a").is_ok());
1341 assert!(component_custom_id("a".repeat(100)).is_ok());
1342
1343 assert!(component_custom_id("a".repeat(101)).is_err());
1344 }
1345
1346 #[test]
1347 fn component_option_description_length() {
1348 assert!(component_option_description("").is_ok());
1349 assert!(component_option_description("a").is_ok());
1350 assert!(component_option_description("a".repeat(100)).is_ok());
1351
1352 assert!(component_option_description("a".repeat(101)).is_err());
1353 }
1354
1355 #[test]
1356 fn component_select_default_values_support() {
1357 assert!(component_select_default_values_supported(SelectMenuType::User).is_ok());
1358 assert!(component_select_default_values_supported(SelectMenuType::Role).is_ok());
1359 assert!(component_select_default_values_supported(SelectMenuType::Mentionable).is_ok());
1360 assert!(component_select_default_values_supported(SelectMenuType::Channel).is_ok());
1361
1362 assert!(component_select_default_values_supported(SelectMenuType::Text).is_err());
1363 }
1364
1365 #[test]
1366 fn component_select_num_default_values() {
1367 assert!(component_select_default_values_count(None, None, 0).is_ok());
1368 assert!(component_select_default_values_count(None, None, 1).is_ok());
1369 assert!(component_select_default_values_count(Some(1), None, 5).is_ok());
1370 assert!(component_select_default_values_count(Some(5), None, 5).is_ok());
1371 assert!(component_select_default_values_count(None, Some(5), 5).is_ok());
1372 assert!(component_select_default_values_count(None, Some(10), 5).is_ok());
1373 assert!(component_select_default_values_count(Some(5), Some(5), 5).is_ok());
1374 assert!(component_select_default_values_count(Some(1), Some(10), 5).is_ok());
1375
1376 assert!(component_select_default_values_count(Some(2), None, 1).is_err());
1377 assert!(component_select_default_values_count(None, Some(1), 2).is_err());
1378 assert!(component_select_default_values_count(Some(1), Some(1), 2).is_err());
1379 assert!(component_select_default_values_count(Some(2), Some(2), 1).is_err());
1380 }
1381
1382 #[test]
1383 fn component_select_max_values_count() {
1384 assert!(component_select_max_values(1).is_ok());
1385 assert!(component_select_max_values(25).is_ok());
1386
1387 assert!(component_select_max_values(0).is_err());
1388 assert!(component_select_max_values(26).is_err());
1389 }
1390
1391 #[test]
1392 fn component_select_min_values_count() {
1393 assert!(component_select_min_values(1).is_ok());
1394 assert!(component_select_min_values(25).is_ok());
1395
1396 assert!(component_select_min_values(26).is_err());
1397 }
1398
1399 #[test]
1400 fn component_select_option_value_length() {
1401 assert!(component_select_option_value("a").is_ok());
1402 assert!(component_select_option_value("a".repeat(100)).is_ok());
1403
1404 assert!(component_select_option_value("a".repeat(101)).is_err());
1405 }
1406
1407 #[test]
1408 fn component_select_options_count() {
1409 let select_menu_options = Vec::from([SelectMenuOption {
1410 default: false,
1411 description: None,
1412 emoji: None,
1413 label: "label".into(),
1414 value: "value".into(),
1415 }]);
1416
1417 assert!(component_select_options(&select_menu_options).is_ok());
1418
1419 let select_menu_options_25 = select_menu_options
1420 .iter()
1421 .cloned()
1422 .cycle()
1423 .take(25)
1424 .collect::<Vec<SelectMenuOption>>();
1425
1426 assert!(component_select_options(&select_menu_options_25).is_ok());
1427
1428 let select_menu_options_26 = select_menu_options
1429 .iter()
1430 .cloned()
1431 .cycle()
1432 .take(26)
1433 .collect::<Vec<SelectMenuOption>>();
1434
1435 assert!(component_select_options(&select_menu_options_26).is_err());
1436 }
1437
1438 #[test]
1439 fn component_select_placeholder_length() {
1440 assert!(component_select_placeholder("").is_ok());
1441 assert!(component_select_placeholder("a").is_ok());
1442 assert!(component_select_placeholder("a".repeat(150)).is_ok());
1443
1444 assert!(component_select_placeholder("a".repeat(151)).is_err());
1445 }
1446
1447 #[test]
1448 fn component_text_input_label_length() {
1449 assert!(component_text_input_label("a").is_ok());
1450 assert!(component_text_input_label("a".repeat(45)).is_ok());
1451
1452 assert!(component_text_input_label("").is_err());
1453 assert!(component_text_input_label("a".repeat(46)).is_err());
1454 }
1455
1456 #[test]
1457 fn component_text_input_max_count() {
1458 assert!(component_text_input_max(1).is_ok());
1459 assert!(component_text_input_max(4000).is_ok());
1460
1461 assert!(component_text_input_max(0).is_err());
1462 assert!(component_text_input_max(4001).is_err());
1463 }
1464
1465 #[test]
1466 fn component_text_input_min_count() {
1467 assert!(component_text_input_min(0).is_ok());
1468 assert!(component_text_input_min(1).is_ok());
1469 assert!(component_text_input_min(4000).is_ok());
1470
1471 assert!(component_text_input_min(4001).is_err());
1472 }
1473
1474 #[test]
1475 fn component_text_input_placeholder_length() {
1476 assert!(component_text_input_placeholder("").is_ok());
1477 assert!(component_text_input_placeholder("a").is_ok());
1478 assert!(component_text_input_placeholder("a".repeat(100)).is_ok());
1479
1480 assert!(component_text_input_placeholder("a".repeat(101)).is_err());
1481 }
1482
1483 #[test]
1484 fn component_text_input_value() {
1485 assert!(component_text_input_min(0).is_ok());
1486 assert!(component_text_input_min(1).is_ok());
1487 assert!(component_text_input_min(4000).is_ok());
1488
1489 assert!(component_text_input_min(4001).is_err());
1490 }
1491}