twilight_validate/
request.rs

1//! Contains all other input validation functions.
2//!
3//! These functions are generally not related to a specific Discord model.
4
5use std::{
6    error::Error,
7    fmt::{Display, Formatter, Result as FmtResult},
8    time::{SystemTime, UNIX_EPOCH},
9};
10use twilight_model::id::Id;
11use twilight_model::id::marker::{ChannelMarker, RoleMarker};
12use twilight_model::util::Timestamp;
13
14/// The maximum audit log reason length in UTF-16 codepoints.
15pub const AUDIT_REASON_MAX: usize = 512;
16
17/// Maximum length of an auto moderation block action's custom message.
18pub const AUTO_MODERATION_ACTION_BLOCK_CUSTOM_MESSAGE_LENGTH_MAX: usize = 150;
19
20/// Maximum amount of mentions that triggers an auto moderation action.
21pub const AUTO_MODERATION_METADATA_MENTION_TOTAL_LIMIT: u8 = 50;
22
23/// Maximum amount of keywords allowed in the keyword filter.
24pub const AUTO_MODERATION_METADATA_KEYWORD_FILTER_MAX: usize = 1000;
25
26/// Maximum length of a keyword in the keyword filter.
27pub const AUTO_MODERATION_METADATA_KEYWORD_FILTER_LENGTH_MAX: usize = 60;
28
29/// Maximum amount of allowed keywords when using the custom keywords.
30pub const AUTO_MODERATION_METADATA_KEYWORD_ALLOW_LIST_MAX: usize = 100;
31
32/// Maximum amount of allowed keywords when using a keyword preset.
33pub const AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_MAX: usize = 1000;
34
35/// Maximum length of a keyword preset keyword.
36pub const AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_LENGTH_MAX: usize = 60;
37
38/// Maximum length of a allowed keyword.
39pub const AUTO_MODERATION_METADATA_KEYWORD_ALLOW_LIST_LENGTH_MAX: usize = 60;
40
41/// Maximum amount of regex patterns that trigger the auto moderation.
42pub const AUTO_MODERATION_METADATA_REGEX_PATTERNS_MAX: usize = 10;
43
44/// Maximum length of a regex pattern.
45pub const AUTO_MODERATION_METADATA_REGEX_PATTERNS_LENGTH_MAX: usize = 260;
46
47/// Maximum amount of seconds (`2_419_200` this is equivalent to `28` days) to disable communication for.
48pub const AUTO_MODERATION_ACTION_METADATA_DURATION_SECONDS_MAX: u32 = 2_419_200;
49
50/// Maximum amount of exempt roles for the auto moderation rule.
51pub const AUTO_MODERATION_EXEMPT_ROLES_MAX: usize = 20;
52
53/// Maximum amount of exempt channels for the auto moderation rule.
54pub const AUTO_MODERATION_EXEMPT_CHANNELS_MAX: usize = 50;
55
56/// Maximum length of a bio.
57pub const BIO_LIMIT_MAX: usize = 400;
58
59/// Minimum length of a bio.
60pub const BIO_LIMIT_MIN: usize = 1;
61
62/// Maximum amount of seconds (`604_800` this is equivalent to `7` days) for messages to be deleted upon ban.
63pub const CREATE_GUILD_BAN_DELETE_MESSAGE_SECONDS_MAX: u32 = 604_800;
64
65/// Maximum amount of time a member can be timed out for.
66pub const COMMUNICATION_DISABLED_MAX_DURATION: i64 = 28 * 24 * 60 * 60;
67
68/// Maximum amount of messages to get.
69pub const GET_CHANNEL_MESSAGES_LIMIT_MAX: u16 = 100;
70
71/// Minimum amount of messages to get.
72pub const GET_CHANNEL_MESSAGES_LIMIT_MIN: u16 = 1;
73
74/// Maximum amount of guilds to get.
75pub const GET_CURRENT_USER_GUILDS_LIMIT_MAX: u16 = 200;
76
77/// Minimum amount of guilds to get.
78pub const GET_CURRENT_USER_GUILDS_LIMIT_MIN: u16 = 1;
79
80/// Maximum amount of entitlements to get.
81pub const GET_ENTITLEMENTS_LIMIT_MAX: u8 = 100;
82
83/// Minimum amount of entitlements to get.
84pub const GET_ENTITLEMENTS_LIMIT_MIN: u8 = 1;
85
86/// Maximum amount of audit log entries to list.
87pub const GET_GUILD_AUDIT_LOG_LIMIT_MAX: u16 = 100;
88
89/// Minimum amount of audit log entries to list.
90pub const GET_GUILD_AUDIT_LOG_LIMIT_MIN: u16 = 1;
91
92/// Maximum amount of guild bans to list.
93pub const GET_GUILD_BANS_LIMIT_MAX: u16 = 1000;
94
95/// Maximum amount of guild members to list.
96pub const GET_GUILD_MEMBERS_LIMIT_MAX: u16 = 1000;
97
98/// Minimum amount of guild members to list.
99pub const GET_GUILD_MEMBERS_LIMIT_MIN: u16 = 1;
100
101/// Maximum amount of users to return when getting reactions.
102pub const GET_REACTIONS_LIMIT_MIN: u16 = 1;
103
104/// Minimum amount of users to return when getting reactions.
105pub const GET_REACTIONS_LIMIT_MAX: u16 = 100;
106
107/// Maximum length of a guild's name.
108pub const GUILD_NAME_LENGTH_MAX: usize = 100;
109
110/// Minimum length of a guild's name.
111pub const GUILD_NAME_LENGTH_MIN: usize = 2;
112
113/// Maximum amount of days to prune users from a guild.
114pub const GUILD_PRUNE_DAYS_MAX: u16 = 30;
115
116/// Minimum amount of days to prune users from a guild.
117pub const GUILD_PRUNE_DAYS_MIN: u16 = 1;
118
119/// Maximum length of an invite's age, in seconds.
120pub const INVITE_AGE_MAX: u32 = 604_800;
121
122/// Maximum uses of an invite, if not unlimited.
123pub const INVITE_USES_MAX: u16 = 100;
124
125/// Maximum length of a maximum.
126pub const NICKNAME_LIMIT_MAX: usize = 32;
127
128/// Minimum length of a nickname.
129pub const NICKNAME_LIMIT_MIN: usize = 1;
130
131/// Maximum pin limit.
132pub const PIN_LIMIT_MAX: u16 = 50;
133
134/// Minimum pin limit.
135pub const PIN_LIMIT_MIN: u16 = 1;
136
137/// Maximum length of a scheduled event's description.
138pub const SCHEDULED_EVENT_DESCRIPTION_MAX: usize = 1000;
139
140/// Minimum length of a scheduled event's description.
141pub const SCHEDULED_EVENT_DESCRIPTION_MIN: usize = 1;
142
143/// Maximum amount of scheduled event users to get.
144pub const SCHEDULED_EVENT_GET_USERS_MAX: u16 = 100;
145
146/// Minimum amount of scheduled event users to get.
147pub const SCHEDULED_EVENT_GET_USERS_MIN: u16 = 1;
148
149/// Maximum length of a scheduled event's name.
150pub const SCHEDULED_EVENT_NAME_MAX: usize = 100;
151
152/// Minimum length of a scheduled event's name.
153pub const SCHEDULED_EVENT_NAME_MIN: usize = 1;
154
155/// Maximum amount of guild members to search for.
156pub const SEARCH_GUILD_MEMBERS_LIMIT_MAX: u16 = 1000;
157
158/// Minimum amount of guild members to search for.
159pub const SEARCH_GUILD_MEMBERS_LIMIT_MIN: u16 = 1;
160
161/// Maximum stage instance topic length.
162pub const STAGE_TOPIC_LENGTH_MAX: usize = 120;
163
164/// Minimum stage instance topic length.
165pub const STAGE_TOPIC_LENGTH_MIN: usize = 1;
166
167/// Maximum length of a guild template description.
168pub const TEMPLATE_DESCRIPTION_LENGTH_MAX: usize = 120;
169
170/// Maximum length of a guild template name.
171pub const TEMPLATE_NAME_LENGTH_MAX: usize = 100;
172
173/// Minimum length of a guild template name.
174pub const TEMPLATE_NAME_LENGTH_MIN: usize = 1;
175
176/// Maximum length of a username.
177pub const USERNAME_LIMIT_MAX: usize = 32;
178
179/// Minimum length of a username.
180pub const USERNAME_LIMIT_MIN: usize = 2;
181
182/// Maximum length of a webhook username.
183pub const WEBHOOK_USERNAME_LIMIT_MAX: usize = 80;
184
185/// Minimum length of a webhook username.
186pub const WEBHOOK_USERNAME_LIMIT_MIN: usize = 2;
187
188/// Forbidden substrings in usernames.
189const USERNAME_INVALID_SUBSTRINGS: [&str; 5] = ["@", "#", ":", "```", "discord"];
190
191/// Forbidden usernames.
192const USERNAME_INVALID_STRINGS: [&str; 2] = ["everyone", "here"];
193
194/// Forbidden webhook usernames.
195const WEBHOOK_INVALID_STRINGS: [&str; 1] = ["clyde"];
196
197/// A field is not valid.
198#[derive(Debug)]
199pub struct ValidationError {
200    /// Type of error that occurred.
201    kind: ValidationErrorType,
202}
203
204impl ValidationError {
205    /// Immutable reference to the type of error that occurred.
206    #[must_use = "retrieving the type has no effect if left unused"]
207    pub const fn kind(&self) -> &ValidationErrorType {
208        &self.kind
209    }
210
211    /// Consume the error, returning the source error if there is any.
212    #[allow(clippy::unused_self)]
213    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
214    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
215        None
216    }
217
218    /// Consume the error, returning the owned error type and the source error.
219    #[must_use = "consuming the error into its parts has no effect if left unused"]
220    pub fn into_parts(self) -> (ValidationErrorType, Option<Box<dyn Error + Send + Sync>>) {
221        (self.kind, None)
222    }
223}
224
225impl Display for ValidationError {
226    #[allow(clippy::too_many_lines)]
227    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
228        match &self.kind {
229            ValidationErrorType::AuditReason { len } => {
230                f.write_str("provided audit reason length is ")?;
231                Display::fmt(len, f)?;
232                f.write_str(", but it must be at most ")?;
233
234                Display::fmt(&AUDIT_REASON_MAX, f)
235            }
236            ValidationErrorType::AutoModerationBlockActionCustomMessageLimit { len } => {
237                f.write_str("provided auto moderation block action custom message length is ")?;
238                Display::fmt(len, f)?;
239                f.write_str(", but it must be at most ")?;
240
241                Display::fmt(&AUTO_MODERATION_ACTION_BLOCK_CUSTOM_MESSAGE_LENGTH_MAX, f)
242            }
243            ValidationErrorType::AutoModerationMetadataMentionTotalLimit { limit } => {
244                f.write_str("provided auto moderation metadata mention_total_limit is ")?;
245                Display::fmt(limit, f)?;
246                f.write_str(", but it must be at most ")?;
247
248                Display::fmt(&AUTO_MODERATION_METADATA_MENTION_TOTAL_LIMIT, f)
249            }
250            ValidationErrorType::AutoModerationMetadataAllowList { len } => {
251                f.write_str("provided auto moderation metadata allow_list length is ")?;
252                Display::fmt(len, f)?;
253                f.write_str(", but it must be at most ")?;
254
255                Display::fmt(&AUTO_MODERATION_METADATA_KEYWORD_ALLOW_LIST_MAX, f)
256            }
257            ValidationErrorType::AutoModerationMetadataAllowListItem { len, substring } => {
258                f.write_str("provided auto moderation metadata allow_list item ")?;
259                Display::fmt(substring, f)?;
260                f.write_str(" length is ")?;
261                Display::fmt(len, f)?;
262                f.write_str(", but it must be at most ")?;
263
264                Display::fmt(&AUTO_MODERATION_METADATA_KEYWORD_ALLOW_LIST_LENGTH_MAX, f)
265            }
266            ValidationErrorType::AutoModerationMetadataKeywordFilter { len } => {
267                f.write_str("provided auto moderation metadata keyword_filter length is ")?;
268                Display::fmt(len, f)?;
269                f.write_str(", but it must be at most ")?;
270
271                Display::fmt(&AUTO_MODERATION_METADATA_KEYWORD_FILTER_MAX, f)
272            }
273            ValidationErrorType::AutoModerationMetadataKeywordFilterItem { len, substring } => {
274                f.write_str("provided auto moderation metadata keyword_filter item ")?;
275                Display::fmt(substring, f)?;
276                f.write_str(" length is ")?;
277                Display::fmt(len, f)?;
278                f.write_str(", but it must be at most ")?;
279
280                Display::fmt(&AUTO_MODERATION_METADATA_KEYWORD_FILTER_LENGTH_MAX, f)
281            }
282            ValidationErrorType::AutoModerationMetadataPresetAllowList { len } => {
283                f.write_str("provided auto moderation metadata allow_list length is ")?;
284                Display::fmt(len, f)?;
285                f.write_str(", but it must be at most ")?;
286
287                Display::fmt(&AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_MAX, f)
288            }
289            ValidationErrorType::AutoModerationMetadataPresetAllowListItem { len, substring } => {
290                f.write_str("provided auto moderation metadata allow_list item ")?;
291                Display::fmt(substring, f)?;
292                f.write_str(" length is ")?;
293                Display::fmt(len, f)?;
294                f.write_str(", but it must be at most ")?;
295
296                Display::fmt(&AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_LENGTH_MAX, f)
297            }
298            ValidationErrorType::AutoModerationActionMetadataDurationSeconds { seconds } => {
299                f.write_str("provided auto moderation action timeout duration is ")?;
300                Display::fmt(seconds, f)?;
301                f.write_str(", but it must be at most ")?;
302
303                Display::fmt(&AUTO_MODERATION_ACTION_METADATA_DURATION_SECONDS_MAX, f)
304            }
305            ValidationErrorType::AutoModerationMetadataRegexPatterns { len } => {
306                f.write_str("provided auto moderation metadata regex_patterns length is ")?;
307                Display::fmt(len, f)?;
308                f.write_str(", but it must be at most ")?;
309
310                Display::fmt(&AUTO_MODERATION_METADATA_REGEX_PATTERNS_MAX, f)
311            }
312            ValidationErrorType::AutoModerationMetadataRegexPatternsItem { len, substring } => {
313                f.write_str("provided auto moderation metadata regex_patterns item ")?;
314                Display::fmt(substring, f)?;
315                f.write_str(" length is ")?;
316                Display::fmt(len, f)?;
317                f.write_str(", but it must be at most ")?;
318
319                Display::fmt(&AUTO_MODERATION_METADATA_REGEX_PATTERNS_LENGTH_MAX, f)
320            }
321            ValidationErrorType::AutoModerationExemptRoles { len } => {
322                f.write_str("provided auto moderation exempt_roles length is ")?;
323                Display::fmt(len, f)?;
324                f.write_str(", but it must be at most ")?;
325
326                Display::fmt(&AUTO_MODERATION_EXEMPT_ROLES_MAX, f)
327            }
328            ValidationErrorType::AutoModerationExemptChannels { len } => {
329                f.write_str("provided auto moderation exempt_channels length is ")?;
330                Display::fmt(len, f)?;
331                f.write_str(", but it must be at most ")?;
332
333                Display::fmt(&AUTO_MODERATION_EXEMPT_CHANNELS_MAX, f)
334            }
335            ValidationErrorType::Bio { len } => {
336                f.write_str("provided bio length is ")?;
337                Display::fmt(len, f)?;
338
339                f.write_str(", but it must be at least")?;
340                Display::fmt(&BIO_LIMIT_MIN, f)?;
341
342                f.write_str(" and at most ")?;
343                Display::fmt(&BIO_LIMIT_MAX, f)
344            }
345            ValidationErrorType::CreateGuildBanDeleteMessageSeconds {
346                seconds: delete_message_seconds,
347            } => {
348                f.write_str("provided create guild ban delete_message_seconds is ")?;
349                Display::fmt(delete_message_seconds, f)?;
350                f.write_str(", but it must be at most ")?;
351
352                Display::fmt(&CREATE_GUILD_BAN_DELETE_MESSAGE_SECONDS_MAX, f)
353            }
354            ValidationErrorType::CommunicationDisabledUntil { .. } => {
355                f.write_str("provided timestamp is too far in the future")
356            }
357            ValidationErrorType::GetChannelMessages { limit } => {
358                f.write_str("provided get guild members limit is ")?;
359                Display::fmt(limit, f)?;
360                f.write_str(", but it must be at least ")?;
361                Display::fmt(&GET_CHANNEL_MESSAGES_LIMIT_MIN, f)?;
362                f.write_str(" and at most ")?;
363
364                Display::fmt(&GET_CHANNEL_MESSAGES_LIMIT_MAX, f)
365            }
366            ValidationErrorType::GetCurrentUserGuilds { limit } => {
367                f.write_str("provided get current user guilds limit is ")?;
368                Display::fmt(limit, f)?;
369                f.write_str(", but it must be at least ")?;
370                Display::fmt(&GET_CURRENT_USER_GUILDS_LIMIT_MIN, f)?;
371                f.write_str(" and at most ")?;
372
373                Display::fmt(&GET_CURRENT_USER_GUILDS_LIMIT_MAX, f)
374            }
375            ValidationErrorType::GetEntitlements { limit } => {
376                f.write_str("provided get entitlements limit is ")?;
377                Display::fmt(limit, f)?;
378                f.write_str(", but it must be at least ")?;
379                Display::fmt(&GET_ENTITLEMENTS_LIMIT_MIN, f)?;
380                f.write_str(" and at most ")?;
381
382                Display::fmt(&GET_ENTITLEMENTS_LIMIT_MAX, f)
383            }
384            ValidationErrorType::GetGuildAuditLog { limit } => {
385                f.write_str("provided get guild audit log limit is ")?;
386                Display::fmt(limit, f)?;
387                f.write_str(", but it must be at least ")?;
388                Display::fmt(&GET_GUILD_MEMBERS_LIMIT_MIN, f)?;
389                f.write_str(" and at most ")?;
390
391                Display::fmt(&GET_GUILD_MEMBERS_LIMIT_MAX, f)
392            }
393            ValidationErrorType::GetGuildBans { limit } => {
394                f.write_str("provided get guild bans limit is ")?;
395                Display::fmt(limit, f)?;
396                f.write_str(", but it must be at most ")?;
397
398                Display::fmt(&GET_GUILD_BANS_LIMIT_MAX, f)
399            }
400            ValidationErrorType::GetGuildMembers { limit } => {
401                f.write_str("provided get guild members limit is ")?;
402                Display::fmt(limit, f)?;
403                f.write_str(", but it must be at least ")?;
404                Display::fmt(&GET_GUILD_MEMBERS_LIMIT_MIN, f)?;
405                f.write_str(" and at most ")?;
406
407                Display::fmt(&GET_GUILD_MEMBERS_LIMIT_MAX, f)
408            }
409            ValidationErrorType::GetReactions { limit } => {
410                f.write_str("provided get reactions limit is ")?;
411                Display::fmt(limit, f)?;
412                f.write_str(", but it must be at least ")?;
413                Display::fmt(&GET_REACTIONS_LIMIT_MIN, f)?;
414                f.write_str(" and at most ")?;
415
416                Display::fmt(&GET_REACTIONS_LIMIT_MAX, f)
417            }
418            ValidationErrorType::GuildName { len } => {
419                f.write_str("provided guild name length is ")?;
420                Display::fmt(len, f)?;
421                f.write_str(", but it must be at least ")?;
422                Display::fmt(&GUILD_NAME_LENGTH_MIN, f)?;
423                f.write_str(" and at most ")?;
424
425                Display::fmt(&GUILD_NAME_LENGTH_MAX, f)
426            }
427            ValidationErrorType::GuildPruneDays { days } => {
428                f.write_str("provided prune days is ")?;
429                Display::fmt(days, f)?;
430                f.write_str(", but it must be at least ")?;
431                Display::fmt(&GUILD_PRUNE_DAYS_MIN, f)?;
432                f.write_str(" and at most ")?;
433
434                Display::fmt(&GUILD_PRUNE_DAYS_MAX, f)
435            }
436            ValidationErrorType::InviteMaxAge { max_age } => {
437                f.write_str("provided invite max_age is ")?;
438                Display::fmt(max_age, f)?;
439                f.write_str(", but it must be at most ")?;
440
441                Display::fmt(&INVITE_AGE_MAX, f)
442            }
443            ValidationErrorType::InviteMaxUses { max_uses } => {
444                f.write_str("provided invite max_uses is ")?;
445                Display::fmt(max_uses, f)?;
446                f.write_str(", but it must be at most ")?;
447
448                Display::fmt(&INVITE_USES_MAX, f)
449            }
450            ValidationErrorType::Nickname { len } => {
451                f.write_str("provided nickname length is ")?;
452                Display::fmt(len, f)?;
453                f.write_str(", but it must be at least ")?;
454                Display::fmt(&NICKNAME_LIMIT_MIN, f)?;
455                f.write_str(" and at most ")?;
456
457                Display::fmt(&NICKNAME_LIMIT_MAX, f)
458            }
459            ValidationErrorType::Pin { limit } => {
460                f.write_str("provided pin limit is ")?;
461                Display::fmt(limit, f)?;
462                f.write_str(", but it must be at least ")?;
463                Display::fmt(&PIN_LIMIT_MIN, f)?;
464                f.write_str(" and at most ")?;
465                Display::fmt(&PIN_LIMIT_MAX, f)
466            }
467            ValidationErrorType::ScheduledEventDescription { len } => {
468                f.write_str("provided scheduled event description is length is ")?;
469                Display::fmt(len, f)?;
470                f.write_str(", but it must be at least ")?;
471                Display::fmt(&SCHEDULED_EVENT_DESCRIPTION_MIN, f)?;
472                f.write_str(" and at most ")?;
473
474                Display::fmt(&SCHEDULED_EVENT_DESCRIPTION_MAX, f)
475            }
476            ValidationErrorType::ScheduledEventGetUsers { limit } => {
477                f.write_str("provided scheduled event get users limit is ")?;
478                Display::fmt(limit, f)?;
479                f.write_str(", but it must be at least ")?;
480                Display::fmt(&SCHEDULED_EVENT_GET_USERS_MIN, f)?;
481                f.write_str(" and at most ")?;
482
483                Display::fmt(&SCHEDULED_EVENT_GET_USERS_MAX, f)
484            }
485            ValidationErrorType::ScheduledEventName { len } => {
486                f.write_str("provided scheduled event name is length is ")?;
487                Display::fmt(len, f)?;
488                f.write_str(", but it must be at least ")?;
489                Display::fmt(&SCHEDULED_EVENT_NAME_MIN, f)?;
490                f.write_str(" and at most ")?;
491
492                Display::fmt(&SCHEDULED_EVENT_NAME_MAX, f)
493            }
494            ValidationErrorType::SearchGuildMembers { limit } => {
495                f.write_str("provided search guild members limit is ")?;
496                Display::fmt(limit, f)?;
497                f.write_str(", but it must be at least ")?;
498                Display::fmt(&SEARCH_GUILD_MEMBERS_LIMIT_MIN, f)?;
499                f.write_str(" and at most ")?;
500
501                Display::fmt(&SEARCH_GUILD_MEMBERS_LIMIT_MAX, f)
502            }
503            ValidationErrorType::StageTopic { len } => {
504                f.write_str("provided stage instance topic length is ")?;
505                Display::fmt(len, f)?;
506                f.write_str(", but it must be at least ")?;
507                Display::fmt(&STAGE_TOPIC_LENGTH_MIN, f)?;
508                f.write_str(" and at most ")?;
509
510                Display::fmt(&STAGE_TOPIC_LENGTH_MAX, f)
511            }
512            ValidationErrorType::TemplateDescription { len } => {
513                f.write_str("provided guild template description topic length is ")?;
514                Display::fmt(len, f)?;
515                f.write_str(", but it must be at most ")?;
516
517                Display::fmt(&TEMPLATE_DESCRIPTION_LENGTH_MAX, f)
518            }
519            ValidationErrorType::TemplateName { len } => {
520                f.write_str("provided guild template name length is ")?;
521                Display::fmt(len, f)?;
522                f.write_str(", but it must be at least ")?;
523                Display::fmt(&TEMPLATE_NAME_LENGTH_MIN, f)?;
524                f.write_str(" and at most ")?;
525
526                Display::fmt(&TEMPLATE_NAME_LENGTH_MAX, f)
527            }
528            ValidationErrorType::Username { len, substring }
529            | ValidationErrorType::WebhookUsername { len, substring } => {
530                f.write_str("provided username")?;
531
532                if let Some(len) = len {
533                    f.write_str(" length is ")?;
534                    Display::fmt(len, f)?;
535                    f.write_str(", but it must be at least ")?;
536                    Display::fmt(&USERNAME_LIMIT_MIN, f)?;
537                    f.write_str(" and at most ")?;
538                    Display::fmt(&USERNAME_LIMIT_MAX, f)?;
539                }
540
541                if let Some(substring) = substring {
542                    if len.is_some() {
543                        f.write_str(", and")?;
544                    }
545
546                    f.write_str(" cannot contain ")?;
547                    Display::fmt(substring, f)?;
548                }
549
550                Ok(())
551            }
552        }
553    }
554}
555
556impl Error for ValidationError {}
557
558/// Type of [`ValidationError`] that occurred.
559#[derive(Debug)]
560pub enum ValidationErrorType {
561    /// Provided audit reason was too large.
562    AuditReason {
563        /// Invalid length.
564        len: usize,
565    },
566    /// Provided block action custom message was too long.
567    AutoModerationBlockActionCustomMessageLimit {
568        /// Invalid limit.
569        len: usize,
570    },
571    /// Provided limit was too large.
572    AutoModerationMetadataMentionTotalLimit {
573        /// Invalid limit.
574        limit: u8,
575    },
576    /// Provided keyword filter was invalid.
577    AutoModerationMetadataKeywordFilter {
578        /// Invalid length.
579        len: usize,
580    },
581    /// Provided keyword was invalid.
582    AutoModerationMetadataKeywordFilterItem {
583        /// Invalid length.
584        len: usize,
585        /// Invalid substring.
586        substring: String,
587    },
588    /// Provided keyword allow list was invalid.
589    AutoModerationMetadataAllowList {
590        /// Invalid length.
591        len: usize,
592    },
593    /// Provided allow list item was invalid.
594    AutoModerationMetadataAllowListItem {
595        /// Invalid length.
596        len: usize,
597        /// Invalid substring.
598        substring: String,
599    },
600    /// Provided keyword preset allow list was invalid.
601    AutoModerationMetadataPresetAllowList {
602        /// Invalid length.
603        len: usize,
604    },
605    /// Provided keyword preset allow list item was invalid.
606    AutoModerationMetadataPresetAllowListItem {
607        /// Invalid length.
608        len: usize,
609        /// Invalid substring.
610        substring: String,
611    },
612    /// Provided seconds to disable communication was invalid.
613    AutoModerationActionMetadataDurationSeconds {
614        /// Invalid seconds.
615        seconds: u32,
616    },
617    /// Provided regex patterns was invalid.
618    AutoModerationMetadataRegexPatterns {
619        /// Invalid length.
620        len: usize,
621    },
622    /// Provided regex patterns item was invalid.
623    AutoModerationMetadataRegexPatternsItem {
624        /// Invalid length.
625        len: usize,
626        /// Invalid substring.
627        substring: String,
628    },
629    /// Provided exempt roles was invalid.
630    AutoModerationExemptRoles {
631        /// Invalid length.
632        len: usize,
633    },
634    /// Provided exempt channels was invalid.
635    AutoModerationExemptChannels {
636        /// Invalid length.
637        len: usize,
638    },
639    /// Provided bio length was invalid.
640    Bio {
641        /// Invalid length.
642        len: usize,
643    },
644    /// Provided create guild ban delete message seconds was invalid.
645    CreateGuildBanDeleteMessageSeconds {
646        /// Invalid seconds.
647        seconds: u32,
648    },
649    /// Provided timestamp is too far in the future.
650    CommunicationDisabledUntil {
651        /// Invalid timestamp.
652        timestamp: Timestamp,
653    },
654    /// Provided get channel messages limit was invalid.
655    GetChannelMessages {
656        /// Invalid limit.
657        limit: u16,
658    },
659    /// Provided get current user guilds limit was invalid.
660    GetCurrentUserGuilds {
661        /// Invalid limit.
662        limit: u16,
663    },
664    /// Provided get entitlements limit was invalid.
665    GetEntitlements {
666        /// Invalid limit.
667        limit: u8,
668    },
669    /// Provided get guild audit log limit was invalid.
670    GetGuildAuditLog {
671        /// Invalid limit.
672        limit: u16,
673    },
674    /// Provided get guild bans limit was invalid.
675    GetGuildBans {
676        /// Invalid limit.
677        limit: u16,
678    },
679    /// Provided get guild members limit was invalid.
680    GetGuildMembers {
681        /// Invalid limit.
682        limit: u16,
683    },
684    /// Provided get reactions limit was invalid.
685    GetReactions {
686        /// Invalid limit.
687        limit: u16,
688    },
689    /// Provided guild name was invalid.
690    GuildName {
691        /// Invalid length.
692        len: usize,
693    },
694    /// Provided guild prune days was invalid.
695    GuildPruneDays {
696        /// Invalid days.
697        days: u16,
698    },
699    /// Provided invite max age was invalid.
700    InviteMaxAge {
701        /// Invalid age.
702        max_age: u32,
703    },
704    /// Provided invite max uses was invalid.
705    InviteMaxUses {
706        /// Invalid age.
707        max_uses: u16,
708    },
709    /// Provided nickname length was invalid.
710    Nickname {
711        /// Invalid length.
712        len: usize,
713    },
714    /// Provided pin limit was invalid.
715    Pin {
716        /// Invalid limit.
717        limit: u16,
718    },
719    /// Scheduled event description is invalid.
720    ScheduledEventDescription {
721        /// Invalid length.
722        len: usize,
723    },
724    /// Scheduled event get users limit is invalid.
725    ScheduledEventGetUsers {
726        /// Invalid limit.
727        limit: u16,
728    },
729    /// Scheduled event name is invalid.
730    ScheduledEventName {
731        /// Invalid length.
732        len: usize,
733    },
734    /// Provided search guild members limit was invalid.
735    SearchGuildMembers {
736        /// Invalid limit.
737        limit: u16,
738    },
739    /// Provided stage instance topic was invalid.
740    StageTopic {
741        /// Invalid length.
742        len: usize,
743    },
744    /// Provided guild template description was invalid.
745    TemplateDescription {
746        /// Invalid length.
747        len: usize,
748    },
749    /// Provided guild template name was invalid.
750    TemplateName {
751        /// Invalid length.
752        len: usize,
753    },
754    /// Provided username was invalid.
755    Username {
756        /// Invalid length.
757        len: Option<usize>,
758        /// Invalid substring.
759        substring: Option<&'static str>,
760    },
761    /// Provided webhook username was invalid.
762    WebhookUsername {
763        /// Invalid length.
764        len: Option<usize>,
765        /// Invalid substring.
766        substring: Option<&'static str>,
767    },
768}
769
770/// Ensure that an audit reason is correct.
771///
772/// The length must be at most [`AUDIT_REASON_MAX`]. This is based on
773/// [this documentation entry].
774///
775/// # Errors
776///
777/// Returns an error of type [`AuditReason`] if the length is invalid.
778///
779/// [`AuditReason`]: ValidationErrorType::AuditReason
780/// [this documentation entry]: https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-entry-structure
781pub fn audit_reason(audit_reason: impl AsRef<str>) -> Result<(), ValidationError> {
782    let len = audit_reason.as_ref().chars().count();
783
784    if len <= AUDIT_REASON_MAX {
785        Ok(())
786    } else {
787        Err(ValidationError {
788            kind: ValidationErrorType::AuditReason { len },
789        })
790    }
791}
792
793/// Ensure that an auto moderation block action's `custom_message` is correct.
794///
795/// The length must be at most [`AUTO_MODERATION_ACTION_BLOCK_CUSTOM_MESSAGE_LENGTH_MAX`].
796/// This is based on [this documentation entry].
797///
798/// # Errors
799///
800/// Returns an error of type [`AutoModerationBlockActionCustomMessageLimit`] if the
801/// length is invalid.
802///
803/// [`AutoModerationBlockActionCustomMessageLimit`]: ValidationErrorType::AutoModerationBlockActionCustomMessageLimit
804/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata
805pub fn auto_moderation_block_action_custom_message_limit(
806    custom_message: impl AsRef<str>,
807) -> Result<(), ValidationError> {
808    let len = custom_message.as_ref().chars().count();
809
810    if len <= AUTO_MODERATION_ACTION_BLOCK_CUSTOM_MESSAGE_LENGTH_MAX {
811        Ok(())
812    } else {
813        Err(ValidationError {
814            kind: ValidationErrorType::AutoModerationBlockActionCustomMessageLimit { len },
815        })
816    }
817}
818
819/// Ensure that an auto moderation rule's `mention_total_limit` is correct.
820///
821/// The length must be at most [`AUTO_MODERATION_METADATA_MENTION_TOTAL_LIMIT`].
822/// This is based on [this documentation entry].
823///
824/// # Errors
825///
826/// Returns an error of type [`AutoModerationMetadataMentionTotalLimit`] if the
827/// length is invalid.
828///
829/// [`AutoModerationMetadataMentionTotalLimit`]: ValidationErrorType::AutoModerationMetadataMentionTotalLimit
830/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata
831pub const fn auto_moderation_metadata_mention_total_limit(
832    limit: u8,
833) -> Result<(), ValidationError> {
834    if limit <= AUTO_MODERATION_METADATA_MENTION_TOTAL_LIMIT {
835        Ok(())
836    } else {
837        Err(ValidationError {
838            kind: ValidationErrorType::AutoModerationMetadataMentionTotalLimit { limit },
839        })
840    }
841}
842
843/// Ensure that an auto moderation rule's `keyword_filter` is correct.
844///
845/// The length must be at most [`AUTO_MODERATION_METADATA_KEYWORD_FILTER_LENGTH_MAX`].
846/// This is based on [this documentation entry].
847///
848/// # Errors
849///
850/// Returns an error of type [`AutoModerationMetadataKeywordFilter`] if the
851/// length is invalid.
852///
853/// Returns an error of type [`AutoModerationMetadataKeywordFilterItem`] if any
854/// of the keywords are invalid.
855///
856/// [`AutoModerationMetadataKeywordFilter`]: ValidationErrorType::AutoModerationMetadataKeywordFilter
857/// [`AutoModerationMetadataKeywordFilterItem`]: ValidationErrorType::AutoModerationMetadataKeywordFilterItem
858/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata-field-limits
859pub fn auto_moderation_metadata_keyword_filter(
860    keywords: &[impl AsRef<str>],
861) -> Result<(), ValidationError> {
862    let len = keywords.len();
863
864    if len <= AUTO_MODERATION_METADATA_KEYWORD_FILTER_MAX {
865        for keyword in keywords {
866            auto_moderation_metadata_keyword_filter_item(keyword)?;
867        }
868
869        Ok(())
870    } else {
871        Err(ValidationError {
872            kind: ValidationErrorType::AutoModerationMetadataKeywordFilter { len },
873        })
874    }
875}
876
877/// Ensure that an auto moderation rule's `keyword_filter` item is correct.
878///
879/// The length must be at most [`AUTO_MODERATION_METADATA_KEYWORD_FILTER_LENGTH_MAX`].
880/// This is based on [this documentation entry].
881///
882/// # Errors
883///
884/// Returns an error of type [`AutoModerationMetadataKeywordFilterItem`] if the
885/// length is invalid.
886///
887/// [`AutoModerationMetadataKeywordFilterItem`]: ValidationErrorType::AutoModerationMetadataKeywordFilterItem
888/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata-field-limits
889pub fn auto_moderation_metadata_keyword_filter_item(
890    keyword: impl AsRef<str>,
891) -> Result<(), ValidationError> {
892    let len = keyword.as_ref().chars().count();
893
894    if len <= AUTO_MODERATION_METADATA_KEYWORD_FILTER_LENGTH_MAX {
895        Ok(())
896    } else {
897        Err(ValidationError {
898            kind: ValidationErrorType::AutoModerationMetadataKeywordFilterItem {
899                len,
900                substring: keyword.as_ref().to_string(),
901            },
902        })
903    }
904}
905
906/// Ensure that an auto moderation rule's `allow_list` is correct.
907///
908/// The length must be at most [`AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_MAX`].
909/// This is based on [this documentation entry].
910///
911/// # Errors
912///
913/// Returns an error of type [`AutoModerationMetadataAllowList`] if the
914/// length is invalid.
915///
916/// Returns an error of type [`AutoModerationMetadataAllowListItem`] if any of
917/// the items are invalid.
918///
919/// [`AutoModerationMetadataAllowList`]: ValidationErrorType::AutoModerationMetadataAllowList
920/// [`AutoModerationMetadataAllowListItem`]: ValidationErrorType::AutoModerationMetadataAllowListItem
921/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata-field-limits
922pub fn auto_moderation_metadata_keyword_allow_list(
923    keywords: &[impl AsRef<str>],
924) -> Result<(), ValidationError> {
925    let len = keywords.len();
926
927    if len <= AUTO_MODERATION_METADATA_KEYWORD_ALLOW_LIST_MAX {
928        for keyword in keywords {
929            auto_moderation_metadata_keyword_allow_list_item(keyword)?;
930        }
931
932        Ok(())
933    } else {
934        Err(ValidationError {
935            kind: ValidationErrorType::AutoModerationMetadataAllowList { len },
936        })
937    }
938}
939
940/// Ensure that an auto moderation rule's `allow_list` item is correct.
941///
942/// The length must be at most [`AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_MAX`].
943/// This is based on [this documentation entry].
944///
945/// # Errors
946///
947/// Returns an error of type [`AutoModerationMetadataAllowListItem`] if the
948/// length is invalid.
949///
950/// [`AutoModerationMetadataAllowListItem`]: ValidationErrorType::AutoModerationMetadataAllowListItem
951/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata-field-limits
952pub fn auto_moderation_metadata_keyword_allow_list_item(
953    keyword: impl AsRef<str>,
954) -> Result<(), ValidationError> {
955    let len = keyword.as_ref().chars().count();
956
957    if len <= AUTO_MODERATION_METADATA_KEYWORD_FILTER_LENGTH_MAX {
958        Ok(())
959    } else {
960        Err(ValidationError {
961            kind: ValidationErrorType::AutoModerationMetadataAllowListItem {
962                len,
963                substring: keyword.as_ref().to_string(),
964            },
965        })
966    }
967}
968
969/// Ensure that an auto moderation rule's `allow_list` is correct when using presets.
970///
971/// The length must be at most [`AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_MAX`].
972/// This is based on [this documentation entry].
973///
974/// # Errors
975///
976/// Returns an error of type [`AutoModerationMetadataPresetAllowList`] if the
977/// length is invalid.
978///
979/// Returns an error of type [`AutoModerationMetadataPresetAllowListItem`] if any of
980/// the items are invalid.
981///
982/// [`AutoModerationMetadataPresetAllowList`]: ValidationErrorType::AutoModerationMetadataPresetAllowList
983/// [`AutoModerationMetadataPresetAllowListItem`]: ValidationErrorType::AutoModerationMetadataPresetAllowListItem
984/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata-field-limits
985pub fn auto_moderation_metadata_preset_allow_list(
986    keywords: &[impl AsRef<str>],
987) -> Result<(), ValidationError> {
988    let len = keywords.len();
989
990    if len <= AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_MAX {
991        for keyword in keywords {
992            auto_moderation_metadata_preset_allow_list_item(keyword)?;
993        }
994
995        Ok(())
996    } else {
997        Err(ValidationError {
998            kind: ValidationErrorType::AutoModerationMetadataPresetAllowList { len },
999        })
1000    }
1001}
1002
1003/// Ensure that an auto moderation rule's `allow_list` item is correct when using presets.
1004///
1005/// The length must be at most [`AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_LENGTH_MAX`].
1006/// This is based on [this documentation entry].
1007///
1008/// # Errors
1009///
1010/// Returns an error of type [`AutoModerationMetadataPresetAllowListItem`] if the
1011/// length is invalid.
1012///
1013/// [`AutoModerationMetadataPresetAllowListItem`]: ValidationErrorType::AutoModerationMetadataPresetAllowListItem
1014/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata-field-limits
1015pub fn auto_moderation_metadata_preset_allow_list_item(
1016    keyword: impl AsRef<str>,
1017) -> Result<(), ValidationError> {
1018    let len = keyword.as_ref().chars().count();
1019
1020    if len <= AUTO_MODERATION_METADATA_PRESET_ALLOW_LIST_LENGTH_MAX {
1021        Ok(())
1022    } else {
1023        Err(ValidationError {
1024            kind: ValidationErrorType::AutoModerationMetadataPresetAllowListItem {
1025                len,
1026                substring: keyword.as_ref().to_string(),
1027            },
1028        })
1029    }
1030}
1031
1032/// Ensure that an auto moderation rule's `regex_patterns` is correct.
1033///
1034/// The length must be at most [`AUTO_MODERATION_METADATA_REGEX_PATTERNS_MAX`].
1035/// This is based on [this documentation entry].
1036///
1037/// # Errors
1038///
1039/// Returns an error of type [`AutoModerationMetadataRegexPatterns`] if the
1040/// length is invalid.
1041///
1042/// Returns an error of type [`AutoModerationMetadataRegexPatternsItem`] if any of
1043/// the items are invalid.
1044///
1045/// [`AutoModerationMetadataRegexPatterns`]: ValidationErrorType::AutoModerationMetadataRegexPatterns
1046/// [`AutoModerationMetadataRegexPatternsItem`]: ValidationErrorType::AutoModerationMetadataRegexPatternsItem
1047/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata-field-limits
1048pub fn auto_moderation_metadata_regex_patterns(
1049    patterns: &[impl AsRef<str>],
1050) -> Result<(), ValidationError> {
1051    let len = patterns.len();
1052
1053    if len <= AUTO_MODERATION_METADATA_REGEX_PATTERNS_MAX {
1054        for pattern in patterns {
1055            auto_moderation_metadata_regex_patterns_item(pattern)?;
1056        }
1057
1058        Ok(())
1059    } else {
1060        Err(ValidationError {
1061            kind: ValidationErrorType::AutoModerationMetadataRegexPatterns { len },
1062        })
1063    }
1064}
1065
1066/// Ensure that an auto moderation rule's `regex_patterns` item is correct.
1067///
1068/// The length must be at most [`AUTO_MODERATION_METADATA_REGEX_PATTERNS_LENGTH_MAX`].
1069/// This is based on [this documentation entry].
1070///
1071/// # Errors
1072///
1073/// Returns an error of type [`AutoModerationMetadataRegexPatternsItem`] if the
1074/// length is invalid.
1075///
1076/// [`AutoModerationMetadataRegexPatternsItem`]: ValidationErrorType::AutoModerationMetadataRegexPatternsItem
1077/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata-field-limits
1078pub fn auto_moderation_metadata_regex_patterns_item(
1079    pattern: impl AsRef<str>,
1080) -> Result<(), ValidationError> {
1081    let len = pattern.as_ref().chars().count();
1082
1083    if len <= AUTO_MODERATION_METADATA_REGEX_PATTERNS_LENGTH_MAX {
1084        Ok(())
1085    } else {
1086        Err(ValidationError {
1087            kind: ValidationErrorType::AutoModerationMetadataRegexPatternsItem {
1088                len,
1089                substring: pattern.as_ref().to_string(),
1090            },
1091        })
1092    }
1093}
1094
1095/// Ensure that the `duration_seconds` field for an auto moderation action
1096/// metadata is correct.
1097///
1098/// The duration must be at most [`AUTO_MODERATION_ACTION_METADATA_DURATION_SECONDS_MAX`].
1099/// This is based on [this documentation entry].
1100///
1101/// # Errors
1102///
1103/// Returns an error of type [`AutoModerationActionMetadataDurationSeconds`] if the
1104/// duration is invalid.
1105///
1106/// [`AutoModerationActionMetadataDurationSeconds`]: ValidationErrorType::AutoModerationActionMetadataDurationSeconds
1107/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata
1108pub const fn auto_moderation_action_metadata_duration_seconds(
1109    seconds: u32,
1110) -> Result<(), ValidationError> {
1111    if seconds <= AUTO_MODERATION_ACTION_METADATA_DURATION_SECONDS_MAX {
1112        Ok(())
1113    } else {
1114        Err(ValidationError {
1115            kind: ValidationErrorType::AutoModerationActionMetadataDurationSeconds { seconds },
1116        })
1117    }
1118}
1119
1120/// Ensure that the `exempt_roles` field for an auto moderation rule is correct.
1121///
1122/// The length must be at most [`AUTO_MODERATION_EXEMPT_ROLES_MAX`]. This is based
1123/// on [this documentation entry].
1124///
1125/// # Errors
1126///
1127/// Returns an error of type [`AutoModerationExemptRoles`] if the length is
1128/// invalid.
1129///
1130/// [`AutoModerationExemptRoles`]: ValidationErrorType::AutoModerationExemptRoles
1131/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-auto-moderation-rule-structure
1132pub const fn auto_moderation_exempt_roles(roles: &[Id<RoleMarker>]) -> Result<(), ValidationError> {
1133    let len = roles.len();
1134
1135    if len <= AUTO_MODERATION_EXEMPT_ROLES_MAX {
1136        Ok(())
1137    } else {
1138        Err(ValidationError {
1139            kind: ValidationErrorType::AutoModerationExemptRoles { len },
1140        })
1141    }
1142}
1143
1144/// Ensure that the `exempt_channels` field for an auto moderation rule is correct.
1145///
1146/// The length must be at most [`AUTO_MODERATION_EXEMPT_CHANNELS_MAX`]. This is based
1147/// on [this documentation entry].
1148///
1149/// # Errors
1150///
1151/// Returns an error of type [`AutoModerationExemptChannels`] if the length is
1152/// invalid.
1153///
1154/// [`AutoModerationExemptChannels`]: ValidationErrorType::AutoModerationExemptChannels
1155/// [this documentation entry]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-auto-moderation-rule-structure
1156pub const fn auto_moderation_exempt_channels(
1157    channels: &[Id<ChannelMarker>],
1158) -> Result<(), ValidationError> {
1159    let len = channels.len();
1160
1161    if len <= AUTO_MODERATION_EXEMPT_CHANNELS_MAX {
1162        Ok(())
1163    } else {
1164        Err(ValidationError {
1165            kind: ValidationErrorType::AutoModerationExemptChannels { len },
1166        })
1167    }
1168}
1169
1170/// Ensures that a bio is correct.
1171///
1172/// The bio's length must be at least [`BIO_LIMIT_MIN`], and at maximum [`BIO_LIMIT_MAX`]
1173///
1174/// This is based off the limitations enforced when setting a bio on the [developer portal].
1175///
1176/// # Errors
1177///
1178/// Returns an error of type [`Bio`] if the length is invalid.
1179///
1180/// [`Bio`]: ValidationErrorType::Bio
1181/// [developer portal]: https://discord.dev
1182pub fn bio(bio: impl AsRef<str>) -> Result<(), ValidationError> {
1183    let len = bio.as_ref().chars().count();
1184
1185    if (BIO_LIMIT_MIN..=BIO_LIMIT_MAX).contains(&len) {
1186        Ok(())
1187    } else {
1188        Err(ValidationError {
1189            kind: ValidationErrorType::Bio { len },
1190        })
1191    }
1192}
1193
1194/// Ensure that the delete message seconds amount for the Create Guild Ban request
1195/// is correct.
1196///
1197/// The seconds must be at most [`CREATE_GUILD_BAN_DELETE_MESSAGE_SECONDS_MAX`]. This
1198/// is based on [this documentation entry].
1199///
1200/// # Errors
1201///
1202/// Returns an error of type [`CreateGuildBanDeleteMessageSeconds`] if the seconds is
1203/// invalid.
1204///
1205/// [`CreateGuildBanDeleteMessageSeconds`]: ValidationErrorType::CreateGuildBanDeleteMessageSeconds
1206/// [this documentation entry]: https://discord.com/developers/docs/resources/guild#create-guild-ban
1207pub const fn create_guild_ban_delete_message_seconds(seconds: u32) -> Result<(), ValidationError> {
1208    if seconds <= CREATE_GUILD_BAN_DELETE_MESSAGE_SECONDS_MAX {
1209        Ok(())
1210    } else {
1211        Err(ValidationError {
1212            kind: ValidationErrorType::CreateGuildBanDeleteMessageSeconds { seconds },
1213        })
1214    }
1215}
1216
1217/// Validate that a timeout time is not too far in the future.
1218///
1219/// The time must not be farther than 28 days in the future.
1220///
1221/// # Errors
1222#[allow(clippy::cast_possible_wrap)] // casting of unix timestamp should never wrap
1223pub fn communication_disabled_until(timestamp: Timestamp) -> Result<(), ValidationError> {
1224    let now = SystemTime::now()
1225        .duration_since(UNIX_EPOCH)
1226        .map_err(|_| ValidationError {
1227            kind: ValidationErrorType::CommunicationDisabledUntil { timestamp },
1228        })?;
1229
1230    let end = timestamp.as_secs();
1231
1232    if end - now.as_secs() as i64 <= COMMUNICATION_DISABLED_MAX_DURATION {
1233        Ok(())
1234    } else {
1235        Err(ValidationError {
1236            kind: ValidationErrorType::CommunicationDisabledUntil { timestamp },
1237        })
1238    }
1239}
1240
1241/// Ensure that the limit for the Get Channel Messages request is correct.
1242///
1243/// The limit must be at least [`GET_CHANNEL_MESSAGES_LIMIT_MIN`] and at most
1244/// [`GET_CHANNEL_MESSAGES_LIMIT_MAX`]. This is based on
1245/// [this documentation entry].
1246///
1247/// # Errors
1248///
1249/// Returns an error of type [`GetChannelMessages`] if the limit is invalid.
1250///
1251/// [`GetChannelMessages`]: ValidationErrorType::GetChannelMessages
1252/// [this documentation entry]: https://discord.com/developers/docs/resources/channel#get-channel-messages
1253pub const fn get_channel_messages_limit(limit: u16) -> Result<(), ValidationError> {
1254    if limit >= GET_CHANNEL_MESSAGES_LIMIT_MIN && limit <= GET_CHANNEL_MESSAGES_LIMIT_MAX {
1255        Ok(())
1256    } else {
1257        Err(ValidationError {
1258            kind: ValidationErrorType::GetChannelMessages { limit },
1259        })
1260    }
1261}
1262
1263/// Ensure that the limit for the Get Current User Guilds request is correct.
1264///
1265/// The limit must be at least [`GET_CURRENT_USER_GUILDS_LIMIT_MIN`] and at most
1266/// [`GET_CURRENT_USER_GUILDS_LIMIT_MAX`]. This is based on
1267/// [this documentation entry].
1268///
1269/// # Errors
1270///
1271/// Returns an error of type [`GetCurrentUserGuilds`] if the limit is invalid.
1272///
1273/// [`GetCurrentUserGuilds`]: ValidationErrorType::GetCurrentUserGuilds
1274/// [this documentation entry]: https://discord.com/developers/docs/resources/user#get-current-user-guilds
1275pub const fn get_current_user_guilds_limit(limit: u16) -> Result<(), ValidationError> {
1276    if limit >= GET_CURRENT_USER_GUILDS_LIMIT_MIN && limit <= GET_CURRENT_USER_GUILDS_LIMIT_MAX {
1277        Ok(())
1278    } else {
1279        Err(ValidationError {
1280            kind: ValidationErrorType::GetCurrentUserGuilds { limit },
1281        })
1282    }
1283}
1284
1285/// Ensure that the limit for the Get Entitlements endpoint is correct.
1286///
1287/// The limit must be at least [`GET_ENTITLEMENTS_LIMIT_MIN`] and at most
1288/// [`GET_ENTITLEMENTS_LIMIT_MAX`]. This is based on
1289/// [this documentation entry].
1290///
1291/// # Errors
1292///
1293/// Returns an error of type [`GetEntitlements`] if the limit is invalid.
1294///
1295/// [`GetEntitlements`]: ValidationErrorType::GetEntitlements
1296/// [this documentation entry]: https://discord.com/developers/docs/monetization/entitlements#list-entitlements
1297pub const fn get_entitlements_limit(limit: u8) -> Result<(), ValidationError> {
1298    if limit >= GET_ENTITLEMENTS_LIMIT_MIN && limit <= GET_ENTITLEMENTS_LIMIT_MAX {
1299        Ok(())
1300    } else {
1301        Err(ValidationError {
1302            kind: ValidationErrorType::GetEntitlements { limit },
1303        })
1304    }
1305}
1306
1307/// Ensure that the limit for the Get Guild Audit Log endpoint is correct.
1308///
1309/// The limit must be at least [`GET_GUILD_AUDIT_LOG_LIMIT_MIN`] and at most
1310/// [`GET_GUILD_AUDIT_LOG_LIMIT_MAX`]. This is based on
1311/// [this documentation entry].
1312///
1313/// # Errors
1314///
1315/// Returns an error of type [`GetGuildAuditLog`] if the limit is invalid.
1316///
1317/// [`GetGuildAuditLog`]: ValidationErrorType::GetGuildAuditLog
1318/// [this documentation entry]: https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log
1319pub const fn get_guild_audit_log_limit(limit: u16) -> Result<(), ValidationError> {
1320    if limit >= GET_GUILD_AUDIT_LOG_LIMIT_MIN && limit <= GET_GUILD_AUDIT_LOG_LIMIT_MAX {
1321        Ok(())
1322    } else {
1323        Err(ValidationError {
1324            kind: ValidationErrorType::GetGuildAuditLog { limit },
1325        })
1326    }
1327}
1328
1329/// Ensure that the limit for the Get Guild Bans endpoint is correct.
1330///
1331/// The limit must be at most [`GET_GUILD_BANS_LIMIT_MAX`]. This is based on
1332/// [this documentation entry].
1333///
1334/// # Errors
1335///
1336/// Returns an error of type [`GetGuildBans`] if the limit is invalid.
1337///
1338/// [`GetGuildBans`]: ValidationErrorType::GetGuildBans
1339/// [this documentation entry]: https://discord.com/developers/docs/resources/guild#get-guild-bans
1340pub const fn get_guild_bans_limit(limit: u16) -> Result<(), ValidationError> {
1341    if limit <= GET_GUILD_BANS_LIMIT_MAX {
1342        Ok(())
1343    } else {
1344        Err(ValidationError {
1345            kind: ValidationErrorType::GetGuildBans { limit },
1346        })
1347    }
1348}
1349
1350/// Ensure that the limit for the Get Guild Members endpoint is correct.
1351///
1352/// The limit must be at least [`GET_GUILD_MEMBERS_LIMIT_MIN`] and at most
1353/// [`GET_GUILD_MEMBERS_LIMIT_MAX`]. This is based on
1354/// [this documentation entry].
1355///
1356/// # Errors
1357///
1358/// Returns an error of type [`GetGuildMembers`] if the limit is invalid.
1359///
1360/// [`GetGuildMembers`]: ValidationErrorType::GetGuildMembers
1361/// [this documentation entry]: https://discord.com/developers/docs/resources/guild#list-guild-members
1362pub const fn get_guild_members_limit(limit: u16) -> Result<(), ValidationError> {
1363    if limit >= GET_GUILD_MEMBERS_LIMIT_MIN && limit <= GET_GUILD_MEMBERS_LIMIT_MAX {
1364        Ok(())
1365    } else {
1366        Err(ValidationError {
1367            kind: ValidationErrorType::GetGuildMembers { limit },
1368        })
1369    }
1370}
1371
1372/// Ensure that the limit for the Get Reactions endpoint is correct.
1373///
1374/// The limit must be at least [`GET_REACTIONS_LIMIT_MIN`] and at most
1375/// [`GET_REACTIONS_LIMIT_MAX`]. This is based on [this documentation entry].
1376///
1377/// # Errors
1378///
1379/// Returns an error of type [`GetReactions`] if the limit is invalid.
1380///
1381/// [`GetReactions`]: ValidationErrorType::GetReactions
1382/// [this documentation entry]: https://discord.com/developers/docs/resources/channel#get-reactions
1383pub const fn get_reactions_limit(limit: u16) -> Result<(), ValidationError> {
1384    if limit >= GET_REACTIONS_LIMIT_MIN && limit <= GET_REACTIONS_LIMIT_MAX {
1385        Ok(())
1386    } else {
1387        Err(ValidationError {
1388            kind: ValidationErrorType::GetReactions { limit },
1389        })
1390    }
1391}
1392
1393/// Ensure that a guild name's length is correct.
1394///
1395/// The length must be at least [`GUILD_NAME_LENGTH_MIN`] and at most
1396/// [`GUILD_NAME_LENGTH_MAX`]. This is based on [this documentation entry].
1397///
1398/// # Errors
1399///
1400/// Returns an error of type [`GuildName`] if the length is invalid.
1401///
1402/// [`GuildName`]: ValidationErrorType::GuildName
1403/// [this documentation entry]: https://discord.com/developers/docs/resources/guild#guild-object
1404pub fn guild_name(name: impl AsRef<str>) -> Result<(), ValidationError> {
1405    let len = name.as_ref().chars().count();
1406
1407    if (GUILD_NAME_LENGTH_MIN..=GUILD_NAME_LENGTH_MAX).contains(&len) {
1408        Ok(())
1409    } else {
1410        Err(ValidationError {
1411            kind: ValidationErrorType::GuildName { len },
1412        })
1413    }
1414}
1415
1416/// Ensure that the days to prune members from a guild is correct.
1417///
1418/// The days must be at least [`GUILD_PRUNE_DAYS_MIN`] and at most
1419/// [`GUILD_PRUNE_DAYS_MAX`]. This is based on [this documentation entry].
1420///
1421/// # Errors
1422///
1423/// Returns an error of type [`GuildPruneDays`] if the days is invalid.
1424///
1425/// [`GuildPruneDays`]: ValidationErrorType::GuildPruneDays
1426/// [this documentation entry]: https://discord.com/developers/docs/resources/guild#get-guild-prune-count
1427pub const fn guild_prune_days(days: u16) -> Result<(), ValidationError> {
1428    if days >= GUILD_PRUNE_DAYS_MIN && days <= GUILD_PRUNE_DAYS_MAX {
1429        Ok(())
1430    } else {
1431        Err(ValidationError {
1432            kind: ValidationErrorType::GuildPruneDays { days },
1433        })
1434    }
1435}
1436
1437/// Ensure that the invite max age is correct.
1438///
1439/// The age must be at most [`INVITE_AGE_MAX`]. This is based on
1440/// [this documentation entry].
1441///
1442/// # Errors
1443///
1444/// Returns an error of type [`InviteMaxAge`] if the age is invalid.
1445///
1446/// [`InviteMaxAge`]: ValidationErrorType::InviteMaxAge
1447/// [this documentation entry]: https://discord.com/developers/docs/resources/channel#create-channel-invite
1448pub const fn invite_max_age(max_age: u32) -> Result<(), ValidationError> {
1449    if max_age <= INVITE_AGE_MAX {
1450        Ok(())
1451    } else {
1452        Err(ValidationError {
1453            kind: ValidationErrorType::InviteMaxAge { max_age },
1454        })
1455    }
1456}
1457
1458/// Ensure that the invite max uses is correct.
1459///
1460/// The age must be at most [`INVITE_USES_MAX`]. This is based on
1461/// [this documentation entry].
1462///
1463/// # Errors
1464///
1465/// Returns an error of type [`InviteMaxUses`] if the uses is invalid.
1466///
1467/// [`InviteMaxUses`]: ValidationErrorType::InviteMaxUses
1468/// [this documentation entry]: https://discord.com/developers/docs/resources/channel#create-channel-invite
1469pub const fn invite_max_uses(max_uses: u16) -> Result<(), ValidationError> {
1470    if max_uses <= INVITE_USES_MAX {
1471        Ok(())
1472    } else {
1473        Err(ValidationError {
1474            kind: ValidationErrorType::InviteMaxUses { max_uses },
1475        })
1476    }
1477}
1478
1479/// Ensure that the nickname length is correct.
1480///
1481/// The length must be at least [`NICKNAME_LIMIT_MIN`] and at most
1482/// [`NICKNAME_LIMIT_MAX`]. This is based on [this documentation entry].
1483///
1484/// # Errors
1485///
1486/// Returns an error of type [`Nickname`] if the length is invalid.
1487///
1488/// [`Nickname`]: ValidationErrorType::Nickname
1489/// [this documentation entry]: https://discord.com/developers/docs/resources/user#usernames-and-nicknames
1490pub fn nickname(nickname: impl AsRef<str>) -> Result<(), ValidationError> {
1491    let len = nickname.as_ref().chars().count();
1492
1493    if (NICKNAME_LIMIT_MIN..=NICKNAME_LIMIT_MAX).contains(&len) {
1494        Ok(())
1495    } else {
1496        Err(ValidationError {
1497            kind: ValidationErrorType::Nickname { len },
1498        })
1499    }
1500}
1501
1502/// Ensure that the pin limit is correct.
1503///
1504/// The limit must be at least [`PIN_LIMIT_MIN`] and at most
1505/// [`PIN_LIMIT_MAX`]. This is based on the documented [pin limit].
1506///
1507/// # Errors
1508///
1509/// Returns an error of type [`Pin`] if the limit is invalid.
1510///
1511/// [`Pin`]: ValidationErrorType::Pin
1512/// [pin limit]: https://discord.com/developers/docs/resources/message#get-channel-pins-query-string-params
1513pub fn pin_limit(limit: u16) -> Result<(), ValidationError> {
1514    if (PIN_LIMIT_MIN..=PIN_LIMIT_MAX).contains(&limit) {
1515        Ok(())
1516    } else {
1517        Err(ValidationError {
1518            kind: ValidationErrorType::Pin { limit },
1519        })
1520    }
1521}
1522
1523/// Ensure that a scheduled event's description is correct.
1524///
1525/// The length must be at least [`SCHEDULED_EVENT_DESCRIPTION_MIN`] and at most
1526/// [`SCHEDULED_EVENT_DESCRIPTION_MAX`]. This is based on
1527/// [this documentation entry].
1528///
1529/// # Errors
1530///
1531/// Returns an error of type [`ScheduledEventDescription`] if the length is
1532/// invalid.
1533///
1534/// [`ScheduledEventDescription`]: ValidationErrorType::ScheduledEventDescription
1535/// [this documentation entry]: https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-structure
1536pub fn scheduled_event_description(description: impl AsRef<str>) -> Result<(), ValidationError> {
1537    let len = description.as_ref().chars().count();
1538
1539    if (SCHEDULED_EVENT_DESCRIPTION_MIN..=SCHEDULED_EVENT_DESCRIPTION_MAX).contains(&len) {
1540        Ok(())
1541    } else {
1542        Err(ValidationError {
1543            kind: ValidationErrorType::ScheduledEventDescription { len },
1544        })
1545    }
1546}
1547
1548/// Ensure that a scheduled event's get users limit amount is correct.
1549///
1550/// The length must be at least [`SCHEDULED_EVENT_GET_USERS_MIN`] and at most
1551/// [`SCHEDULED_EVENT_GET_USERS_MAX`]. This is based on [this documentation
1552/// entry].
1553///
1554/// # Errors
1555///
1556/// Returns an error of type [`ScheduledEventGetUsers`] if the limit is invalid.
1557///
1558/// [`ScheduledEventGetUsers`]: ValidationErrorType::ScheduledEventGetUsers
1559/// [this documentation entry]: https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event-users-query-string-params
1560pub const fn scheduled_event_get_users(limit: u16) -> Result<(), ValidationError> {
1561    if limit >= SCHEDULED_EVENT_GET_USERS_MIN && limit <= SCHEDULED_EVENT_GET_USERS_MAX {
1562        Ok(())
1563    } else {
1564        Err(ValidationError {
1565            kind: ValidationErrorType::ScheduledEventGetUsers { limit },
1566        })
1567    }
1568}
1569
1570/// Ensure that a scheduled event's name is correct.
1571///
1572/// The length must be at least [`SCHEDULED_EVENT_NAME_MIN`] and at most
1573/// [`SCHEDULED_EVENT_NAME_MAX`]. This is based on [this documentation entry].
1574///
1575/// # Errors
1576///
1577/// Returns an error of type [`ScheduledEventName`] if the length is invalid.
1578///
1579/// [`ScheduledEventName`]: ValidationErrorType::ScheduledEventName
1580/// [this documentation entry]: https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-structure
1581pub fn scheduled_event_name(name: impl AsRef<str>) -> Result<(), ValidationError> {
1582    let len = name.as_ref().chars().count();
1583
1584    if (SCHEDULED_EVENT_NAME_MIN..=SCHEDULED_EVENT_NAME_MAX).contains(&len) {
1585        Ok(())
1586    } else {
1587        Err(ValidationError {
1588            kind: ValidationErrorType::ScheduledEventName { len },
1589        })
1590    }
1591}
1592
1593/// Ensure that the limit for the Search Guild Members endpoint is correct.
1594///
1595/// The limit must be at least [`SEARCH_GUILD_MEMBERS_LIMIT_MIN`] and at most
1596/// [`SEARCH_GUILD_MEMBERS_LIMIT_MAX`]. This is based on
1597/// [this documentation entry].
1598///
1599/// # Errors
1600///
1601/// Returns an error of type [`SearchGuildMembers`] if the limit is invalid.
1602///
1603/// [`SearchGuildMembers`]: ValidationErrorType::SearchGuildMembers
1604/// [this documentation entry]: https://discord.com/developers/docs/resources/guild#search-guild-members
1605pub const fn search_guild_members_limit(limit: u16) -> Result<(), ValidationError> {
1606    if limit >= SEARCH_GUILD_MEMBERS_LIMIT_MIN && limit <= SEARCH_GUILD_MEMBERS_LIMIT_MAX {
1607        Ok(())
1608    } else {
1609        Err(ValidationError {
1610            kind: ValidationErrorType::SearchGuildMembers { limit },
1611        })
1612    }
1613}
1614
1615/// Ensure that the stage instance's topic length is correct.
1616///
1617/// The length must be at least [`STAGE_TOPIC_LENGTH_MIN`] and at most
1618/// [`STAGE_TOPIC_LENGTH_MAX`]. This is based on [this documentation entry].
1619///
1620/// # Errors
1621///
1622/// Returns an error of type [`StageTopic`] if the length is invalid.
1623///
1624/// [`StageTopic`]: ValidationErrorType::StageTopic
1625/// [this documentation entry]: https://discord.com/developers/docs/resources/stage-instance#stage-instance-object
1626pub fn stage_topic(topic: impl AsRef<str>) -> Result<(), ValidationError> {
1627    let len = topic.as_ref().chars().count();
1628
1629    if (STAGE_TOPIC_LENGTH_MIN..=STAGE_TOPIC_LENGTH_MAX).contains(&len) {
1630        Ok(())
1631    } else {
1632        Err(ValidationError {
1633            kind: ValidationErrorType::StageTopic { len },
1634        })
1635    }
1636}
1637
1638/// Ensure that a guild template's description length is correct.
1639///
1640/// The length must be at most [`TEMPLATE_DESCRIPTION_LENGTH_MAX`]. This is
1641/// based on [this documentation entry].
1642///
1643/// # Errors
1644///
1645/// Returns an error of type [`TemplateDescription`] if the length is invalid.
1646///
1647/// [`TemplateDescription`]: ValidationErrorType::TemplateDescription
1648/// [this documentation entry]: https://discord.com/developers/docs/resources/guild-template#guild-template-object-guild-template-structure
1649pub fn template_description(description: impl AsRef<str>) -> Result<(), ValidationError> {
1650    let len = description.as_ref().chars().count();
1651
1652    if len <= TEMPLATE_DESCRIPTION_LENGTH_MAX {
1653        Ok(())
1654    } else {
1655        Err(ValidationError {
1656            kind: ValidationErrorType::TemplateDescription { len },
1657        })
1658    }
1659}
1660
1661/// Ensure that a guild template's name length is correct.
1662///
1663/// The length must be at least [`TEMPLATE_NAME_LENGTH_MIN`] and at most
1664/// [`TEMPLATE_NAME_LENGTH_MAX`]. This is based on [this documentation entry].
1665///
1666/// # Errors
1667///
1668/// Returns an error of type [`TemplateName`] if the length is invalid.
1669///
1670/// [`TemplateName`]: ValidationErrorType::TemplateName
1671/// [this documentation entry]: https://discord.com/developers/docs/resources/guild-template#guild-template-object-guild-template-structure
1672pub fn template_name(name: impl AsRef<str>) -> Result<(), ValidationError> {
1673    let len = name.as_ref().chars().count();
1674
1675    if (TEMPLATE_NAME_LENGTH_MIN..=TEMPLATE_NAME_LENGTH_MAX).contains(&len) {
1676        Ok(())
1677    } else {
1678        Err(ValidationError {
1679            kind: ValidationErrorType::TemplateName { len },
1680        })
1681    }
1682}
1683
1684/// Ensure that a username is correct.
1685///
1686/// The length must be at least [`USERNAME_LIMIT_MIN`] and at most
1687/// [`USERNAME_LIMIT_MAX`]. It must also be free of certain substrings. This is
1688/// based on [this documentation entry].
1689///
1690/// # Errors
1691///
1692/// Returns an error of type [`Username`] if the length is invalid.
1693///
1694/// [`Username`]: ValidationErrorType::Username
1695/// [this documentation entry]: https://discord.com/developers/docs/resources/user#usernames-and-nicknames
1696pub fn username(value: impl AsRef<str>) -> Result<(), ValidationError> {
1697    let value = value.as_ref();
1698    let len = value.chars().count();
1699
1700    let range = USERNAME_LIMIT_MIN..=USERNAME_LIMIT_MAX;
1701    let invalid_len = (!range.contains(&len)).then_some(len);
1702
1703    let invalid_substring = USERNAME_INVALID_SUBSTRINGS
1704        .into_iter()
1705        .find(|invalid_substring| value.contains(invalid_substring))
1706        .or_else(|| {
1707            USERNAME_INVALID_STRINGS
1708                .into_iter()
1709                .find(|invalid_string| value == *invalid_string)
1710        });
1711
1712    if invalid_len.is_none() && invalid_substring.is_none() {
1713        Ok(())
1714    } else {
1715        Err(ValidationError {
1716            kind: ValidationErrorType::Username {
1717                len: invalid_len,
1718                substring: invalid_substring,
1719            },
1720        })
1721    }
1722}
1723
1724/// Ensure that a webhook is correct.
1725///
1726/// The length must be at least [`WEBHOOK_USERNAME_LIMIT_MIN`] and at most
1727/// [`WEBHOOK_USERNAME_LIMIT_MAX`]. It must also be free of certain substrings.
1728/// This is based on [this documentation entry].
1729///
1730/// # Errors
1731///
1732/// Returns an error of type [`WebhookUsername`] if the length is invalid.
1733///
1734/// [`WebhookUsername`]: ValidationErrorType::WebhookUsername
1735/// [this documentation entry]: https://discord.com/developers/docs/resources/webhook#create-webhook
1736pub fn webhook_username(value: impl AsRef<str>) -> Result<(), ValidationError> {
1737    let value = value.as_ref();
1738    let len = value.chars().count();
1739
1740    let range = WEBHOOK_USERNAME_LIMIT_MIN..=WEBHOOK_USERNAME_LIMIT_MAX;
1741    let invalid_len = (!range.contains(&len)).then_some(len);
1742
1743    let invalid_substring = WEBHOOK_INVALID_STRINGS
1744        .into_iter()
1745        .find(|invalid_string| value == *invalid_string);
1746
1747    if invalid_len.is_none() && invalid_substring.is_none() {
1748        Ok(())
1749    } else {
1750        Err(ValidationError {
1751            kind: ValidationErrorType::WebhookUsername {
1752                len: invalid_len,
1753                substring: invalid_substring,
1754            },
1755        })
1756    }
1757}
1758
1759#[cfg(test)]
1760mod tests {
1761    use super::*;
1762
1763    #[test]
1764    fn username_variants() {
1765        let expected = format!(
1766            "provided username length is 200, but it must be at least {USERNAME_LIMIT_MIN} and at \
1767            most {USERNAME_LIMIT_MAX}, and cannot contain :"
1768        );
1769        let actual = ValidationError {
1770            kind: ValidationErrorType::Username {
1771                len: Some(200),
1772                substring: Some(":"),
1773            },
1774        };
1775        assert_eq!(expected, actual.to_string());
1776
1777        let expected = format!(
1778            "provided username length is 200, but it must be at least {USERNAME_LIMIT_MIN} and at \
1779            most {USERNAME_LIMIT_MAX}",
1780        );
1781        let actual = ValidationError {
1782            kind: ValidationErrorType::Username {
1783                len: Some(200),
1784                substring: None,
1785            },
1786        };
1787        assert_eq!(expected, actual.to_string());
1788
1789        let expected = "provided username cannot contain :".to_string();
1790        let actual = ValidationError {
1791            kind: ValidationErrorType::Username {
1792                len: None,
1793                substring: Some(":"),
1794            },
1795        };
1796        assert_eq!(expected, actual.to_string());
1797    }
1798
1799    #[test]
1800    fn audit_reason_length() {
1801        assert!(audit_reason("").is_ok());
1802        assert!(audit_reason("a").is_ok());
1803        assert!(audit_reason("a".repeat(500)).is_ok());
1804        assert!(audit_reason("a".repeat(512)).is_ok());
1805
1806        assert!(audit_reason("a".repeat(513)).is_err());
1807    }
1808
1809    #[test]
1810    fn auto_moderation_block_action_custom_message() {
1811        assert!(auto_moderation_block_action_custom_message_limit("").is_ok());
1812        assert!(auto_moderation_block_action_custom_message_limit("a".repeat(150)).is_ok());
1813        assert!(matches!(
1814            auto_moderation_block_action_custom_message_limit("a".repeat(151))
1815                .unwrap_err()
1816                .kind,
1817            ValidationErrorType::AutoModerationBlockActionCustomMessageLimit { len: 151 }
1818        ));
1819    }
1820
1821    #[test]
1822    fn auto_moderation_metadata_mention_total() {
1823        assert!(auto_moderation_metadata_mention_total_limit(0).is_ok());
1824        assert!(auto_moderation_metadata_mention_total_limit(1).is_ok());
1825        assert!(auto_moderation_metadata_mention_total_limit(50).is_ok());
1826
1827        assert!(auto_moderation_metadata_mention_total_limit(51).is_err());
1828    }
1829
1830    #[test]
1831    fn auto_moderation_metadata_keyword_filter_max() {
1832        let mut keywords = (0..1000).map(|_| "a").collect::<Vec<_>>();
1833
1834        assert!(auto_moderation_metadata_keyword_filter(&[] as &[&str]).is_ok());
1835        assert!(auto_moderation_metadata_keyword_filter(&["a".repeat(60)]).is_ok());
1836        assert!(auto_moderation_metadata_keyword_filter(&keywords).is_ok());
1837
1838        keywords.push("a");
1839
1840        assert!(auto_moderation_metadata_keyword_filter(&["a".repeat(61)]).is_err());
1841        assert!(auto_moderation_metadata_keyword_filter(&keywords).is_err());
1842    }
1843
1844    #[test]
1845    fn auto_moderation_metadata_keyword_allow_list_max() {
1846        let mut allow_list = (0..100).map(|_| "a").collect::<Vec<_>>();
1847
1848        assert!(auto_moderation_metadata_keyword_allow_list(&[] as &[&str]).is_ok());
1849        assert!(auto_moderation_metadata_keyword_allow_list(&["a".repeat(60)]).is_ok());
1850
1851        allow_list.push("a");
1852
1853        assert!(auto_moderation_metadata_keyword_allow_list(&["a".repeat(61)]).is_err());
1854        assert!(auto_moderation_metadata_keyword_allow_list(&allow_list).is_err());
1855    }
1856
1857    #[test]
1858    fn auto_moderation_metadata_preset_allow_list_max() {
1859        let mut allow_list = (0..1000).map(|_| "a").collect::<Vec<_>>();
1860
1861        assert!(auto_moderation_metadata_preset_allow_list(&[] as &[&str]).is_ok());
1862        assert!(auto_moderation_metadata_preset_allow_list(&["a".repeat(60)]).is_ok());
1863
1864        allow_list.push("a");
1865
1866        assert!(auto_moderation_metadata_preset_allow_list(&["a".repeat(61)]).is_err());
1867        assert!(auto_moderation_metadata_preset_allow_list(&allow_list).is_err());
1868    }
1869
1870    #[test]
1871    fn auto_moderation_metadata_regex_patterns_max() {
1872        let mut patterns = (0..10).map(|_| "a").collect::<Vec<_>>();
1873
1874        assert!(auto_moderation_metadata_regex_patterns(&[] as &[&str]).is_ok());
1875        assert!(auto_moderation_metadata_regex_patterns(&["a".repeat(260)]).is_ok());
1876
1877        patterns.push("a");
1878
1879        assert!(auto_moderation_metadata_regex_patterns(&["a".repeat(261)]).is_err());
1880        assert!(auto_moderation_metadata_regex_patterns(&patterns).is_err());
1881    }
1882
1883    #[test]
1884    fn auto_moderation_exempt_roles_max() {
1885        let mut roles = (1..=20).map(Id::new).collect::<Vec<_>>();
1886
1887        assert!(auto_moderation_exempt_roles(&[]).is_ok());
1888        assert!(auto_moderation_exempt_roles(&roles).is_ok());
1889
1890        roles.push(Id::new(21));
1891
1892        assert!(auto_moderation_exempt_roles(&roles).is_err());
1893    }
1894
1895    #[test]
1896    fn auto_moderation_exempt_channels_max() {
1897        let mut channels = (1..=50).map(Id::new).collect::<Vec<_>>();
1898
1899        assert!(auto_moderation_exempt_channels(&[]).is_ok());
1900        assert!(auto_moderation_exempt_channels(&channels).is_ok());
1901
1902        channels.push(Id::new(51));
1903
1904        assert!(auto_moderation_exempt_channels(&channels).is_err());
1905    }
1906
1907    #[test]
1908    fn auto_moderation_action_metadata_duration_seconds_max() {
1909        assert!(auto_moderation_action_metadata_duration_seconds(0).is_ok());
1910        assert!(auto_moderation_action_metadata_duration_seconds(1).is_ok());
1911        assert!(auto_moderation_action_metadata_duration_seconds(2_419_200).is_ok());
1912
1913        assert!(auto_moderation_action_metadata_duration_seconds(2_419_201).is_err());
1914    }
1915
1916    #[test]
1917    fn bio_length() {
1918        assert!(bio("a").is_ok());
1919        assert!(bio("a".repeat(400)).is_ok());
1920
1921        assert!(bio("").is_err());
1922        assert!(bio("b".repeat(401)).is_err());
1923    }
1924
1925    #[test]
1926    fn create_guild_ban_delete_message_seconds_max() {
1927        assert!(create_guild_ban_delete_message_seconds(0).is_ok());
1928        assert!(create_guild_ban_delete_message_seconds(1).is_ok());
1929        assert!(create_guild_ban_delete_message_seconds(604_800).is_ok());
1930
1931        assert!(create_guild_ban_delete_message_seconds(604_801).is_err());
1932    }
1933
1934    #[test]
1935    fn communication_disabled_until_max() {
1936        #[allow(clippy::cast_possible_wrap)]
1937        let now = SystemTime::now()
1938            .duration_since(UNIX_EPOCH)
1939            .unwrap()
1940            .as_secs() as i64;
1941
1942        let ok_timestamp =
1943            Timestamp::from_secs(now + COMMUNICATION_DISABLED_MAX_DURATION - 1000).unwrap();
1944        assert!(communication_disabled_until(ok_timestamp).is_ok());
1945
1946        let err_timestamp =
1947            Timestamp::from_secs(now + COMMUNICATION_DISABLED_MAX_DURATION + 1000).unwrap();
1948        assert!(communication_disabled_until(err_timestamp).is_err());
1949    }
1950
1951    #[test]
1952    fn get_channel_messages_limit_count() {
1953        assert!(get_channel_messages_limit(1).is_ok());
1954        assert!(get_channel_messages_limit(100).is_ok());
1955
1956        assert!(get_channel_messages_limit(0).is_err());
1957        assert!(get_channel_messages_limit(101).is_err());
1958    }
1959
1960    #[test]
1961    fn get_current_user_guilds_limit_count() {
1962        assert!(get_current_user_guilds_limit(1).is_ok());
1963        assert!(get_current_user_guilds_limit(200).is_ok());
1964
1965        assert!(get_current_user_guilds_limit(0).is_err());
1966        assert!(get_current_user_guilds_limit(201).is_err());
1967    }
1968
1969    #[test]
1970    fn get_guild_log_limit_count() {
1971        assert!(get_guild_audit_log_limit(1).is_ok());
1972        assert!(get_guild_audit_log_limit(100).is_ok());
1973
1974        assert!(get_guild_audit_log_limit(0).is_err());
1975        assert!(get_guild_audit_log_limit(101).is_err());
1976    }
1977
1978    #[test]
1979    fn get_guild_bans_limit_count() {
1980        assert!(get_guild_bans_limit(0).is_ok());
1981        assert!(get_guild_bans_limit(1000).is_ok());
1982
1983        assert!(get_guild_bans_limit(1001).is_err());
1984    }
1985
1986    #[test]
1987    fn get_guild_members_limit_count() {
1988        assert!(get_guild_members_limit(1).is_ok());
1989        assert!(get_guild_members_limit(1000).is_ok());
1990
1991        assert!(get_guild_members_limit(0).is_err());
1992        assert!(get_guild_members_limit(1001).is_err());
1993    }
1994
1995    #[test]
1996    fn get_reactions_limit_count() {
1997        assert!(get_reactions_limit(1).is_ok());
1998        assert!(get_reactions_limit(100).is_ok());
1999
2000        assert!(get_reactions_limit(0).is_err());
2001        assert!(get_reactions_limit(101).is_err());
2002    }
2003
2004    #[test]
2005    fn guild_name_length() {
2006        assert!(guild_name("aa").is_ok());
2007        assert!(guild_name("a".repeat(100)).is_ok());
2008
2009        assert!(guild_name("").is_err());
2010        assert!(guild_name("a").is_err());
2011        assert!(guild_name("a".repeat(101)).is_err());
2012    }
2013
2014    #[test]
2015    fn guild_prune_days_length() {
2016        assert!(guild_prune_days(1).is_ok());
2017        assert!(guild_prune_days(30).is_ok());
2018
2019        assert!(guild_prune_days(0).is_err());
2020        assert!(guild_prune_days(31).is_err());
2021        assert!(guild_prune_days(100).is_err());
2022    }
2023
2024    #[test]
2025    fn invite_max_age_length() {
2026        assert!(invite_max_age(0).is_ok());
2027        assert!(invite_max_age(86_400).is_ok());
2028        assert!(invite_max_age(604_800).is_ok());
2029
2030        assert!(invite_max_age(604_801).is_err());
2031    }
2032
2033    #[test]
2034    fn invite_max_uses_count() {
2035        assert!(invite_max_uses(0).is_ok());
2036        assert!(invite_max_uses(100).is_ok());
2037
2038        assert!(invite_max_uses(101).is_err());
2039    }
2040
2041    #[test]
2042    fn nickname_length() {
2043        assert!(nickname("a").is_ok());
2044        assert!(nickname("a".repeat(32)).is_ok());
2045
2046        assert!(nickname("").is_err());
2047        assert!(nickname("a".repeat(33)).is_err());
2048    }
2049
2050    #[test]
2051    fn pin_limit_test() {
2052        assert!(pin_limit(1).is_ok());
2053        assert!(pin_limit(50).is_ok());
2054
2055        assert!(pin_limit(0).is_err());
2056        assert!(pin_limit(51).is_err());
2057    }
2058
2059    #[test]
2060    fn scheduled_event_description_length() {
2061        assert!(scheduled_event_description("a").is_ok());
2062        assert!(scheduled_event_description("a".repeat(1000)).is_ok());
2063
2064        assert!(scheduled_event_description("").is_err());
2065        assert!(scheduled_event_description("a".repeat(1001)).is_err());
2066    }
2067
2068    #[test]
2069    fn scheduled_event_get_users_length() {
2070        assert!(scheduled_event_get_users(0).is_err());
2071        assert!(scheduled_event_get_users(101).is_err());
2072        assert!(scheduled_event_get_users(100).is_ok());
2073        assert!(scheduled_event_get_users(1).is_ok());
2074    }
2075
2076    #[test]
2077    fn scheduled_event_name_length() {
2078        assert!(scheduled_event_name("a").is_ok());
2079        assert!(scheduled_event_name("a".repeat(100)).is_ok());
2080
2081        assert!(scheduled_event_name("").is_err());
2082        assert!(scheduled_event_name("a".repeat(101)).is_err());
2083    }
2084
2085    #[test]
2086    fn search_guild_members_limit_count() {
2087        assert!(search_guild_members_limit(1).is_ok());
2088        assert!(search_guild_members_limit(1000).is_ok());
2089
2090        assert!(search_guild_members_limit(0).is_err());
2091        assert!(search_guild_members_limit(1001).is_err());
2092    }
2093
2094    #[test]
2095    fn stage_topic_length() {
2096        assert!(stage_topic("a").is_ok());
2097        assert!(stage_topic("a".repeat(120)).is_ok());
2098
2099        assert!(stage_topic("").is_err());
2100        assert!(stage_topic("a".repeat(121)).is_err());
2101    }
2102
2103    #[test]
2104    fn template_description_length() {
2105        assert!(template_description("").is_ok());
2106        assert!(template_description("a").is_ok());
2107        assert!(template_description("a".repeat(120)).is_ok());
2108
2109        assert!(template_description("a".repeat(121)).is_err());
2110    }
2111
2112    #[test]
2113    fn template_name_length() {
2114        assert!(template_name("a").is_ok());
2115        assert!(template_name("a".repeat(100)).is_ok());
2116
2117        assert!(template_name("").is_err());
2118        assert!(template_name("a".repeat(101)).is_err());
2119    }
2120
2121    #[test]
2122    fn username_length() {
2123        assert!(username("aa").is_ok());
2124        assert!(username("a".repeat(32)).is_ok());
2125
2126        assert!(username("a").is_err());
2127        assert!(username("a".repeat(33)).is_err());
2128
2129        assert!(username("no @ in username").is_err());
2130        assert!(username("no # in username").is_err());
2131        assert!(username("no : in username").is_err());
2132        assert!(username(r"no ``` in username").is_err());
2133        assert!(username("no discord in username").is_err());
2134        assert!(username("everyone").is_err());
2135        assert!(username("here").is_err());
2136    }
2137
2138    #[test]
2139    fn webhook_username_length() {
2140        assert!(webhook_username("aa").is_ok());
2141        assert!(webhook_username("a".repeat(80)).is_ok());
2142
2143        assert!(webhook_username("a").is_err());
2144        assert!(webhook_username("a".repeat(81)).is_err());
2145
2146        assert!(webhook_username("clyde").is_err());
2147    }
2148}