twilight_validate/component/
component_v2.rs

1//! Validates components V2.
2
3use super::{
4    action_row, button, select_menu, text_input, ComponentValidationError,
5    ComponentValidationErrorType,
6};
7use twilight_model::channel::message::component::{
8    Container, MediaGallery, MediaGalleryItem, Section, TextDisplay, Thumbnail,
9};
10use twilight_model::channel::message::Component;
11
12/// Maximum length of text display content.
13pub const TEXT_DISPLAY_CONTENT_LENGTH_MAX: usize = 2000;
14
15/// Minimum amount of items in a media gallery.
16pub const MEDIA_GALLERY_ITEMS_MIN: usize = 1;
17
18/// Maximum amount of items in a media gallery.
19pub const MEDIA_GALLERY_ITEMS_MAX: usize = 10;
20
21/// Maximum length of a description of a media gallery item.
22pub const MEDIA_GALLERY_ITEM_DESCRIPTION_LENGTH_MAX: usize = 1024;
23
24/// Minimum amount of components in a section.
25pub const SECTION_COMPONENTS_MIN: usize = 1;
26
27/// Maximum amount of components in a section.
28pub const SECTION_COMPONENTS_MAX: usize = 3;
29
30/// Maximum length of a thumbnail description
31pub const THUMBNAIL_DESCRIPTION_LENGTH_MAX: usize = 1024;
32
33/// Ensure that a top-level request component is correct in V2.
34///
35/// Intended to ensure that a fully formed top-level component for requests
36/// is an action row.
37///
38/// Refer to other validators like [`button`] if you need to validate other
39/// components.
40///
41/// # Errors
42///
43/// For errors refer to the errors of the following functions:
44/// - [`action_row`]
45/// - [`button`]
46/// - [`container`]
47/// - [`media_gallery`]
48/// - [`section`]
49/// - [`select_menu`]
50/// - [`text_display`]
51/// - [`text_input`]
52/// - [`thumbnail`]
53pub fn component_v2(component: &Component) -> Result<(), ComponentValidationError> {
54    match component {
55        Component::ActionRow(ar) => action_row(ar, true)?,
56        Component::Button(b) => button(b)?,
57        Component::Container(c) => container(c)?,
58        Component::MediaGallery(mg) => media_gallery(mg)?,
59        Component::Section(s) => section(s)?,
60        Component::SelectMenu(sm) => select_menu(sm)?,
61        Component::TextDisplay(td) => text_display(td)?,
62        Component::TextInput(ti) => text_input(ti)?,
63        Component::Thumbnail(t) => thumbnail(t)?,
64        Component::Separator(_) | Component::File(_) | Component::Unknown(_) => (),
65    }
66
67    Ok(())
68}
69
70/// Validates a text display component.
71///
72/// # Errors
73///
74/// This will error with [`TextDisplayContentTooLong`] if the content is longer
75/// than [`TEXT_DISPLAY_CONTENT_LENGTH_MAX`].
76///
77/// [`TextDisplayContentTooLong`]: ComponentValidationErrorType::TextDisplayContentTooLong
78pub fn text_display(text_display: &TextDisplay) -> Result<(), ComponentValidationError> {
79    let content_len = text_display.content.len();
80    if content_len > TEXT_DISPLAY_CONTENT_LENGTH_MAX {
81        return Err(ComponentValidationError {
82            kind: ComponentValidationErrorType::TextDisplayContentTooLong { len: content_len },
83        });
84    }
85
86    Ok(())
87}
88
89/// Validates a media gallery component.
90///
91/// # Errors
92///
93/// This will error with [`MediaGalleryItemCountOutOfRange`] if the amount of
94/// media items is less than [`MEDIA_GALLERY_ITEMS_MIN`] or greater than
95/// [`MEDIA_GALLERY_ITEMS_MAX`].
96///
97/// For errors for validation of induvidual items see the dovumentation for [`media_gallery_item`].
98///
99/// [`MediaGalleryItemCountOutOfRange`]: ComponentValidationErrorType::MediaGalleryItemCountOutOfRange
100pub fn media_gallery(media_gallery: &MediaGallery) -> Result<(), ComponentValidationError> {
101    let items = media_gallery.items.len();
102    if !(MEDIA_GALLERY_ITEMS_MIN..=MEDIA_GALLERY_ITEMS_MAX).contains(&items) {
103        return Err(ComponentValidationError {
104            kind: ComponentValidationErrorType::MediaGalleryItemCountOutOfRange { count: items },
105        });
106    }
107
108    for item in &media_gallery.items {
109        media_gallery_item(item)?;
110    }
111
112    Ok(())
113}
114
115/// Validates a section component.
116///
117/// # Errors
118///
119/// This will error with [`SectionComponentCountOutOfRange`] if the amount of
120/// section components is less than [`SECTION_COMPONENTS_MIN`] or greater than
121/// [`SECTION_COMPONENTS_MAX`].
122///
123/// For validation of specific components see:
124/// - [`button`]
125/// - [`text_display`]
126/// - [`thumbnail`]
127///
128/// [`SectionComponentCountOutOfRange`]: ComponentValidationErrorType::SectionComponentCountOutOfRange
129pub fn section(section: &Section) -> Result<(), ComponentValidationError> {
130    let components = section.components.len();
131    if !(SECTION_COMPONENTS_MIN..=SECTION_COMPONENTS_MAX).contains(&components) {
132        return Err(ComponentValidationError {
133            kind: ComponentValidationErrorType::SectionComponentCountOutOfRange {
134                count: components,
135            },
136        });
137    }
138
139    for component in &section.components {
140        match component {
141            Component::TextDisplay(td) => text_display(td)?,
142            _ => {
143                return Err(ComponentValidationError {
144                    kind: ComponentValidationErrorType::DisallowedChildren,
145                })
146            }
147        }
148    }
149
150    match section.accessory.as_ref() {
151        Component::Button(b) => button(b)?,
152        Component::Thumbnail(t) => thumbnail(t)?,
153        _ => {
154            return Err(ComponentValidationError {
155                kind: ComponentValidationErrorType::DisallowedChildren,
156            })
157        }
158    }
159
160    Ok(())
161}
162
163/// Validates a container component.
164///
165/// The only allowed components that are allowed are: `action_row`, `file`,
166/// `media_gallery`, `section`, `separator` and `text_display`.
167///
168/// # Errors
169///
170/// For errors for specific components refer to the errors of the following functions:
171/// - [`action_row`]
172/// - [`media_gallery`]
173/// - [`text_display`]
174/// - [`section`]
175///
176/// If any except the allowed components are used if will fail with [`DisallowedChildren`].
177///
178/// [`DisallowedChildren`]: ComponentValidationErrorType::DisallowedChildren
179pub fn container(container: &Container) -> Result<(), ComponentValidationError> {
180    for component in &container.components {
181        match component {
182            Component::ActionRow(ar) => action_row(ar, true)?,
183            Component::TextDisplay(td) => text_display(td)?,
184            Component::Section(s) => section(s)?,
185            Component::MediaGallery(mg) => media_gallery(mg)?,
186            Component::Separator(_) | Component::File(_) => (),
187            _ => {
188                return Err(ComponentValidationError {
189                    kind: ComponentValidationErrorType::DisallowedChildren,
190                })
191            }
192        }
193    }
194
195    Ok(())
196}
197
198/// Validates a thumbnail component.
199///
200/// # Errors
201///
202/// This will error with [`ThumbnailDescriptionTooLong`] if the description is longer
203/// than [`THUMBNAIL_DESCRIPTION_LENGTH_MAX`].
204///
205/// [`TextDisplayContentTooLong`]: ComponentValidationErrorType::ThumbnailDescriptionTooLong
206pub fn thumbnail(thumbnail: &Thumbnail) -> Result<(), ComponentValidationError> {
207    let Some(Some(desc)) = thumbnail.description.as_ref() else {
208        return Ok(());
209    };
210
211    let len = desc.len();
212    if len > THUMBNAIL_DESCRIPTION_LENGTH_MAX {
213        return Err(ComponentValidationError {
214            kind: ComponentValidationErrorType::ThumbnailDescriptionTooLong { len },
215        });
216    }
217
218    Ok(())
219}
220
221/// Validates a media gallery item
222///
223/// # Errors
224///
225/// This will error with [`MediaGalleryItemDescriptionTooLong`] if the description is longer
226/// than [`MEDIA_GALLERY_ITEM_DESCRIPTION_LENGTH_MAX`].
227///
228/// [`TextDisplayContentTooLong`]: ComponentValidationErrorType::MediaGalleryItemDescriptionTooLong
229fn media_gallery_item(item: &MediaGalleryItem) -> Result<(), ComponentValidationError> {
230    let Some(desc) = item.description.as_ref() else {
231        return Ok(());
232    };
233
234    let len = desc.len();
235    if len > MEDIA_GALLERY_ITEM_DESCRIPTION_LENGTH_MAX {
236        return Err(ComponentValidationError {
237            kind: ComponentValidationErrorType::MediaGalleryItemDescriptionTooLong { len },
238        });
239    }
240
241    Ok(())
242}