use std::{
error::Error,
fmt::{Display, Formatter, Result as FmtResult},
};
use twilight_model::channel::ChannelType;
pub const CHANNEL_BITRATE_MIN: u32 = 8000;
pub const CHANNEL_BULK_DELETE_MESSAGES_MAX: usize = 100;
pub const CHANNEL_BULK_DELETE_MESSAGES_MIN: usize = 2;
pub const CHANNEL_FORUM_TOPIC_LENGTH_MAX: usize = 4096;
pub const CHANNEL_NAME_LENGTH_MAX: usize = 100;
pub const CHANNEL_NAME_LENGTH_MIN: usize = 1;
pub const CHANNEL_RATE_LIMIT_PER_USER_MAX: u16 = 21_600;
pub const CHANNEL_THREAD_GET_MEMBERS_LIMIT_MAX: u32 = 100;
pub const CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN: u32 = 1;
pub const CHANNEL_TOPIC_LENGTH_MAX: usize = 1024;
pub const CHANNEL_USER_LIMIT_MAX: u16 = 99;
#[derive(Debug)]
pub struct ChannelValidationError {
kind: ChannelValidationErrorType,
}
impl ChannelValidationError {
#[must_use = "retrieving the type has no effect if left unused"]
pub const fn kind(&self) -> &ChannelValidationErrorType {
&self.kind
}
#[allow(clippy::unused_self)]
#[must_use = "consuming the error and retrieving the source has no effect if left unused"]
pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
None
}
#[must_use = "consuming the error into its parts has no effect if left unused"]
pub fn into_parts(
self,
) -> (
ChannelValidationErrorType,
Option<Box<dyn Error + Send + Sync>>,
) {
(self.kind, None)
}
}
impl Display for ChannelValidationError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match &self.kind {
ChannelValidationErrorType::BitrateInvalid => {
f.write_str("bitrate is less than ")?;
Display::fmt(&CHANNEL_BITRATE_MIN, f)
}
ChannelValidationErrorType::BulkDeleteMessagesInvalid => {
f.write_str("number of messages deleted in bulk is less than ")?;
Display::fmt(&CHANNEL_BULK_DELETE_MESSAGES_MIN, f)?;
f.write_str(" or greater than ")?;
Display::fmt(&CHANNEL_BULK_DELETE_MESSAGES_MAX, f)
}
ChannelValidationErrorType::ForumTopicInvalid => {
f.write_str("the forum topic is invalid")
}
ChannelValidationErrorType::NameInvalid => {
f.write_str("the length of the name is invalid")
}
ChannelValidationErrorType::RateLimitPerUserInvalid { .. } => {
f.write_str("the rate limit per user is invalid")
}
ChannelValidationErrorType::ThreadMemberLimitInvalid => {
f.write_str("number of members to return is less than ")?;
Display::fmt(&CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN, f)?;
f.write_str(" or greater than ")?;
Display::fmt(&CHANNEL_THREAD_GET_MEMBERS_LIMIT_MAX, f)
}
ChannelValidationErrorType::TopicInvalid => f.write_str("the topic is invalid"),
ChannelValidationErrorType::TypeInvalid { kind } => {
Display::fmt(kind.name(), f)?;
f.write_str(" is not a thread")
}
ChannelValidationErrorType::UserLimitInvalid => {
f.write_str("user limit is greater than ")?;
Display::fmt(&CHANNEL_USER_LIMIT_MAX, f)
}
}
}
}
impl Error for ChannelValidationError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum ChannelValidationErrorType {
BitrateInvalid,
BulkDeleteMessagesInvalid,
ForumTopicInvalid,
NameInvalid,
RateLimitPerUserInvalid {
rate_limit_per_user: u16,
},
ThreadMemberLimitInvalid,
TopicInvalid,
TypeInvalid {
kind: ChannelType,
},
UserLimitInvalid,
}
pub const fn bitrate(value: u32) -> Result<(), ChannelValidationError> {
if value >= CHANNEL_BITRATE_MIN {
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::BitrateInvalid,
})
}
}
pub const fn bulk_delete_messages(message_count: usize) -> Result<(), ChannelValidationError> {
if message_count >= CHANNEL_BULK_DELETE_MESSAGES_MIN
&& message_count <= CHANNEL_BULK_DELETE_MESSAGES_MAX
{
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::BulkDeleteMessagesInvalid,
})
}
}
pub const fn is_thread(kind: ChannelType) -> Result<(), ChannelValidationError> {
if matches!(
kind,
ChannelType::AnnouncementThread | ChannelType::PublicThread | ChannelType::PrivateThread
) {
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::TypeInvalid { kind },
})
}
}
pub fn forum_topic(value: impl AsRef<str>) -> Result<(), ChannelValidationError> {
let count = value.as_ref().chars().count();
if count <= CHANNEL_FORUM_TOPIC_LENGTH_MAX {
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::TopicInvalid,
})
}
}
pub fn name(value: impl AsRef<str>) -> Result<(), ChannelValidationError> {
let len = value.as_ref().chars().count();
if (CHANNEL_NAME_LENGTH_MIN..=CHANNEL_NAME_LENGTH_MAX).contains(&len) {
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::NameInvalid,
})
}
}
pub const fn rate_limit_per_user(value: u16) -> Result<(), ChannelValidationError> {
if value <= CHANNEL_RATE_LIMIT_PER_USER_MAX {
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::RateLimitPerUserInvalid {
rate_limit_per_user: value,
},
})
}
}
pub const fn thread_member_limit(value: u32) -> Result<(), ChannelValidationError> {
if value >= CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN
&& value <= CHANNEL_THREAD_GET_MEMBERS_LIMIT_MAX
{
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::ThreadMemberLimitInvalid,
})
}
}
pub fn topic(value: impl AsRef<str>) -> Result<(), ChannelValidationError> {
let count = value.as_ref().chars().count();
if count <= CHANNEL_TOPIC_LENGTH_MAX {
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::TopicInvalid,
})
}
}
pub const fn user_limit(value: u16) -> Result<(), ChannelValidationError> {
if value <= CHANNEL_USER_LIMIT_MAX {
Ok(())
} else {
Err(ChannelValidationError {
kind: ChannelValidationErrorType::UserLimitInvalid,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bulk_delete_messages() {
assert!(matches!(
super::bulk_delete_messages(0).unwrap_err().kind(),
ChannelValidationErrorType::BulkDeleteMessagesInvalid,
));
assert!(matches!(
super::bulk_delete_messages(1).unwrap_err().kind(),
ChannelValidationErrorType::BulkDeleteMessagesInvalid,
));
assert!(super::bulk_delete_messages(100).is_ok());
assert!(matches!(
super::bulk_delete_messages(101).unwrap_err().kind(),
ChannelValidationErrorType::BulkDeleteMessagesInvalid,
));
}
#[test]
fn channel_bitrate() {
assert!(bitrate(8000).is_ok());
assert!(bitrate(7000).is_err());
}
#[test]
fn thread_is_thread() {
assert!(is_thread(ChannelType::AnnouncementThread).is_ok());
assert!(is_thread(ChannelType::PrivateThread).is_ok());
assert!(is_thread(ChannelType::PublicThread).is_ok());
assert!(is_thread(ChannelType::Group).is_err());
}
#[test]
fn channel_name() {
assert!(name("a").is_ok());
assert!(name("a".repeat(100)).is_ok());
assert!(name("").is_err());
assert!(name("a".repeat(101)).is_err());
}
#[test]
fn rate_limit_per_user_value() {
assert!(rate_limit_per_user(0).is_ok());
assert!(rate_limit_per_user(21_600).is_ok());
assert!(rate_limit_per_user(21_601).is_err());
}
#[test]
fn thread_member_limit_value() {
assert!(thread_member_limit(1).is_ok());
assert!(thread_member_limit(100).is_ok());
assert!(thread_member_limit(50).is_ok());
assert!(thread_member_limit(0).is_err());
assert!(thread_member_limit(101).is_err());
}
#[test]
fn topic_length() {
assert!(topic("").is_ok());
assert!(topic("a").is_ok());
assert!(topic("a".repeat(1_024)).is_ok());
assert!(topic("a".repeat(1_025)).is_err());
}
#[test]
fn user_limit() {
assert!(super::user_limit(0).is_ok());
assert!(super::user_limit(99).is_ok());
assert!(matches!(
super::user_limit(100).unwrap_err().kind(),
ChannelValidationErrorType::UserLimitInvalid
));
}
}