Skip to main content

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