use std::fmt;
use std::str::FromStr;
use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
macro_rules! snowflake {
($Name:ident, $kind:literal) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[allow(dead_code)]
pub struct $Name(pub u64);
impl $Name {
#[allow(dead_code)]
pub fn as_u64(self) -> u64 {
self.0
}
}
impl fmt::Display for $Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for $Name {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
if s.is_empty() {
return Err(anyhow!("empty {} snowflake", $kind));
}
let n: u64 = s
.parse()
.with_context(|| format!("invalid {} snowflake: {:?}", $kind, s))?;
Ok($Name(n))
}
}
impl Serialize for $Name {
fn serialize<S: Serializer>(&self, ser: S) -> std::result::Result<S::Ok, S::Error> {
ser.collect_str(&self.0)
}
}
impl<'de> Deserialize<'de> for $Name {
fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
let s = String::deserialize(de)?;
s.parse().map_err(serde::de::Error::custom)
}
}
};
}
snowflake!(GuildId, "guild");
snowflake!(ChannelId, "channel");
snowflake!(UserId, "user");
snowflake!(MessageId, "message");
snowflake!(RoleId, "role");
snowflake!(EmojiId, "emoji");
snowflake!(StickerId, "sticker");
snowflake!(ThreadId, "thread");
snowflake!(AttachmentId, "attachment");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_valid() {
let g: GuildId = "123456789012345678".parse().unwrap();
assert_eq!(g.as_u64(), 123456789012345678u64);
assert_eq!(g.to_string(), "123456789012345678");
}
#[test]
fn rejects_garbage() {
assert!("not-a-snowflake".parse::<GuildId>().is_err());
assert!("".parse::<ChannelId>().is_err());
}
#[test]
fn type_isolation() {
let g: GuildId = "1".parse().unwrap();
let c: ChannelId = "1".parse().unwrap();
let _ = (g, c);
}
}