#[cfg(all(feature = "model", feature = "utils"))]
use std::error::Error as StdError;
use std::fmt;
#[cfg(all(feature = "model", feature = "utils"))]
use std::result::Result as StdResult;
use std::str::FromStr;
use super::prelude::*;
#[cfg(all(feature = "model", any(feature = "cache", feature = "utils")))]
use crate::utils;
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Copy, PartialEq, Eq)]
enum ImageHashInner {
Normal { hash: [u8; 16], is_animated: bool },
Clyde,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ImageHash(ImageHashInner);
impl ImageHash {
#[must_use]
pub fn is_animated(&self) -> bool {
match &self.0 {
ImageHashInner::Normal {
is_animated, ..
} => *is_animated,
ImageHashInner::Clyde => true,
}
}
}
impl std::fmt::Debug for ImageHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("\"")?;
<Self as std::fmt::Display>::fmt(self, f)?;
f.write_str("\"")
}
}
impl serde::Serialize for ImageHash {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.to_string().serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for ImageHash {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let helper = arrayvec::ArrayString::<34>::deserialize(deserializer)?;
Self::from_str(&helper).map_err(serde::de::Error::custom)
}
}
impl std::fmt::Display for ImageHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ImageHashInner::Normal {
hash,
is_animated,
} = &self.0
else {
return f.write_str("clyde");
};
if *is_animated {
f.write_str("a_")?;
}
for byte in hash {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum ImageHashParseError {
InvalidLength(usize),
}
impl std::error::Error for ImageHashParseError {}
impl std::fmt::Display for ImageHashParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength(length) => {
write!(f, "Invalid length {length}, expected 32 or 34 characters")
},
}
}
}
impl std::str::FromStr for ImageHash {
type Err = ImageHashParseError;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
let (hex, is_animated) = if s.len() == 34 && s.starts_with("a_") {
(&s[2..], true)
} else if s.len() == 32 {
(s, false)
} else if s == "clyde" {
return Ok(Self(ImageHashInner::Clyde));
} else {
return Err(Self::Err::InvalidLength(s.len()));
};
let mut hash = [0u8; 16];
for i in (0..hex.len()).step_by(2) {
let hex_byte = &hex[i..i + 2];
hash[i / 2] = u8::from_str_radix(hex_byte, 16).unwrap_or_else(|err| {
tracing::warn!("Invalid byte in ImageHash ({s}): {err}");
0
});
}
Ok(Self(ImageHashInner::Normal {
is_animated,
hash,
}))
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub struct EmojiIdentifier {
pub animated: bool,
pub id: EmojiId,
pub name: String,
}
#[cfg(all(feature = "model", feature = "utils"))]
impl EmojiIdentifier {
#[must_use]
pub fn url(&self) -> String {
let ext = if self.animated { "gif" } else { "png" };
cdn!("/emojis/{}.{}", self.id, ext)
}
}
#[derive(Debug)]
#[cfg(all(feature = "model", feature = "utils"))]
pub struct EmojiIdentifierParseError {
parsed_string: String,
}
#[cfg(all(feature = "model", feature = "utils"))]
impl fmt::Display for EmojiIdentifierParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "`{}` is not a valid emoji identifier", self.parsed_string)
}
}
#[cfg(all(feature = "model", feature = "utils"))]
impl StdError for EmojiIdentifierParseError {}
#[cfg(all(feature = "model", feature = "utils"))]
impl FromStr for EmojiIdentifier {
type Err = EmojiIdentifierParseError;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
utils::parse_emoji(s).ok_or_else(|| EmojiIdentifierParseError {
parsed_string: s.to_owned(),
})
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Incident {
pub created_at: String,
pub id: String,
pub impact: String,
pub incident_updates: Vec<IncidentUpdate>,
pub monitoring_at: Option<String>,
pub name: String,
pub page_id: String,
pub resolved_at: Option<String>,
pub shortlink: String,
pub status: String,
pub updated_at: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct IncidentUpdate {
pub body: String,
pub created_at: String,
pub display_at: String,
pub id: String,
pub incident_id: String,
pub status: String,
pub updated_at: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Maintenance {
pub created_at: String,
pub id: String,
pub impact: String,
pub incident_updates: Vec<IncidentUpdate>,
pub monitoring_at: Option<String>,
pub name: String,
pub page_id: String,
pub resolved_at: Option<String>,
pub scheduled_for: String,
pub scheduled_until: String,
pub shortlink: String,
pub status: String,
pub updated_at: String,
}
#[cfg(test)]
mod test {
use crate::model::prelude::*;
#[test]
fn test_formatters() {
assert_eq!(ChannelId::new(1).to_string(), "1");
assert_eq!(EmojiId::new(2).to_string(), "2");
assert_eq!(GuildId::new(3).to_string(), "3");
assert_eq!(RoleId::new(4).to_string(), "4");
assert_eq!(UserId::new(5).to_string(), "5");
}
}