Skip to main content

twilight_validate/
component.rs

1//! Constants, error types, and functions for validating [`Component`]s.
2
3mod component_v2;
4
5use std::{
6    error::Error,
7    fmt::{Debug, Display, Formatter, Result as FmtResult},
8};
9use twilight_model::channel::message::component::{
10    ActionRow, Button, ButtonStyle, Component, ComponentType, SelectMenu, SelectMenuOption,
11    SelectMenuType, TextInput,
12};
13
14pub use component_v2::{
15    CHECKBOXGROUP_MAXIMUM_VALUES_LIMIT, CHECKBOXGROUP_MAXIMUM_VALUES_REQUIREMENT,
16    CHECKBOXGROUP_MINIMUM_VALUES_LIMIT, CHECKBOXGROUP_OPTION_COUNT,
17    FILE_UPLOAD_MAXIMUM_VALUES_LIMIT, FILE_UPLOAD_MINIMUM_VALUES_LIMIT,
18    LABEL_DESCRIPTION_LENGTH_MAX, LABEL_LABEL_LENGTH_MAX,
19    MEDIA_GALLERY_ITEM_DESCRIPTION_LENGTH_MAX, MEDIA_GALLERY_ITEMS_MAX, MEDIA_GALLERY_ITEMS_MIN,
20    SECTION_COMPONENTS_MAX, SECTION_COMPONENTS_MIN, TEXT_DISPLAY_CONTENT_LENGTH_MAX,
21    THUMBNAIL_DESCRIPTION_LENGTH_MAX, component_v2, container, file_upload, label, media_gallery,
22    media_gallery_item, section, text_display, thumbnail,
23};
24
25/// Maximum number of [`Component`]s allowed inside an [`ActionRow`].
26///
27/// This is defined in Discord's documentation, per
28/// [Discord Docs/Action Rows][1].
29///
30/// [1]: https://discord.com/developers/docs/components/reference#action-row
31pub const ACTION_ROW_COMPONENT_COUNT: usize = 5;
32
33/// Maximum number of root [`Component`]s in a message.
34///
35/// This is defined in Discord's documentation, per
36/// [Discord Docs][1].
37///
38/// [1]: https://discord.com/developers/docs/components/reference#legacy-message-component-behavior
39pub const COMPONENT_COUNT: usize = 5;
40
41/// Maximum total number of [`Component`]s in a component V2 message.
42///
43/// This is defined in Discord's documentation, per
44/// [Discord Docs][1].
45///
46/// [1]: https://discord.com/developers/docs/components/reference#component-reference
47pub const COMPONENT_V2_COUNT: usize = 40;
48
49/// Maximum length of a [`Component`] custom ID in codepoints.
50///
51/// An example of a component with a custom ID is the
52/// [`Button`][`Button::custom_id`].
53///
54/// This is defined in Discord's documentation, per
55/// [Discord Docs/Components][1].
56///
57/// [1]: https://discord.com/developers/docs/interactions/message-components#component-object-component-structure
58pub const COMPONENT_CUSTOM_ID_LENGTH: usize = 100;
59
60/// Maximum [`Component`] label length in codepoints.
61///
62/// An example of a component with a label is the [`Button`][`Button::label`].
63///
64/// This is defined in Discord's documentation, per
65/// [Discord Docs/Components][1].
66///
67/// [1]: https://discord.com/developers/docs/interactions/message-components#component-object-component-structure
68pub const COMPONENT_BUTTON_LABEL_LENGTH: usize = 80;
69
70/// Maximum number of [`SelectMenuOption`]s that can be chosen in a
71/// [`SelectMenu`].
72///
73/// This is defined in Dicsord's documentation, per
74/// [Discord Docs/Select Menu][1].
75///
76/// [1]: https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
77pub const SELECT_MAXIMUM_VALUES_LIMIT: usize = 25;
78
79/// Minimum number of [`SelectMenuOption`]s that can be chosen in a
80/// [`SelectMenu`].
81///
82/// This is defined in Dicsord's documentation, per
83/// [Discord Docs/Select Menu][1].
84///
85/// [1]: https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
86pub const SELECT_MAXIMUM_VALUES_REQUIREMENT: usize = 1;
87
88/// Maximum number of [`SelectMenuOption`]s that must be chosen in a
89/// [`SelectMenu`].
90///
91/// This is defined in Dicsord's documentation, per
92/// [Discord Docs/Select Menu][1].
93///
94/// [1]: https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
95pub const SELECT_MINIMUM_VALUES_LIMIT: usize = 25;
96
97/// Maximum number of [`SelectMenuOption`]s in a [`SelectMenu`].
98///
99/// This is defined in Discord's documentation, per
100/// [Discord Docs/Select Menu][1].
101///
102/// [1]: https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
103pub const SELECT_OPTION_COUNT: usize = 25;
104
105/// Maximum length of a [`SelectMenuOption::description`] in codepoints.
106///
107/// This is defined in Discord's documentation, per
108/// [Discord Docs/Select Menu Option][1].
109///
110/// [1]: https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
111pub const SELECT_OPTION_DESCRIPTION_LENGTH: usize = 100;
112
113/// Maximum length of a [`SelectMenuOption::label`] in codepoints.
114///
115/// This is defined in Discord's documentation, per
116/// [Discord Docs/Select Menu Option][1].
117///
118/// [1]: https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
119pub const SELECT_OPTION_LABEL_LENGTH: usize = 100;
120
121/// Maximum length of a [`SelectMenuOption::value`] in codepoints.
122///
123/// This is defined in Discord's documentation, per
124/// [Discord Docs/Select Menu Option][1].
125///
126/// [1]: https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
127pub const SELECT_OPTION_VALUE_LENGTH: usize = 100;
128
129/// Maximum length of a [`SelectMenu::placeholder`] in codepoints.
130///
131/// This is defined in Discord's documentation, per
132/// [Discord Docs/Select Menu][1].
133///
134/// [1]: https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure
135pub const SELECT_PLACEHOLDER_LENGTH: usize = 150;
136
137/// Maximum length of [`TextInput::label`].
138///
139/// This is based on [Discord Docs/Text Inputs].
140///
141/// [Discord Docs/Text Inputs]: https://discord.com/developers/docs/interactions/message-components#text-inputs
142pub const TEXT_INPUT_LABEL_MAX: usize = 45;
143
144/// Minimum length of [`TextInput::label`].
145///
146/// This is based on [Discord Docs/Text Inputs].
147///
148/// [Discord Docs/Text Inputs]: https://discord.com/developers/docs/interactions/message-components#text-inputs
149pub const TEXT_INPUT_LABEL_MIN: usize = 1;
150
151/// Maximum length of [`TextInput::value`].
152///
153/// This is based on [Discord Docs/Text Inputs].
154///
155/// [Discord Docs/Text Inputs]: https://discord.com/developers/docs/interactions/message-components#text-inputs
156pub const TEXT_INPUT_LENGTH_MAX: usize = 4000;
157
158/// Minimum length of [`TextInput::value`].
159///
160/// This is based on [Discord Docs/Text Inputs].
161///
162/// [Discord Docs/Text Inputs]: https://discord.com/developers/docs/interactions/message-components#text-inputs
163pub const TEXT_INPUT_LENGTH_MIN: usize = 1;
164
165/// Maximum length of a [`TextInput::placeholder`] in codepoints.
166///
167/// This is based on [Discord Docs/Text Inputs].
168///
169/// [Discord Docs/Text Inputs]: https://discord.com/developers/docs/interactions/message-components#text-inputs
170pub const TEXT_INPUT_PLACEHOLDER_MAX: usize = 100;
171
172/// A provided [`Component`] is invalid.
173///
174/// While multiple components may be invalid, validation will short-circuit on
175/// the first invalid component.
176#[derive(Debug)]
177pub struct ComponentValidationError {
178    /// Type of error that occurred.
179    kind: ComponentValidationErrorType,
180}
181
182impl ComponentValidationError {
183    /// Immutable reference to the type of error that occurred.
184    #[must_use = "retrieving the type has no effect if left unused"]
185    pub const fn kind(&self) -> &ComponentValidationErrorType {
186        &self.kind
187    }
188
189    /// Consume the error, returning the source error if there is any.
190    #[allow(clippy::unused_self)]
191    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
192    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
193        None
194    }
195
196    /// Consume the error, returning the owned error type and the source error.
197    #[must_use = "consuming the error into its parts has no effect if left unused"]
198    pub fn into_parts(
199        self,
200    ) -> (
201        ComponentValidationErrorType,
202        Option<Box<dyn Error + Send + Sync>>,
203    ) {
204        (self.kind, None)
205    }
206}
207
208impl Display for ComponentValidationError {
209    #[allow(clippy::too_many_lines)]
210    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
211        match &self.kind {
212            ComponentValidationErrorType::ActionRowComponentCount { count } => {
213                f.write_str("an action row has ")?;
214                Display::fmt(&count, f)?;
215                f.write_str(" children, but the max is ")?;
216
217                Display::fmt(&ACTION_ROW_COMPONENT_COUNT, f)
218            }
219            ComponentValidationErrorType::ButtonConflict => {
220                f.write_str("button has both a custom id and url, which is never valid")
221            }
222            ComponentValidationErrorType::ButtonStyle { style } => {
223                f.write_str("button has a type of ")?;
224                Debug::fmt(style, f)?;
225                f.write_str(", which must have a ")?;
226
227                f.write_str(if *style == ButtonStyle::Link {
228                    "url"
229                } else {
230                    "custom id"
231                })?;
232
233                f.write_str(" configured")
234            }
235            ComponentValidationErrorType::CheckboxGroupMaximumValuesCount { count } => {
236                f.write_str("maximum number of values that can be chosen is ")?;
237                Display::fmt(count, f)?;
238                f.write_str(", but must be greater than or equal to ")?;
239                Display::fmt(&CHECKBOXGROUP_MAXIMUM_VALUES_REQUIREMENT, f)?;
240                f.write_str("and less than or equal to ")?;
241
242                Display::fmt(&CHECKBOXGROUP_MAXIMUM_VALUES_LIMIT, f)
243            }
244            ComponentValidationErrorType::CheckboxGroupMinimumValuesCount { count } => {
245                f.write_str("minimum number of values that must be chosen is ")?;
246                Display::fmt(count, f)?;
247                f.write_str(", but must be less than or equal to ")?;
248
249                Display::fmt(&CHECKBOXGROUP_MINIMUM_VALUES_LIMIT, f)
250            }
251            ComponentValidationErrorType::CheckboxGroupOptionCount { count } => {
252                f.write_str("a checkbox group has ")?;
253                Display::fmt(&count, f)?;
254                f.write_str(" options, but the max is ")?;
255
256                Display::fmt(&CHECKBOXGROUP_OPTION_COUNT, f)
257            }
258            ComponentValidationErrorType::CheckboxGroupOptionsMissing => {
259                f.write_str("a checkbox group is missing it's options field")
260            }
261            ComponentValidationErrorType::CheckboxGroupRequiredWithNoMin => f.write_str(
262                "a checkbox group is marked required but has a min of 0 selected options",
263            ),
264            ComponentValidationErrorType::ComponentCount { count } => {
265                Display::fmt(count, f)?;
266                f.write_str(" components were provided, but the max is ")?;
267
268                Display::fmt(&COMPONENT_COUNT, f)
269            }
270            ComponentValidationErrorType::ComponentCustomIdLength { chars } => {
271                f.write_str("a component's custom id is ")?;
272                Display::fmt(&chars, f)?;
273                f.write_str(" characters long, but the max is ")?;
274
275                Display::fmt(&COMPONENT_CUSTOM_ID_LENGTH, f)
276            }
277            ComponentValidationErrorType::ComponentLabelLength { chars } => {
278                f.write_str("a component's label is ")?;
279                Display::fmt(&chars, f)?;
280                f.write_str(" characters long, but the max is ")?;
281
282                Display::fmt(&COMPONENT_BUTTON_LABEL_LENGTH, f)
283            }
284            ComponentValidationErrorType::InvalidChildComponent { kind } => {
285                f.write_str("a '")?;
286                Display::fmt(&kind, f)?;
287
288                f.write_str(" component was provided, but can not be a child component")
289            }
290            ComponentValidationErrorType::InvalidRootComponent { kind } => {
291                f.write_str("a '")?;
292                Display::fmt(kind, f)?;
293
294                f.write_str("' component was provided, but can not be a root component")
295            }
296            ComponentValidationErrorType::SelectMaximumValuesCount { count } => {
297                f.write_str("maximum number of values that can be chosen is ")?;
298                Display::fmt(count, f)?;
299                f.write_str(", but must be greater than or equal to ")?;
300                Display::fmt(&SELECT_MAXIMUM_VALUES_REQUIREMENT, f)?;
301                f.write_str("and less than or equal to ")?;
302
303                Display::fmt(&SELECT_MAXIMUM_VALUES_LIMIT, f)
304            }
305            ComponentValidationErrorType::SelectMinimumValuesCount { count } => {
306                f.write_str("minimum number of values that must be chosen is ")?;
307                Display::fmt(count, f)?;
308                f.write_str(", but must be less than or equal to ")?;
309
310                Display::fmt(&SELECT_MINIMUM_VALUES_LIMIT, f)
311            }
312            ComponentValidationErrorType::SelectNotEnoughDefaultValues { provided, min } => {
313                f.write_str("a select menu provided ")?;
314                Display::fmt(provided, f)?;
315                f.write_str(" values, but it requires at least ")?;
316                Display::fmt(min, f)?;
317                f.write_str(" values")
318            }
319            ComponentValidationErrorType::SelectOptionsMissing => {
320                f.write_str("a text select menu doesn't specify the required options field")
321            }
322            ComponentValidationErrorType::SelectOptionDescriptionLength { chars } => {
323                f.write_str("a select menu option's description is ")?;
324                Display::fmt(&chars, f)?;
325                f.write_str(" characters long, but the max is ")?;
326
327                Display::fmt(&SELECT_OPTION_DESCRIPTION_LENGTH, f)
328            }
329            ComponentValidationErrorType::SelectOptionLabelLength { chars } => {
330                f.write_str("a select menu option's label is ")?;
331                Display::fmt(&chars, f)?;
332                f.write_str(" characters long, but the max is ")?;
333
334                Display::fmt(&SELECT_OPTION_LABEL_LENGTH, f)
335            }
336            ComponentValidationErrorType::SelectOptionValueLength { chars } => {
337                f.write_str("a select menu option's value is ")?;
338                Display::fmt(&chars, f)?;
339                f.write_str(" characters long, but the max is ")?;
340
341                Display::fmt(&SELECT_OPTION_VALUE_LENGTH, f)
342            }
343            ComponentValidationErrorType::SelectPlaceholderLength { chars } => {
344                f.write_str("a select menu's placeholder is ")?;
345                Display::fmt(&chars, f)?;
346                f.write_str(" characters long, but the max is ")?;
347
348                Display::fmt(&SELECT_PLACEHOLDER_LENGTH, f)
349            }
350            ComponentValidationErrorType::SelectOptionCount { count } => {
351                f.write_str("a select menu has ")?;
352                Display::fmt(&count, f)?;
353                f.write_str(" options, but the max is ")?;
354
355                Display::fmt(&SELECT_OPTION_COUNT, f)
356            }
357            ComponentValidationErrorType::SelectTooManyDefaultValues { provided, max } => {
358                f.write_str("a select menu provided ")?;
359                Display::fmt(provided, f)?;
360                f.write_str(" values, but it allows at most ")?;
361                Display::fmt(max, f)?;
362                f.write_str(" values")
363            }
364            ComponentValidationErrorType::SelectUnsupportedDefaultValues { kind } => {
365                f.write_str("a select menu has defined default_values, but its type, ")?;
366                Debug::fmt(kind, f)?;
367                f.write_str(", does not support them")
368            }
369            ComponentValidationErrorType::TextInputLabelLength { len: count } => {
370                f.write_str("a text input label length is ")?;
371                Display::fmt(count, f)?;
372                f.write_str(", but it must be at least ")?;
373                Display::fmt(&TEXT_INPUT_LABEL_MIN, f)?;
374                f.write_str(" and at most ")?;
375
376                Display::fmt(&TEXT_INPUT_LABEL_MAX, f)
377            }
378            ComponentValidationErrorType::TextInputMaxLength { len: count } => {
379                f.write_str("a text input max length is ")?;
380                Display::fmt(count, f)?;
381                f.write_str(", but it must be at least ")?;
382                Display::fmt(&TEXT_INPUT_LENGTH_MIN, f)?;
383                f.write_str(" and at most ")?;
384
385                Display::fmt(&TEXT_INPUT_LENGTH_MAX, f)
386            }
387            ComponentValidationErrorType::TextInputMinLength { len: count } => {
388                f.write_str("a text input min length is ")?;
389                Display::fmt(count, f)?;
390                f.write_str(", but it must be at most ")?;
391
392                Display::fmt(&TEXT_INPUT_LENGTH_MAX, f)
393            }
394            ComponentValidationErrorType::TextInputPlaceholderLength { chars } => {
395                f.write_str("a text input's placeholder is ")?;
396                Display::fmt(&chars, f)?;
397                f.write_str(" characters long, but the max is ")?;
398
399                Display::fmt(&TEXT_INPUT_PLACEHOLDER_MAX, f)
400            }
401            ComponentValidationErrorType::TextInputValueLength { chars } => {
402                f.write_str("a text input's value is ")?;
403                Display::fmt(&chars, f)?;
404                f.write_str(" characters long, but the max is ")?;
405
406                Display::fmt(&TEXT_INPUT_PLACEHOLDER_MAX, f)
407            }
408            ComponentValidationErrorType::TextInputDisallowedLabel => {
409                f.write_str("a text input contained a label when disallowed")
410            }
411            ComponentValidationErrorType::DisallowedV2Components => {
412                f.write_str("a V2 component was used in a component V1 message")
413            }
414            ComponentValidationErrorType::DisallowedChildren => {
415                f.write_str("a component contains a disallowed child component")
416            }
417            ComponentValidationErrorType::TextDisplayContentTooLong { len: count } => {
418                f.write_str("a text display content length is ")?;
419                Display::fmt(count, f)?;
420                f.write_str(" characters long, but the max is ")?;
421
422                Display::fmt(&TEXT_DISPLAY_CONTENT_LENGTH_MAX, f)
423            }
424            ComponentValidationErrorType::MediaGalleryItemCountOutOfRange { count } => {
425                f.write_str("a media gallery has ")?;
426                Display::fmt(count, f)?;
427                f.write_str(" items, but the min and max are ")?;
428                Display::fmt(&MEDIA_GALLERY_ITEMS_MIN, f)?;
429                f.write_str(" and ")?;
430                Display::fmt(&MEDIA_GALLERY_ITEMS_MAX, f)?;
431
432                f.write_str(" respectively")
433            }
434            ComponentValidationErrorType::MediaGalleryItemDescriptionTooLong { len } => {
435                f.write_str("a media gallery item description length is ")?;
436                Display::fmt(len, f)?;
437                f.write_str(" characters long, but the max is ")?;
438
439                Display::fmt(&MEDIA_GALLERY_ITEM_DESCRIPTION_LENGTH_MAX, f)
440            }
441            ComponentValidationErrorType::SectionComponentCountOutOfRange { count } => {
442                f.write_str("a section has ")?;
443                Display::fmt(count, f)?;
444                f.write_str(" components, but the min and max are ")?;
445                Display::fmt(&SECTION_COMPONENTS_MIN, f)?;
446                f.write_str(" and ")?;
447                Display::fmt(&SECTION_COMPONENTS_MAX, f)?;
448
449                f.write_str(" respectively")
450            }
451            ComponentValidationErrorType::SelectDisallowedDisabled => {
452                f.write_str("a select menu was disabled when disallowed")
453            }
454            ComponentValidationErrorType::ThumbnailDescriptionTooLong { len } => {
455                f.write_str("a thumbnail description length is ")?;
456                Display::fmt(len, f)?;
457                f.write_str(" characters long, but the max is ")?;
458
459                Display::fmt(&THUMBNAIL_DESCRIPTION_LENGTH_MAX, f)
460            }
461            ComponentValidationErrorType::LabelLabelTooLong { len } => {
462                f.write_str("a label text of a label component is ")?;
463                Display::fmt(len, f)?;
464                f.write_str(" characters long, but the max is ")?;
465
466                Display::fmt(&LABEL_LABEL_LENGTH_MAX, f)
467            }
468            ComponentValidationErrorType::LabelDescriptionTooLong { len } => {
469                f.write_str("a label description length is ")?;
470                Display::fmt(len, f)?;
471                f.write_str(" characters long, but the max is ")?;
472
473                Display::fmt(&LABEL_DESCRIPTION_LENGTH_MAX, f)
474            }
475            ComponentValidationErrorType::FileUploadMaximumValuesCount { count } => {
476                f.write_str("maximum number of files that can be uploaded is ")?;
477                Display::fmt(count, f)?;
478                f.write_str(", but must be less than or equal to ")?;
479
480                Display::fmt(&FILE_UPLOAD_MAXIMUM_VALUES_LIMIT, f)
481            }
482            ComponentValidationErrorType::FileUploadMinimumValuesCount { count } => {
483                f.write_str("minimum number of files that must be uploaded is ")?;
484                Display::fmt(count, f)?;
485                f.write_str(", but must be less than or equal to ")?;
486
487                Display::fmt(&FILE_UPLOAD_MINIMUM_VALUES_LIMIT, f)
488            }
489        }
490    }
491}
492
493impl Error for ComponentValidationError {}
494
495/// Type of [`ComponentValidationError`] that occurred.
496#[derive(Debug)]
497#[non_exhaustive]
498pub enum ComponentValidationErrorType {
499    /// Number of components a provided [`ActionRow`] is larger than
500    /// [the maximum][`ACTION_ROW_COMPONENT_COUNT`].
501    ActionRowComponentCount {
502        /// Number of components within the action row.
503        count: usize,
504    },
505    /// Button has both a custom ID and URL set.
506    ButtonConflict,
507    /// Button does not have the required field based on its style.
508    ///
509    /// A button with a style of [`ButtonStyle::Link`] must have a URL set,
510    /// while buttons of other styles must have a custom ID set.
511    ButtonStyle {
512        /// Style of the button.
513        style: ButtonStyle,
514    },
515    /// Maximum number of items that can be chosen is smaller than
516    /// [the minimum][`CHECKBOXGROUP_MAXIMUM_VALUES_REQUIREMENT`] or larger than
517    /// [the maximum][`CHECKBOXGROUP_MAXIMUM_VALUES_LIMIT`].
518    CheckboxGroupMaximumValuesCount {
519        /// Number of options that were provided.
520        count: usize,
521    },
522    /// Number of checkbox group options provided is larger than
523    /// [the maximum][`CHECKBOXGROUP_OPTION_COUNT`]
524    CheckboxGroupOptionCount {
525        /// Number of options that were provided
526        count: usize,
527    },
528    /// Minimum values exceeds limit
529    CheckboxGroupMinimumValuesCount {
530        /// Number of options that were provided.
531        count: usize,
532    },
533    /// Checkbox group is missing options
534    CheckboxGroupOptionsMissing,
535    /// Checkbox group minimum is set to 0 but marked as required
536    CheckboxGroupRequiredWithNoMin,
537    /// Number of components provided is larger than
538    /// [the maximum][`COMPONENT_COUNT`].
539    ComponentCount {
540        /// Number of components that were provided.
541        count: usize,
542    },
543    /// Component custom ID is larger than the
544    /// [the maximum][`COMPONENT_CUSTOM_ID_LENGTH`].
545    ComponentCustomIdLength {
546        /// Number of codepoints that were provided.
547        chars: usize,
548    },
549    /// Component label is larger than [the maximum][`COMPONENT_BUTTON_LABEL_LENGTH`].
550    ComponentLabelLength {
551        /// Number of codepoints that were provided.
552        chars: usize,
553    },
554    /// Provided component cannot be a child component.
555    InvalidChildComponent {
556        /// Type of provided component.
557        kind: ComponentType,
558    },
559    /// Provided component cannot be a root component.
560    InvalidRootComponent {
561        /// Type of provided component.
562        kind: ComponentType,
563    },
564    /// Maximum number of items that can be chosen is smaller than
565    /// [the minimum][`SELECT_MAXIMUM_VALUES_REQUIREMENT`] or larger than
566    /// [the maximum][`SELECT_MAXIMUM_VALUES_LIMIT`].
567    SelectMaximumValuesCount {
568        /// Number of options that were provided.
569        count: usize,
570    },
571    /// Minimum number of items that must be chosen is larger than
572    /// [the maximum][`SELECT_MINIMUM_VALUES_LIMIT`].
573    SelectMinimumValuesCount {
574        /// Number of options that were provided.
575        count: usize,
576    },
577    /// The select menu specifies less default values than its own minimum values requirement.
578    SelectNotEnoughDefaultValues {
579        /// Number of default values provided.
580        provided: usize,
581        /// Select menu's minimum number of default values.
582        min: usize,
583    },
584    /// The `options` field is `None` for a [text select menu][text-select].
585    ///
586    /// [text-select]: SelectMenuType::Text
587    SelectOptionsMissing,
588    /// Number of select menu options provided is larger than
589    /// [the maximum][`SELECT_OPTION_COUNT`].
590    SelectOptionCount {
591        /// Number of options that were provided.
592        count: usize,
593    },
594    /// Description of a select menu option is larger than
595    /// [the maximum][`SELECT_OPTION_DESCRIPTION_LENGTH`].
596    SelectOptionDescriptionLength {
597        /// Number of codepoints that were provided.
598        chars: usize,
599    },
600    /// Label of a select menu option is larger than
601    /// [the maximum][`SELECT_OPTION_LABEL_LENGTH`].
602    SelectOptionLabelLength {
603        /// Number of codepoints that were provided.
604        chars: usize,
605    },
606    /// Value of a select menu option is larger than
607    /// [the maximum][`SELECT_OPTION_VALUE_LENGTH`].
608    SelectOptionValueLength {
609        /// Number of codepoints that were provided.
610        chars: usize,
611    },
612    /// Placeholder of a component is larger than the
613    /// [maximum][`SELECT_PLACEHOLDER_LENGTH`].
614    SelectPlaceholderLength {
615        /// Number of codepoints that were provided.
616        chars: usize,
617    },
618    /// The select menu specifies less default values than its own minimum values requirement.
619    SelectTooManyDefaultValues {
620        /// Number of default values provided.
621        provided: usize,
622        /// Select menu's maximum number of values.
623        max: usize,
624    },
625    /// The select menu type doesn't support the `default_values` field.
626    SelectUnsupportedDefaultValues {
627        /// The select menu's type.
628        kind: SelectMenuType,
629    },
630    /// [`TextInput::label`] is invalid.
631    TextInputLabelLength {
632        /// Provided length.
633        len: usize,
634    },
635    /// [`TextInput::max_length`] is invalid.
636    TextInputMaxLength {
637        /// Provided length.
638        len: usize,
639    },
640    /// [`TextInput::min_length`] is too long.
641    TextInputMinLength {
642        /// Provided length.
643        len: usize,
644    },
645    /// Placeholder of a [`TextInput`] component is larger than
646    /// [`TEXT_INPUT_PLACEHOLDER_MAX`].
647    TextInputPlaceholderLength {
648        /// Provided number of codepoints.
649        chars: usize,
650    },
651    /// Value of a [`TextInput`] component is larger than
652    /// [`TEXT_INPUT_LENGTH_MAX`].
653    TextInputValueLength {
654        /// Provided number of codepoints.
655        chars: usize,
656    },
657    /// A [`TextInput`] component contained a label in a context where it is not allowed.
658    TextInputDisallowedLabel,
659    /// V2 components used in a V1 component.
660    DisallowedV2Components,
661    /// Disallowed children components are found in a root component.
662    DisallowedChildren,
663    /// Content of text display component is too long.
664    TextDisplayContentTooLong {
665        /// Length of the provided content.
666        len: usize,
667    },
668    /// The number of items in a media gallery is out of range.
669    MediaGalleryItemCountOutOfRange {
670        /// Number of items in the media gallery.
671        count: usize,
672    },
673    /// The description of a media gallery item is too long.
674    MediaGalleryItemDescriptionTooLong {
675        /// Length of the provided description.
676        len: usize,
677    },
678    /// The number of components in a section is out of range.
679    SectionComponentCountOutOfRange {
680        /// Number of components in the section.
681        count: usize,
682    },
683    /// A select component's `disabled` field was `true` in a context where disabled selects are
684    /// not allowed.
685    SelectDisallowedDisabled,
686    /// The length of the thumbnail description is too long.
687    ThumbnailDescriptionTooLong {
688        /// Length of the provided description.
689        len: usize,
690    },
691    /// The length of the label text of a label is too long.
692    LabelLabelTooLong {
693        /// Length of the provided label.
694        len: usize,
695    },
696    /// The length of the description of a label is too long.
697    LabelDescriptionTooLong {
698        /// Length of the provided description.
699        len: usize,
700    },
701    /// Maximum number of files that can be uploaded is larger than
702    /// [the maximum][`FILE_UPLOAD_MAXIMUM_VALUES_LIMIT`].
703    FileUploadMaximumValuesCount {
704        /// Provided value for [`FileUpload::max_values`].
705        ///
706        /// [`FileUpload::max_values`]: twilight_model::channel::message::component::FileUpload::max_values
707        count: u8,
708    },
709    /// Minimum number of files that must be uploaded is larger than
710    /// [the maximum][`FILE_UPLOAD_MINIMUM_VALUES_LIMIT`].
711    FileUploadMinimumValuesCount {
712        /// Value provided for [`FileUpload::min_values`].
713        ///
714        /// [`FileUpload::min_values`]: twilight_model::channel::message::component::FileUpload::min_values
715        count: u8,
716    },
717}
718
719/// Ensure that a top-level request component is correct in V1.
720///
721/// Intended to ensure that a fully formed top-level component for requests
722/// is an action row.
723///
724/// Refer to other validators like [`button`] if you need to validate other
725/// components.
726///
727/// # Errors
728///
729/// Returns an error of type [`InvalidRootComponent`] if the component is not an
730/// [`ActionRow`].
731///
732/// Refer to [`action_row`] for potential errors when validating an action row
733/// component.
734///
735/// Returns an error if any components V2 components are used.
736///
737/// [`InvalidRootComponent`]: ComponentValidationErrorType::InvalidRootComponent
738pub fn component_v1(component: &Component) -> Result<(), ComponentValidationError> {
739    match component {
740        Component::ActionRow(action_row) => self::action_row(action_row, false)?,
741        other => {
742            return Err(ComponentValidationError {
743                kind: ComponentValidationErrorType::InvalidRootComponent { kind: other.kind() },
744            });
745        }
746    }
747
748    Ok(())
749}
750
751/// Ensure that a top-level request component is correct in V1.
752///
753/// Intended to ensure that a fully formed top-level component for requests
754/// is an action row.
755///
756/// Refer to other validators like [`button`] if you need to validate other
757/// components.
758///
759/// # Errors
760///
761/// Returns an error of type [`InvalidRootComponent`] if the component is not an
762/// [`ActionRow`].
763///
764/// Refer to [`action_row`] for potential errors when validating an action row
765/// component.
766///
767/// [`InvalidRootComponent`]: ComponentValidationErrorType::InvalidRootComponent
768#[deprecated(note = "Use component_v1 for old components and component_v2 for new ones")]
769pub fn component(component: &Component) -> Result<(), ComponentValidationError> {
770    component_v1(component)
771}
772
773/// Ensure that an action row is correct.
774///
775/// # Errors
776///
777/// Returns an error of type [`ActionRowComponentCount`] if the action row has
778/// too many components in it.
779///
780/// Returns an error of type [`DisallowedChildren`] if the action row contains V2 components
781/// that are disallowed in action rows and `is_v2` is `true`.
782///
783/// Returns an error of type [`DisallowedV2Components`] if the action row contains V2 components
784/// and `is_v2` is `false`.
785///
786/// Returns an error of type [`InvalidChildComponent`] if the provided nested
787/// component is an [`ActionRow`] or a [`Label`]. Action rows cannot contain other top-level
788/// components.
789///
790/// Refer to [`button`] for potential errors when validating a button in the
791/// action row.
792///
793/// Refer to [`select_menu`] for potential errors when validating a select menu
794/// in the action row.
795///
796/// Refer to [`text_input`] for potential errors when validating a text input in
797/// the action row.
798///
799/// [`ActionRowComponentCount`]: ComponentValidationErrorType::ActionRowComponentCount
800/// [`DisallowedChildren`]: ComponentValidationErrorType::DisallowedChildren
801/// [`DisallowedV2Components`]: ComponentValidationErrorType::DisallowedV2Components
802/// [`InvalidChildComponent`]: ComponentValidationErrorType::InvalidChildComponent
803/// [`Label`]: twilight_model::channel::message::component::Label
804pub fn action_row(action_row: &ActionRow, is_v2: bool) -> Result<(), ComponentValidationError> {
805    self::component_action_row_components(&action_row.components)?;
806
807    for component in &action_row.components {
808        match component {
809            Component::ActionRow(_) | Component::Label(_) => {
810                return Err(ComponentValidationError {
811                    kind: ComponentValidationErrorType::InvalidChildComponent {
812                        kind: component.kind(),
813                    },
814                });
815            }
816            Component::Button(button) => self::button(button)?,
817            Component::SelectMenu(select_menu) => self::select_menu(select_menu, true)?,
818            Component::TextInput(text_input) => self::text_input(text_input, true)?,
819            Component::Unknown(unknown) => {
820                return Err(ComponentValidationError {
821                    kind: ComponentValidationErrorType::InvalidChildComponent {
822                        kind: ComponentType::Unknown(*unknown),
823                    },
824                });
825            }
826
827            Component::TextDisplay(_)
828            | Component::MediaGallery(_)
829            | Component::Separator(_)
830            | Component::File(_)
831            | Component::Section(_)
832            | Component::Container(_)
833            | Component::Thumbnail(_)
834            | Component::Checkbox(_)
835            | Component::CheckboxGroup(_)
836            | Component::FileUpload(_) => {
837                return Err(ComponentValidationError {
838                    kind: if is_v2 {
839                        ComponentValidationErrorType::DisallowedChildren
840                    } else {
841                        ComponentValidationErrorType::DisallowedV2Components
842                    },
843                });
844            }
845        }
846    }
847
848    Ok(())
849}
850
851/// Ensure that a button is correct.
852///
853/// # Errors
854///
855/// Returns an error of type [`ButtonConflict`] if both a custom ID and URL are
856/// specified.
857///
858/// Returns an error of type
859/// [`ButtonStyle`][`ComponentValidationErrorType::ButtonStyle`] if
860/// [`ButtonStyle::Link`] is provided and a URL is provided, or if the style is
861/// not [`ButtonStyle::Link`] and a custom ID is not provided.
862///
863/// Returns an error of type [`ComponentCustomIdLength`] if the provided custom
864/// ID is too long.
865///
866/// Returns an error of type [`ComponentLabelLength`] if the provided button
867/// label is too long.
868///
869/// [`ButtonConflict`]: ComponentValidationErrorType::ButtonConflict
870/// [`ComponentCustomIdLength`]: ComponentValidationErrorType::ComponentCustomIdLength
871/// [`ComponentLabelLength`]: ComponentValidationErrorType::ComponentLabelLength
872pub fn button(button: &Button) -> Result<(), ComponentValidationError> {
873    let has_custom_id = button.custom_id.is_some();
874    let has_emoji = button.emoji.is_some();
875    let has_label = button.label.is_some();
876    let has_sku_id = button.sku_id.is_some();
877    let has_url = button.url.is_some();
878
879    // First check if a custom ID and URL are both set. If so this
880    // results in a conflict, as no valid button may have both set.
881    if has_custom_id && has_url {
882        return Err(ComponentValidationError {
883            kind: ComponentValidationErrorType::ButtonConflict,
884        });
885    }
886
887    // Next, we check if the button is a premium and a SKU ID is not set.
888    //
889    // Also, we check if the button is not a premium and custom ID, label,
890    // URL or emoji is set.
891    let is_premium = button.style == ButtonStyle::Premium;
892    if is_premium && (has_custom_id || has_url || has_label || has_emoji || !has_sku_id) {
893        return Err(ComponentValidationError {
894            kind: ComponentValidationErrorType::ButtonStyle {
895                style: button.style,
896            },
897        });
898    }
899
900    // Then, we check if the button is a link and a URL is not set.
901    //
902    // Lastly, we check if the button is not a link and a custom ID is
903    // not set.
904    let is_link = button.style == ButtonStyle::Link;
905
906    if (is_link && !has_url) || (!is_link && !has_custom_id) {
907        return Err(ComponentValidationError {
908            kind: ComponentValidationErrorType::ButtonStyle {
909                style: button.style,
910            },
911        });
912    }
913
914    if let Some(custom_id) = button.custom_id.as_ref() {
915        self::component_custom_id(custom_id)?;
916    }
917
918    if let Some(label) = button.label.as_ref() {
919        self::component_button_label(label)?;
920    }
921
922    Ok(())
923}
924
925/// Ensure that a select menu is correct.
926///
927/// The `disabled_allowed` parameter determines whether the `disabled` field is allowed in this
928/// context.
929/// It should be disallowed if this component is placed in a modal.
930///
931/// # Errors
932///
933/// Returns an error of type [`SelectDisallowedDisabled`] if `disabled_allowed` is `false` and
934/// the provided select menu is disabled.
935///
936/// Returns an error of type [`ComponentCustomIdLength`] if the provided custom
937/// ID is too long.
938///
939/// Returns an error of type [`ComponentLabelLength`] if the provided button
940/// label is too long.
941///
942/// Returns an error of type [`SelectMaximumValuesCount`] if the provided number
943/// of select menu values that can be chosen is smaller than the minimum or
944/// larger than the maximum.
945///
946/// Returns an error of type [`SelectMinimumValuesCount`] if the provided number
947/// of select menu values that must be chosen is larger than the maximum.
948///
949/// Returns an error of type [`SelectOptionDescriptionLength`] if a provided
950/// select option description is too long.
951///
952/// Returns an error of type [`SelectOptionLabelLength`] if a provided select
953/// option label is too long.
954///
955/// Returns an error of type [`SelectOptionValueLength`] error type if
956/// a provided select option value is too long.
957///
958/// Returns an error of type [`SelectPlaceholderLength`] if a provided select
959/// placeholder is too long.
960///
961/// Returns an error of type [`SelectUnsupportedDefaultValues`] if the select menu's type doesn't
962/// support the `default_values` field.
963///
964/// Returns an error of type [`SelectNotEnoughDefaultValues`] if the select menu specifies fewer
965/// default values than its minimum values property.
966///
967/// Returns an error of type [`SelectTooManyDefaultValues`] if the select menu specifies more
968/// default values than its maximum values property.
969///
970/// [`SelectDisallowedDisabled`]: ComponentValidationErrorType::SelectDisallowedDisabled
971/// [`ComponentCustomIdLength`]: ComponentValidationErrorType::ComponentCustomIdLength
972/// [`ComponentLabelLength`]: ComponentValidationErrorType::ComponentLabelLength
973/// [`SelectMaximumValuesCount`]: ComponentValidationErrorType::SelectMaximumValuesCount
974/// [`SelectMinimumValuesCount`]: ComponentValidationErrorType::SelectMinimumValuesCount
975/// [`SelectOptionDescriptionLength`]: ComponentValidationErrorType::SelectOptionDescriptionLength
976/// [`SelectOptionLabelLength`]: ComponentValidationErrorType::SelectOptionLabelLength
977/// [`SelectOptionValueLength`]: ComponentValidationErrorType::SelectOptionValueLength
978/// [`SelectPlaceholderLength`]: ComponentValidationErrorType::SelectPlaceholderLength
979/// [`SelectUnsupportedDefaultValues`]: ComponentValidationErrorType::SelectUnsupportedDefaultValues
980/// [`SelectNotEnoughDefaultValues`]: ComponentValidationErrorType::SelectNotEnoughDefaultValues
981/// [`SelectTooManyDefaultValues`]: ComponentValidationErrorType::SelectTooManyDefaultValues
982pub fn select_menu(
983    select_menu: &SelectMenu,
984    disabled_allowed: bool,
985) -> Result<(), ComponentValidationError> {
986    self::component_custom_id(&select_menu.custom_id)?;
987
988    if !disabled_allowed && select_menu.disabled {
989        return Err(ComponentValidationError {
990            kind: ComponentValidationErrorType::SelectDisallowedDisabled,
991        });
992    }
993
994    // There aren't any requirements for channel_types that we could validate here
995    if let SelectMenuType::Text = &select_menu.kind {
996        let options = select_menu
997            .options
998            .as_ref()
999            .ok_or(ComponentValidationError {
1000                kind: ComponentValidationErrorType::SelectOptionsMissing,
1001            })?;
1002        for option in options {
1003            component_select_option_label(&option.label)?;
1004            component_select_option_value(&option.value)?;
1005
1006            if let Some(description) = option.description.as_ref() {
1007                component_option_description(description)?;
1008            }
1009        }
1010        component_select_options(options)?;
1011    }
1012
1013    if let Some(placeholder) = select_menu.placeholder.as_ref() {
1014        self::component_select_placeholder(placeholder)?;
1015    }
1016
1017    if let Some(max_values) = select_menu.max_values {
1018        self::component_select_max_values(usize::from(max_values))?;
1019    }
1020
1021    if let Some(min_values) = select_menu.min_values {
1022        self::component_select_min_values(usize::from(min_values))?;
1023    }
1024
1025    if let Some(default_values) = select_menu.default_values.as_ref() {
1026        component_select_default_values_supported(select_menu.kind)?;
1027        component_select_default_values_count(
1028            select_menu.min_values,
1029            select_menu.max_values,
1030            default_values.len(),
1031        )?;
1032    }
1033
1034    Ok(())
1035}
1036
1037/// Ensure that a text input is correct.
1038///
1039/// The `label_allowed` parameter determines whether the deprecated [`TextInput::label`] field is
1040/// allowed in this context.
1041/// It should be disallowed if the text input component is place within a label component.
1042///
1043/// # Errors
1044///
1045/// Returns an error of type [`TextInputDisallowedLabel`] if `label_allowed` is `false` and
1046/// the `label` field of this text input is set.
1047///
1048/// Returns an error of type [`ComponentCustomIdLength`] if the provided custom
1049/// ID is too long.
1050///
1051/// Returns an error of type [`ComponentLabelLength`] if the provided button
1052/// label is too long.
1053///
1054/// Returns an error of type [`TextInputMaxLength`] if the length is invalid.
1055///
1056/// Returns an error of type [`TextInputMinLength`] if the length is invalid.
1057///
1058/// Returns an error of type [`TextInputPlaceholderLength`] if the provided
1059/// placeholder is too long.
1060///
1061/// Returns an error of type [`TextInputValueLength`] if the length is invalid.
1062///
1063/// [`TextInputDisallowedLabel`]: ComponentValidationErrorType::TextInputDisallowedLabel
1064/// [`ComponentCustomIdLength`]: ComponentValidationErrorType::ComponentCustomIdLength
1065/// [`ComponentLabelLength`]: ComponentValidationErrorType::ComponentLabelLength
1066/// [`TextInputMaxLength`]: ComponentValidationErrorType::TextInputMaxLength
1067/// [`TextInputMinLength`]: ComponentValidationErrorType::TextInputMinLength
1068/// [`TextInputPlaceholderLength`]: ComponentValidationErrorType::TextInputPlaceholderLength
1069/// [`TextInputValueLength`]: ComponentValidationErrorType::TextInputValueLength
1070pub fn text_input(
1071    text_input: &TextInput,
1072    label_allowed: bool,
1073) -> Result<(), ComponentValidationError> {
1074    self::component_custom_id(&text_input.custom_id)?;
1075
1076    #[allow(deprecated)]
1077    if let Some(label) = &text_input.label {
1078        if !label_allowed {
1079            return Err(ComponentValidationError {
1080                kind: ComponentValidationErrorType::TextInputDisallowedLabel,
1081            });
1082        }
1083
1084        self::component_text_input_label(label)?;
1085    }
1086
1087    if let Some(max_length) = text_input.max_length {
1088        self::component_text_input_max(max_length)?;
1089    }
1090
1091    if let Some(min_length) = text_input.min_length {
1092        self::component_text_input_min(min_length)?;
1093    }
1094
1095    if let Some(placeholder) = text_input.placeholder.as_ref() {
1096        self::component_text_input_placeholder(placeholder)?;
1097    }
1098
1099    if let Some(value) = text_input.value.as_ref() {
1100        self::component_text_input_value(value)?;
1101    }
1102
1103    Ok(())
1104}
1105
1106/// Validate that an [`ActionRow`] does not contain too many components.
1107///
1108/// [`ActionRow`]s may only have so many components within it, defined by
1109/// [`ACTION_ROW_COMPONENT_COUNT`].
1110///
1111/// # Errors
1112///
1113/// Returns an error of type [`ActionRowComponentCount`] if the provided list of
1114/// components is too many for an [`ActionRow`].
1115///
1116/// [`ActionRowComponentCount`]: ComponentValidationErrorType::ActionRowComponentCount
1117/// [`ActionRow`]: twilight_model::application::component::ActionRow
1118const fn component_action_row_components(
1119    components: &[Component],
1120) -> Result<(), ComponentValidationError> {
1121    let count = components.len();
1122
1123    if count > COMPONENT_COUNT {
1124        return Err(ComponentValidationError {
1125            kind: ComponentValidationErrorType::ActionRowComponentCount { count },
1126        });
1127    }
1128
1129    Ok(())
1130}
1131
1132/// Validate that a [`Component`]'s label is not too long.
1133///
1134/// # Errors
1135///
1136/// Returns an error of type [`ComponentLabelLength`] if the provided component
1137/// label is too long.
1138///
1139/// [`ComponentLabelLength`]: ComponentValidationErrorType::ComponentLabelLength
1140fn component_button_label(label: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1141    let chars = label.as_ref().chars().count();
1142
1143    if chars > COMPONENT_BUTTON_LABEL_LENGTH {
1144        return Err(ComponentValidationError {
1145            kind: ComponentValidationErrorType::ComponentLabelLength { chars },
1146        });
1147    }
1148
1149    Ok(())
1150}
1151
1152/// Validate that a custom ID is not too long.
1153///
1154/// # Errors
1155///
1156/// Returns an error of type [`ComponentCustomIdLength`] if the provided custom
1157/// ID is too long.
1158///
1159/// [`ComponentCustomIdLength`]: ComponentValidationErrorType::ComponentCustomIdLength
1160fn component_custom_id(custom_id: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1161    let chars = custom_id.as_ref().chars().count();
1162
1163    if chars > COMPONENT_CUSTOM_ID_LENGTH {
1164        return Err(ComponentValidationError {
1165            kind: ComponentValidationErrorType::ComponentCustomIdLength { chars },
1166        });
1167    }
1168
1169    Ok(())
1170}
1171
1172/// Validate a [`SelectMenuOption::description`]'s length.
1173///
1174/// # Errors
1175///
1176/// Returns an error of type [`SelectOptionDescriptionLength`] if the provided
1177/// select option description is too long.
1178///
1179/// [`SelectMenuOption::description`]: twilight_model::application::component::select_menu::SelectMenuOption::description
1180/// [`SelectOptionDescriptionLength`]: ComponentValidationErrorType::SelectOptionDescriptionLength
1181fn component_option_description(
1182    description: impl AsRef<str>,
1183) -> Result<(), ComponentValidationError> {
1184    let chars = description.as_ref().chars().count();
1185
1186    if chars > SELECT_OPTION_DESCRIPTION_LENGTH {
1187        return Err(ComponentValidationError {
1188            kind: ComponentValidationErrorType::SelectOptionDescriptionLength { chars },
1189        });
1190    }
1191
1192    Ok(())
1193}
1194
1195/// Validate a [`SelectMenuType`] supports the `default_values` field.
1196///
1197/// # Errors
1198///
1199/// Returns an error of type [`SelectUnsupportedDefaultValues`] if the provided component type
1200/// doesn't support the `default_values` field.
1201const fn component_select_default_values_supported(
1202    menu_type: SelectMenuType,
1203) -> Result<(), ComponentValidationError> {
1204    if !matches!(
1205        menu_type,
1206        SelectMenuType::User
1207            | SelectMenuType::Role
1208            | SelectMenuType::Mentionable
1209            | SelectMenuType::Channel
1210    ) {
1211        return Err(ComponentValidationError {
1212            kind: ComponentValidationErrorType::SelectUnsupportedDefaultValues { kind: menu_type },
1213        });
1214    }
1215
1216    Ok(())
1217}
1218
1219/// Validate a [`SelectMenu`]'s `default_values` field has the correct number of values.
1220///
1221/// # Errors
1222///
1223/// Returns an error of the type [`SelectTooManyDefaultValues`] if the provided list of default
1224/// values exceeds the provided `max_values` (if present).
1225///
1226/// Alternatively, this returns an error of the type [`SelectNotEnoughDefaultValues`] if the
1227/// provided list of default values doesn't meet the provided `min_values` requirement (if present).
1228const fn component_select_default_values_count(
1229    min_values: Option<u8>,
1230    max_values: Option<u8>,
1231    default_values: usize,
1232) -> Result<(), ComponentValidationError> {
1233    if let Some(min) = min_values {
1234        let min = min as usize;
1235        if default_values < min {
1236            return Err(ComponentValidationError {
1237                kind: ComponentValidationErrorType::SelectNotEnoughDefaultValues {
1238                    provided: default_values,
1239                    min,
1240                },
1241            });
1242        }
1243    }
1244    if let Some(max) = max_values {
1245        let max = max as usize;
1246        if default_values > max {
1247            return Err(ComponentValidationError {
1248                kind: ComponentValidationErrorType::SelectTooManyDefaultValues {
1249                    provided: default_values,
1250                    max,
1251                },
1252            });
1253        }
1254    }
1255
1256    Ok(())
1257}
1258
1259/// Validate a [`SelectMenu::max_values`] amount.
1260///
1261/// # Errors
1262///
1263/// Returns an error of type [`SelectMaximumValuesCount`] if the provided number
1264/// of values that can be chosen is smaller than
1265/// [the minimum][`SELECT_MAXIMUM_VALUES_REQUIREMENT`] or larger than
1266/// [the maximum][`SELECT_MAXIMUM_VALUES_LIMIT`].
1267///
1268/// [`SelectMenu::max_values`]: twilight_model::application::component::select_menu::SelectMenu::max_values
1269/// [`SelectMaximumValuesCount`]: ComponentValidationErrorType::SelectMaximumValuesCount
1270const fn component_select_max_values(count: usize) -> Result<(), ComponentValidationError> {
1271    if count > SELECT_MAXIMUM_VALUES_LIMIT {
1272        return Err(ComponentValidationError {
1273            kind: ComponentValidationErrorType::SelectMaximumValuesCount { count },
1274        });
1275    }
1276
1277    if count < SELECT_MAXIMUM_VALUES_REQUIREMENT {
1278        return Err(ComponentValidationError {
1279            kind: ComponentValidationErrorType::SelectMaximumValuesCount { count },
1280        });
1281    }
1282
1283    Ok(())
1284}
1285
1286/// Validate a [`SelectMenu::min_values`] amount.
1287///
1288/// # Errors
1289///
1290/// Returns an error of type [`SelectMinimumValuesCount`] if the provided number
1291/// of values that must be chosen is larger than
1292/// [the maximum][`SELECT_MINIMUM_VALUES_LIMIT`].
1293///
1294/// [`SelectMenu::min_values`]: twilight_model::application::component::select_menu::SelectMenu::min_values
1295/// [`SelectMinimumValuesCount`]: ComponentValidationErrorType::SelectMinimumValuesCount
1296const fn component_select_min_values(count: usize) -> Result<(), ComponentValidationError> {
1297    if count > SELECT_MINIMUM_VALUES_LIMIT {
1298        return Err(ComponentValidationError {
1299            kind: ComponentValidationErrorType::SelectMinimumValuesCount { count },
1300        });
1301    }
1302
1303    Ok(())
1304}
1305
1306/// Validate a [`SelectMenuOption::label`]'s length.
1307///
1308/// # Errors
1309///
1310/// Returns an error of type [`SelectOptionLabelLength`] if the provided select
1311/// option label is too long.
1312///
1313/// [`SelectMenuOption::label`]: twilight_model::application::component::select_menu::SelectMenuOption::label
1314/// [`SelectOptionLabelLength`]: ComponentValidationErrorType::SelectOptionLabelLength
1315fn component_select_option_label(label: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1316    let chars = label.as_ref().chars().count();
1317
1318    if chars > SELECT_OPTION_LABEL_LENGTH {
1319        return Err(ComponentValidationError {
1320            kind: ComponentValidationErrorType::SelectOptionLabelLength { chars },
1321        });
1322    }
1323
1324    Ok(())
1325}
1326
1327/// Validate a [`SelectMenuOption::value`]'s length.
1328///
1329/// # Errors
1330///
1331/// Returns an error of type [`SelectOptionValueLength`] if the provided select
1332/// option value is too long.
1333///
1334/// [`SelectMenuOption::value`]: twilight_model::application::component::select_menu::SelectMenuOption::value
1335/// [`SelectOptionValueLength`]: ComponentValidationErrorType::SelectOptionValueLength
1336fn component_select_option_value(value: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1337    let chars = value.as_ref().chars().count();
1338
1339    if chars > SELECT_OPTION_VALUE_LENGTH {
1340        return Err(ComponentValidationError {
1341            kind: ComponentValidationErrorType::SelectOptionValueLength { chars },
1342        });
1343    }
1344
1345    Ok(())
1346}
1347
1348/// Validate a [`SelectMenu`]s number of [`options`].
1349///
1350/// [`Component::SelectMenu`]s may only have so many options within it, defined
1351/// by [`SELECT_OPTION_COUNT`].
1352///
1353/// # Errors
1354///
1355/// Returns an error of type [`SelectOptionCount`] if the provided list of
1356/// [`SelectMenuOption`]s is too many for a [`SelectMenu`].
1357///
1358/// [`SelectMenu::options`]: twilight_model::application::component::select_menu::SelectMenu::options
1359/// [`SelectMenuOption`]: twilight_model::application::component::select_menu::SelectMenuOption
1360/// [`SelectMenu`]: twilight_model::application::component::select_menu::SelectMenu
1361/// [`SelectOptionCount`]: ComponentValidationErrorType::SelectOptionCount
1362const fn component_select_options(
1363    options: &[SelectMenuOption],
1364) -> Result<(), ComponentValidationError> {
1365    let count = options.len();
1366
1367    if count > SELECT_OPTION_COUNT {
1368        return Err(ComponentValidationError {
1369            kind: ComponentValidationErrorType::SelectOptionCount { count },
1370        });
1371    }
1372
1373    Ok(())
1374}
1375
1376/// Validate a [`SelectMenu::placeholder`]'s length.
1377///
1378/// # Errors
1379///
1380/// Returns an error of type [`SelectPlaceholderLength`] if the provided select
1381/// placeholder is too long.
1382///
1383/// [`SelectMenu::placeholder`]: twilight_model::application::component::select_menu::SelectMenu::placeholder
1384/// [`SelectPlaceholderLength`]: ComponentValidationErrorType::SelectPlaceHolderLength
1385fn component_select_placeholder(
1386    placeholder: impl AsRef<str>,
1387) -> Result<(), ComponentValidationError> {
1388    let chars = placeholder.as_ref().chars().count();
1389
1390    if chars > SELECT_PLACEHOLDER_LENGTH {
1391        return Err(ComponentValidationError {
1392            kind: ComponentValidationErrorType::SelectPlaceholderLength { chars },
1393        });
1394    }
1395
1396    Ok(())
1397}
1398
1399/// Ensure a [`TextInput::label`]'s length is correct.
1400///
1401/// The length must be at most [`TEXT_INPUT_LABEL_MAX`].
1402///
1403/// # Errors
1404///
1405/// Returns an error of type [`TextInputLabelLength`] if the provided
1406/// label is too long.
1407///
1408/// [`TextInput::label`]: twilight_model::application::component::text_input::TextInput::label
1409/// [`TextInputLabelLength`]: ComponentValidationErrorType::TextInputLabelLength
1410fn component_text_input_label(label: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1411    let len = label.as_ref().len();
1412
1413    if (TEXT_INPUT_LABEL_MIN..=TEXT_INPUT_LABEL_MAX).contains(&len) {
1414        Ok(())
1415    } else {
1416        Err(ComponentValidationError {
1417            kind: ComponentValidationErrorType::TextInputLabelLength { len },
1418        })
1419    }
1420}
1421
1422/// Ensure a [`TextInput::max_length`]'s value is correct.
1423///
1424/// # Errors
1425///
1426/// Returns an error of type [`TextInputMaxLength`] if the length is invalid.
1427///
1428/// [`TextInput::max_length`]: twilight_model::application::component::text_input::TextInput::max_length
1429/// [`TextInputMaxLength`]: ComponentValidationErrorType::TextInputMaxLength
1430const fn component_text_input_max(len: u16) -> Result<(), ComponentValidationError> {
1431    let len = len as usize;
1432
1433    if len >= TEXT_INPUT_LENGTH_MIN && len <= TEXT_INPUT_LENGTH_MAX {
1434        Ok(())
1435    } else {
1436        Err(ComponentValidationError {
1437            kind: ComponentValidationErrorType::TextInputMaxLength { len },
1438        })
1439    }
1440}
1441
1442/// Ensure a [`TextInput::min_length`]'s value is correct.
1443///
1444/// # Errors
1445///
1446/// Returns an error of type [`TextInputMinLength`] if the length is invalid.
1447///
1448/// [`TextInput::min_length`]: twilight_model::application::component::text_input::TextInput::min_length
1449/// [`TextInputMinLength`]: ComponentValidationErrorType::TextInputMinLength
1450const fn component_text_input_min(len: u16) -> Result<(), ComponentValidationError> {
1451    let len = len as usize;
1452
1453    if len <= TEXT_INPUT_LENGTH_MAX {
1454        Ok(())
1455    } else {
1456        Err(ComponentValidationError {
1457            kind: ComponentValidationErrorType::TextInputMinLength { len },
1458        })
1459    }
1460}
1461
1462/// Ensure a [`TextInput::placeholder`]'s length is correct.
1463///
1464/// The length must be at most [`TEXT_INPUT_PLACEHOLDER_MAX`].
1465///
1466/// # Errors
1467///
1468/// Returns an error of type [`TextInputPlaceholderLength`] if the provided
1469/// placeholder is too long.
1470///
1471/// [`TextInput::placeholder`]: twilight_model::application::component::text_input::TextInput::placeholder
1472/// [`TextInputPlaceholderLength`]: ComponentValidationErrorType::TextInputPlaceholderLength
1473fn component_text_input_placeholder(
1474    placeholder: impl AsRef<str>,
1475) -> Result<(), ComponentValidationError> {
1476    let chars = placeholder.as_ref().chars().count();
1477
1478    if chars <= TEXT_INPUT_PLACEHOLDER_MAX {
1479        Ok(())
1480    } else {
1481        Err(ComponentValidationError {
1482            kind: ComponentValidationErrorType::TextInputPlaceholderLength { chars },
1483        })
1484    }
1485}
1486
1487/// Ensure a [`TextInput::value`]'s length is correct.
1488///
1489/// # Errors
1490///
1491/// Returns an error of type [`TextInputValueLength`] if the length is invalid.
1492///
1493/// [`TextInput::value_length`]: twilight_model::application::component::text_input::TextInput::value
1494/// [`TextInputValueLength`]: ComponentValidationErrorType::TextInputValueLength
1495fn component_text_input_value(value: impl AsRef<str>) -> Result<(), ComponentValidationError> {
1496    let chars = value.as_ref().chars().count();
1497
1498    if chars <= TEXT_INPUT_LENGTH_MAX {
1499        Ok(())
1500    } else {
1501        Err(ComponentValidationError {
1502            kind: ComponentValidationErrorType::TextInputValueLength { chars },
1503        })
1504    }
1505}
1506
1507#[allow(clippy::non_ascii_literal)]
1508#[cfg(test)]
1509mod tests {
1510    use super::*;
1511    use static_assertions::{assert_fields, assert_impl_all};
1512    use twilight_model::channel::message::EmojiReactionType;
1513
1514    assert_fields!(ComponentValidationErrorType::ActionRowComponentCount: count);
1515    assert_fields!(ComponentValidationErrorType::ComponentCount: count);
1516    assert_fields!(ComponentValidationErrorType::ComponentCustomIdLength: chars);
1517    assert_fields!(ComponentValidationErrorType::ComponentLabelLength: chars);
1518    assert_fields!(ComponentValidationErrorType::InvalidChildComponent: kind);
1519    assert_fields!(ComponentValidationErrorType::InvalidRootComponent: kind);
1520    assert_fields!(ComponentValidationErrorType::SelectMaximumValuesCount: count);
1521    assert_fields!(ComponentValidationErrorType::SelectMinimumValuesCount: count);
1522    assert_fields!(ComponentValidationErrorType::SelectOptionDescriptionLength: chars);
1523    assert_fields!(ComponentValidationErrorType::SelectOptionLabelLength: chars);
1524    assert_fields!(ComponentValidationErrorType::SelectOptionValueLength: chars);
1525    assert_fields!(ComponentValidationErrorType::SelectPlaceholderLength: chars);
1526    assert_impl_all!(ComponentValidationErrorType: Debug, Send, Sync);
1527    assert_impl_all!(ComponentValidationError: Debug, Send, Sync);
1528
1529    // All styles of buttons.
1530    const ALL_BUTTON_STYLES: &[ButtonStyle] = &[
1531        ButtonStyle::Primary,
1532        ButtonStyle::Secondary,
1533        ButtonStyle::Success,
1534        ButtonStyle::Danger,
1535        ButtonStyle::Link,
1536        ButtonStyle::Premium,
1537    ];
1538
1539    #[test]
1540    fn component_action_row() {
1541        let button = Button {
1542            custom_id: None,
1543            disabled: false,
1544            emoji: Some(EmojiReactionType::Unicode {
1545                name: "📚".into()
1546            }),
1547            label: Some("Read".into()),
1548            style: ButtonStyle::Link,
1549            url: Some("https://abebooks.com".into()),
1550            sku_id: None,
1551            id: None,
1552        };
1553
1554        let select_menu = SelectMenu {
1555            channel_types: None,
1556            custom_id: "custom id 2".into(),
1557            disabled: false,
1558            default_values: None,
1559            kind: SelectMenuType::Text,
1560            max_values: Some(2),
1561            min_values: Some(1),
1562            options: Some(Vec::from([SelectMenuOption {
1563                default: true,
1564                description: Some("Book 1 of the Expanse".into()),
1565                emoji: None,
1566                label: "Leviathan Wakes".into(),
1567                value: "9780316129084".into(),
1568            }])),
1569            placeholder: Some("Choose a book".into()),
1570            id: None,
1571            required: None,
1572        };
1573
1574        let action_row = ActionRow {
1575            components: Vec::from([
1576                Component::SelectMenu(select_menu.clone()),
1577                Component::Button(button),
1578            ]),
1579            id: None,
1580        };
1581
1582        assert!(component_v1(&Component::ActionRow(action_row.clone())).is_ok());
1583
1584        assert!(component_v1(&Component::SelectMenu(select_menu.clone())).is_err());
1585
1586        assert!(super::action_row(&action_row, false).is_ok());
1587
1588        let invalid_action_row = Component::ActionRow(ActionRow {
1589            components: Vec::from([
1590                Component::SelectMenu(select_menu.clone()),
1591                Component::SelectMenu(select_menu.clone()),
1592                Component::SelectMenu(select_menu.clone()),
1593                Component::SelectMenu(select_menu.clone()),
1594                Component::SelectMenu(select_menu.clone()),
1595                Component::SelectMenu(select_menu),
1596            ]),
1597            id: None,
1598        });
1599
1600        assert!(component_v1(&invalid_action_row).is_err());
1601    }
1602
1603    // Test that a button with both a custom ID and URL results in a
1604    // [`ComponentValidationErrorType::ButtonConflict`] error type.
1605    #[test]
1606    fn button_conflict() {
1607        let button = Button {
1608            custom_id: Some("a".to_owned()),
1609            disabled: false,
1610            emoji: None,
1611            label: None,
1612            style: ButtonStyle::Primary,
1613            url: Some("https://twilight.rs".to_owned()),
1614            sku_id: None,
1615            id: None,
1616        };
1617
1618        assert!(matches!(
1619            super::button(&button),
1620            Err(ComponentValidationError {
1621                kind: ComponentValidationErrorType::ButtonConflict,
1622            }),
1623        ));
1624    }
1625
1626    // Test that all button styles with no custom ID or URL results in a
1627    // [`ComponentValidationErrorType::ButtonStyle`] error type.
1628    #[test]
1629    fn button_style() {
1630        for style in ALL_BUTTON_STYLES {
1631            let button = Button {
1632                custom_id: None,
1633                disabled: false,
1634                emoji: None,
1635                label: Some("some label".to_owned()),
1636                style: *style,
1637                url: None,
1638                sku_id: None,
1639                id: None,
1640            };
1641
1642            assert!(matches!(
1643                super::button(&button),
1644                Err(ComponentValidationError {
1645                    kind: ComponentValidationErrorType::ButtonStyle {
1646                        style: error_style,
1647                    }
1648                })
1649                if error_style == *style
1650            ));
1651        }
1652    }
1653
1654    #[test]
1655    fn component_label() {
1656        assert!(component_button_label("").is_ok());
1657        assert!(component_button_label("a").is_ok());
1658        assert!(component_button_label("a".repeat(80)).is_ok());
1659
1660        assert!(component_button_label("a".repeat(81)).is_err());
1661    }
1662
1663    #[test]
1664    fn component_custom_id_length() {
1665        assert!(component_custom_id("").is_ok());
1666        assert!(component_custom_id("a").is_ok());
1667        assert!(component_custom_id("a".repeat(100)).is_ok());
1668
1669        assert!(component_custom_id("a".repeat(101)).is_err());
1670    }
1671
1672    #[test]
1673    fn component_option_description_length() {
1674        assert!(component_option_description("").is_ok());
1675        assert!(component_option_description("a").is_ok());
1676        assert!(component_option_description("a".repeat(100)).is_ok());
1677
1678        assert!(component_option_description("a".repeat(101)).is_err());
1679    }
1680
1681    #[test]
1682    fn component_select_default_values_support() {
1683        assert!(component_select_default_values_supported(SelectMenuType::User).is_ok());
1684        assert!(component_select_default_values_supported(SelectMenuType::Role).is_ok());
1685        assert!(component_select_default_values_supported(SelectMenuType::Mentionable).is_ok());
1686        assert!(component_select_default_values_supported(SelectMenuType::Channel).is_ok());
1687
1688        assert!(component_select_default_values_supported(SelectMenuType::Text).is_err());
1689    }
1690
1691    #[test]
1692    fn component_select_num_default_values() {
1693        assert!(component_select_default_values_count(None, None, 0).is_ok());
1694        assert!(component_select_default_values_count(None, None, 1).is_ok());
1695        assert!(component_select_default_values_count(Some(1), None, 5).is_ok());
1696        assert!(component_select_default_values_count(Some(5), None, 5).is_ok());
1697        assert!(component_select_default_values_count(None, Some(5), 5).is_ok());
1698        assert!(component_select_default_values_count(None, Some(10), 5).is_ok());
1699        assert!(component_select_default_values_count(Some(5), Some(5), 5).is_ok());
1700        assert!(component_select_default_values_count(Some(1), Some(10), 5).is_ok());
1701
1702        assert!(component_select_default_values_count(Some(2), None, 1).is_err());
1703        assert!(component_select_default_values_count(None, Some(1), 2).is_err());
1704        assert!(component_select_default_values_count(Some(1), Some(1), 2).is_err());
1705        assert!(component_select_default_values_count(Some(2), Some(2), 1).is_err());
1706    }
1707
1708    #[test]
1709    fn component_select_max_values_count() {
1710        assert!(component_select_max_values(1).is_ok());
1711        assert!(component_select_max_values(25).is_ok());
1712
1713        assert!(component_select_max_values(0).is_err());
1714        assert!(component_select_max_values(26).is_err());
1715    }
1716
1717    #[test]
1718    fn component_select_min_values_count() {
1719        assert!(component_select_min_values(1).is_ok());
1720        assert!(component_select_min_values(25).is_ok());
1721
1722        assert!(component_select_min_values(26).is_err());
1723    }
1724
1725    #[test]
1726    fn component_select_option_value_length() {
1727        assert!(component_select_option_value("a").is_ok());
1728        assert!(component_select_option_value("a".repeat(100)).is_ok());
1729
1730        assert!(component_select_option_value("a".repeat(101)).is_err());
1731    }
1732
1733    #[test]
1734    fn component_select_options_count() {
1735        let select_menu_options = Vec::from([SelectMenuOption {
1736            default: false,
1737            description: None,
1738            emoji: None,
1739            label: "label".into(),
1740            value: "value".into(),
1741        }]);
1742
1743        assert!(component_select_options(&select_menu_options).is_ok());
1744
1745        let select_menu_options_25 = select_menu_options
1746            .iter()
1747            .cloned()
1748            .cycle()
1749            .take(25)
1750            .collect::<Vec<SelectMenuOption>>();
1751
1752        assert!(component_select_options(&select_menu_options_25).is_ok());
1753
1754        let select_menu_options_26 = select_menu_options
1755            .iter()
1756            .cloned()
1757            .cycle()
1758            .take(26)
1759            .collect::<Vec<SelectMenuOption>>();
1760
1761        assert!(component_select_options(&select_menu_options_26).is_err());
1762    }
1763
1764    #[test]
1765    fn component_select_placeholder_length() {
1766        assert!(component_select_placeholder("").is_ok());
1767        assert!(component_select_placeholder("a").is_ok());
1768        assert!(component_select_placeholder("a".repeat(150)).is_ok());
1769
1770        assert!(component_select_placeholder("a".repeat(151)).is_err());
1771    }
1772
1773    #[test]
1774    fn component_text_input_label_length() {
1775        assert!(component_text_input_label("a").is_ok());
1776        assert!(component_text_input_label("a".repeat(45)).is_ok());
1777
1778        assert!(component_text_input_label("").is_err());
1779        assert!(component_text_input_label("a".repeat(46)).is_err());
1780    }
1781
1782    #[test]
1783    fn component_text_input_max_count() {
1784        assert!(component_text_input_max(1).is_ok());
1785        assert!(component_text_input_max(4000).is_ok());
1786
1787        assert!(component_text_input_max(0).is_err());
1788        assert!(component_text_input_max(4001).is_err());
1789    }
1790
1791    #[test]
1792    fn component_text_input_min_count() {
1793        assert!(component_text_input_min(0).is_ok());
1794        assert!(component_text_input_min(1).is_ok());
1795        assert!(component_text_input_min(4000).is_ok());
1796
1797        assert!(component_text_input_min(4001).is_err());
1798    }
1799
1800    #[test]
1801    fn component_text_input_placeholder_length() {
1802        assert!(component_text_input_placeholder("").is_ok());
1803        assert!(component_text_input_placeholder("a").is_ok());
1804        assert!(component_text_input_placeholder("a".repeat(100)).is_ok());
1805
1806        assert!(component_text_input_placeholder("a".repeat(101)).is_err());
1807    }
1808
1809    #[test]
1810    fn component_text_input_value() {
1811        assert!(component_text_input_min(0).is_ok());
1812        assert!(component_text_input_min(1).is_ok());
1813        assert!(component_text_input_min(4000).is_ok());
1814
1815        assert!(component_text_input_min(4001).is_err());
1816    }
1817}