twilight_validate/
channel.rs

1//! Constants, error types, and functions for validating channel fields.
2
3use std::{
4    error::Error,
5    fmt::{Display, Formatter, Result as FmtResult},
6};
7use twilight_model::channel::ChannelType;
8
9/// Minimum bitrate of a voice channel.
10pub const CHANNEL_BITRATE_MIN: u32 = 8000;
11
12/// Maximum number of bulk messages that can be deleted.
13pub const CHANNEL_BULK_DELETE_MESSAGES_MAX: usize = 100;
14
15/// Minimum number of bulk messages that can be deleted.
16pub const CHANNEL_BULK_DELETE_MESSAGES_MIN: usize = 2;
17
18/// Maximum length of a forum channel's topic.
19pub const CHANNEL_FORUM_TOPIC_LENGTH_MAX: usize = 4096;
20
21/// Maximum length of a channel's name.
22pub const CHANNEL_NAME_LENGTH_MAX: usize = 100;
23
24/// Minimum length of a channel's name.
25pub const CHANNEL_NAME_LENGTH_MIN: usize = 1;
26
27/// Maximum length of a channel's rate limit per user.
28pub const CHANNEL_RATE_LIMIT_PER_USER_MAX: u16 = 21_600;
29
30/// Maximum number of members that can be returned in a thread.
31pub const CHANNEL_THREAD_GET_MEMBERS_LIMIT_MAX: u32 = 100;
32
33/// Minimum number of members that can be returned in a thread.
34pub const CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN: u32 = 1;
35
36/// Maximum length of a channel's topic.
37pub const CHANNEL_TOPIC_LENGTH_MAX: usize = 1024;
38
39/// Maximum user limit of an audio channel.
40pub const CHANNEL_USER_LIMIT_MAX: u16 = 99;
41
42/// Returned when the channel can not be updated as configured.
43#[derive(Debug)]
44pub struct ChannelValidationError {
45    /// Type of error that occurred.
46    kind: ChannelValidationErrorType,
47}
48
49impl ChannelValidationError {
50    /// Immutable reference to the type of error that occurred.
51    #[must_use = "retrieving the type has no effect if left unused"]
52    pub const fn kind(&self) -> &ChannelValidationErrorType {
53        &self.kind
54    }
55
56    /// Consume the error, returning the source error if there is any.
57    #[allow(clippy::unused_self)]
58    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
59    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
60        None
61    }
62
63    /// Consume the error, returning the owned error type and the source error.
64    #[must_use = "consuming the error into its parts has no effect if left unused"]
65    pub fn into_parts(
66        self,
67    ) -> (
68        ChannelValidationErrorType,
69        Option<Box<dyn Error + Send + Sync>>,
70    ) {
71        (self.kind, None)
72    }
73}
74
75impl Display for ChannelValidationError {
76    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
77        match &self.kind {
78            ChannelValidationErrorType::BitrateInvalid => {
79                f.write_str("bitrate is less than ")?;
80                Display::fmt(&CHANNEL_BITRATE_MIN, f)
81            }
82            ChannelValidationErrorType::BulkDeleteMessagesInvalid => {
83                f.write_str("number of messages deleted in bulk is less than ")?;
84                Display::fmt(&CHANNEL_BULK_DELETE_MESSAGES_MIN, f)?;
85                f.write_str(" or greater than ")?;
86
87                Display::fmt(&CHANNEL_BULK_DELETE_MESSAGES_MAX, f)
88            }
89            ChannelValidationErrorType::ForumTopicInvalid => {
90                f.write_str("the forum topic is invalid")
91            }
92            ChannelValidationErrorType::NameInvalid => {
93                f.write_str("the length of the name is invalid")
94            }
95            ChannelValidationErrorType::RateLimitPerUserInvalid { .. } => {
96                f.write_str("the rate limit per user is invalid")
97            }
98            ChannelValidationErrorType::ThreadMemberLimitInvalid => {
99                f.write_str("number of members to return is less than ")?;
100                Display::fmt(&CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN, f)?;
101                f.write_str(" or greater than ")?;
102
103                Display::fmt(&CHANNEL_THREAD_GET_MEMBERS_LIMIT_MAX, f)
104            }
105            ChannelValidationErrorType::TopicInvalid => f.write_str("the topic is invalid"),
106            ChannelValidationErrorType::TypeInvalid { kind } => {
107                Display::fmt(kind.name(), f)?;
108
109                f.write_str(" is not a thread")
110            }
111            ChannelValidationErrorType::UserLimitInvalid => {
112                f.write_str("user limit is greater than ")?;
113
114                Display::fmt(&CHANNEL_USER_LIMIT_MAX, f)
115            }
116        }
117    }
118}
119
120impl Error for ChannelValidationError {}
121
122/// Type of [`ChannelValidationError`] that occurred.
123#[derive(Debug)]
124#[non_exhaustive]
125pub enum ChannelValidationErrorType {
126    /// The bitrate is less than 8000.
127    BitrateInvalid,
128    /// Number of messages being deleted in bulk is invalid.
129    BulkDeleteMessagesInvalid,
130    /// The length of the topic is more than 4096 UTF-16 characters.
131    ForumTopicInvalid,
132    /// The length of the name is either fewer than 1 UTF-16 characters or
133    /// more than 100 UTF-16 characters.
134    NameInvalid,
135    /// The seconds of the rate limit per user is more than 21600.
136    RateLimitPerUserInvalid {
137        /// Provided ratelimit is invalid.
138        rate_limit_per_user: u16,
139    },
140    /// The number of members to return is less than 1 or greater than 100.
141    ThreadMemberLimitInvalid,
142    /// The length of the topic is more than 1024 UTF-16 characters.
143    TopicInvalid,
144    /// Provided type was not a thread.
145    TypeInvalid {
146        /// Provided type.
147        kind: ChannelType,
148    },
149    /// User limit is greater than 99.
150    UserLimitInvalid,
151}
152
153/// Ensure a channel's bitrate is collect.
154///
155/// Must be at least 8000.
156///
157/// # Errors
158///
159/// Returns an error of type [`BitrateInvalid`] if the bitrate is invalid.
160///
161/// [`BitrateInvalid`]: ChannelValidationErrorType::BitrateInvalid
162pub const fn bitrate(value: u32) -> Result<(), ChannelValidationError> {
163    if value >= CHANNEL_BITRATE_MIN {
164        Ok(())
165    } else {
166        Err(ChannelValidationError {
167            kind: ChannelValidationErrorType::BitrateInvalid,
168        })
169    }
170}
171
172/// Ensure the number of messages to delete in bulk is correct.
173///
174/// # Errors
175///
176/// Returns an error of type [`BulkDeleteMessagesInvalid`] if the number of
177/// messages to delete in bulk is invalid.
178///
179/// [`BulkDeleteMessagesInvalid`]: ChannelValidationErrorType::BulkDeleteMessagesInvalid
180pub const fn bulk_delete_messages(message_count: usize) -> Result<(), ChannelValidationError> {
181    if message_count >= CHANNEL_BULK_DELETE_MESSAGES_MIN
182        && message_count <= CHANNEL_BULK_DELETE_MESSAGES_MAX
183    {
184        Ok(())
185    } else {
186        Err(ChannelValidationError {
187            kind: ChannelValidationErrorType::BulkDeleteMessagesInvalid,
188        })
189    }
190}
191
192/// Ensure a channel is a thread.
193///
194/// # Errors
195///
196/// Returns an error of type [`ChannelValidationErrorType::TypeInvalid`] if the
197/// channel is not a thread.
198pub const fn is_thread(kind: ChannelType) -> Result<(), ChannelValidationError> {
199    if matches!(
200        kind,
201        ChannelType::AnnouncementThread | ChannelType::PublicThread | ChannelType::PrivateThread
202    ) {
203        Ok(())
204    } else {
205        Err(ChannelValidationError {
206            kind: ChannelValidationErrorType::TypeInvalid { kind },
207        })
208    }
209}
210
211/// Ensure a forum channel's topic's length is correct.
212///
213/// # Errors
214///
215/// Returns an error of type [`TopicInvalid`] if the
216/// topic is invalid.
217///
218/// [`TopicInvalid`]: ChannelValidationErrorType::TopicInvalid
219pub fn forum_topic(value: impl AsRef<str>) -> Result<(), ChannelValidationError> {
220    let count = value.as_ref().chars().count();
221
222    if count <= CHANNEL_FORUM_TOPIC_LENGTH_MAX {
223        Ok(())
224    } else {
225        Err(ChannelValidationError {
226            kind: ChannelValidationErrorType::TopicInvalid,
227        })
228    }
229}
230
231/// Ensure a channel's name's length is correct.
232///
233/// The length must be less than [`CHANNEL_NAME_LENGTH_MIN`] and at most
234/// [`CHANNEL_NAME_LENGTH_MAX`]. This is based on [this documentation entry].
235///
236/// # Errors
237///
238/// Returns an error of type [`NameInvalid`] if the channel's name's length is
239/// incorrect.
240///
241/// [`NameInvalid`]: ChannelValidationErrorType::NameInvalid
242/// [this documentation entry]: https://discord.com/developers/docs/resources/channel#channels-resource
243pub fn name(value: impl AsRef<str>) -> Result<(), ChannelValidationError> {
244    let len = value.as_ref().chars().count();
245
246    if (CHANNEL_NAME_LENGTH_MIN..=CHANNEL_NAME_LENGTH_MAX).contains(&len) {
247        Ok(())
248    } else {
249        Err(ChannelValidationError {
250            kind: ChannelValidationErrorType::NameInvalid,
251        })
252    }
253}
254
255/// Ensure a channel's rate limit per user is correct.
256///
257/// The value must be at most [`CHANNEL_RATE_LIMIT_PER_USER_MAX`]. This is based
258/// on [this documentation entry].
259///
260/// # Errors
261///
262/// Returns an error of type [`RateLimitPerUserInvalid`] if the rate limit is
263/// invalid.
264///
265/// [`RateLimitPerUserInvalid`]: ChannelValidationErrorType::RateLimitPerUserInvalid
266/// [this documentation entry]: https://discord.com/developers/docs/resources/channel#channels-resource
267pub const fn rate_limit_per_user(value: u16) -> Result<(), ChannelValidationError> {
268    if value <= CHANNEL_RATE_LIMIT_PER_USER_MAX {
269        Ok(())
270    } else {
271        Err(ChannelValidationError {
272            kind: ChannelValidationErrorType::RateLimitPerUserInvalid {
273                rate_limit_per_user: value,
274            },
275        })
276    }
277}
278
279/// Ensure the limit set for the number of thread members to return is correct.
280///
281/// The value must be at least [`CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN`] and at most
282/// [`CHANNEL_THREAD_GET_MEMBERS_LIMIT_MAX`]. This is based on [this documentation entry].
283///
284/// # Errors
285///
286/// Returns an error of type [`ThreadMemberLimitInvalid`] if the limit is invalid.
287///
288/// [`ThreadMemberLimitInvalid`]: ChannelValidationErrorType::ThreadMemberLimitInvalid
289/// [this documentation entry]: https://discord.com/developers/docs/resources/channel#list-thread-members-query-string-params
290pub const fn thread_member_limit(value: u32) -> Result<(), ChannelValidationError> {
291    if value >= CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN
292        && value <= CHANNEL_THREAD_GET_MEMBERS_LIMIT_MAX
293    {
294        Ok(())
295    } else {
296        Err(ChannelValidationError {
297            kind: ChannelValidationErrorType::ThreadMemberLimitInvalid,
298        })
299    }
300}
301
302/// Ensure a channel's topic's length is correct.
303///
304/// # Errors
305///
306/// Returns an error of type [`TopicInvalid`] if the
307/// topic is invalid.
308///
309/// [`TopicInvalid`]: ChannelValidationErrorType::TopicInvalid
310pub fn topic(value: impl AsRef<str>) -> Result<(), ChannelValidationError> {
311    let count = value.as_ref().chars().count();
312
313    if count <= CHANNEL_TOPIC_LENGTH_MAX {
314        Ok(())
315    } else {
316        Err(ChannelValidationError {
317            kind: ChannelValidationErrorType::TopicInvalid,
318        })
319    }
320}
321
322/// Ensure a channel's user limit is correct.
323///
324/// Must be at most 99.
325///
326/// # Errors
327///
328/// Returns an error of type [`UserLimitInvalid`] if the user limit is invalid.
329///
330/// [`UserLimitInvalid`]: ChannelValidationErrorType::BitrateInvalid
331pub const fn user_limit(value: u16) -> Result<(), ChannelValidationError> {
332    if value <= CHANNEL_USER_LIMIT_MAX {
333        Ok(())
334    } else {
335        Err(ChannelValidationError {
336            kind: ChannelValidationErrorType::UserLimitInvalid,
337        })
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn bulk_delete_messages() {
347        assert!(matches!(
348            super::bulk_delete_messages(0).unwrap_err().kind(),
349            ChannelValidationErrorType::BulkDeleteMessagesInvalid,
350        ));
351        assert!(matches!(
352            super::bulk_delete_messages(1).unwrap_err().kind(),
353            ChannelValidationErrorType::BulkDeleteMessagesInvalid,
354        ));
355        assert!(super::bulk_delete_messages(100).is_ok());
356        assert!(matches!(
357            super::bulk_delete_messages(101).unwrap_err().kind(),
358            ChannelValidationErrorType::BulkDeleteMessagesInvalid,
359        ));
360    }
361
362    #[test]
363    fn channel_bitrate() {
364        assert!(bitrate(8000).is_ok());
365
366        assert!(bitrate(7000).is_err());
367    }
368
369    #[test]
370    fn thread_is_thread() {
371        assert!(is_thread(ChannelType::AnnouncementThread).is_ok());
372        assert!(is_thread(ChannelType::PrivateThread).is_ok());
373        assert!(is_thread(ChannelType::PublicThread).is_ok());
374
375        assert!(is_thread(ChannelType::Group).is_err());
376    }
377
378    #[test]
379    fn channel_name() {
380        assert!(name("a").is_ok());
381        assert!(name("a".repeat(100)).is_ok());
382
383        assert!(name("").is_err());
384        assert!(name("a".repeat(101)).is_err());
385    }
386
387    #[test]
388    fn rate_limit_per_user_value() {
389        assert!(rate_limit_per_user(0).is_ok());
390        assert!(rate_limit_per_user(21_600).is_ok());
391
392        assert!(rate_limit_per_user(21_601).is_err());
393    }
394
395    #[test]
396    fn thread_member_limit_value() {
397        assert!(thread_member_limit(1).is_ok());
398        assert!(thread_member_limit(100).is_ok());
399        assert!(thread_member_limit(50).is_ok());
400
401        assert!(thread_member_limit(0).is_err());
402        assert!(thread_member_limit(101).is_err());
403    }
404
405    #[test]
406    fn topic_length() {
407        assert!(topic("").is_ok());
408        assert!(topic("a").is_ok());
409        assert!(topic("a".repeat(1_024)).is_ok());
410
411        assert!(topic("a".repeat(1_025)).is_err());
412    }
413
414    #[test]
415    fn user_limit() {
416        assert!(super::user_limit(0).is_ok());
417        assert!(super::user_limit(99).is_ok());
418        assert!(matches!(
419            super::user_limit(100).unwrap_err().kind(),
420            ChannelValidationErrorType::UserLimitInvalid
421        ));
422    }
423}