pub mod marker;
mod anonymizable;
pub use anonymizable::AnonymizableId;
use serde::{
de::{Deserialize, Deserializer, Error as DeError, Unexpected, Visitor},
ser::{Serialize, Serializer},
};
use std::{
any,
cmp::Ordering,
fmt::{Debug, Display, Formatter, Result as FmtResult},
hash::{Hash, Hasher},
marker::PhantomData,
num::{NonZeroI64, NonZeroU64, ParseIntError, TryFromIntError},
str::FromStr,
};
#[repr(transparent)]
pub struct Id<T> {
phantom: PhantomData<fn(T) -> T>,
value: NonZeroU64,
}
impl<T> Id<T> {
const fn from_nonzero(value: NonZeroU64) -> Self {
Self {
phantom: PhantomData,
value,
}
}
#[track_caller]
pub const fn new(n: u64) -> Self {
if let Some(id) = Self::new_checked(n) {
id
} else {
panic!("value is zero");
}
}
#[allow(unsafe_code)]
pub const unsafe fn new_unchecked(n: u64) -> Self {
Self::from_nonzero(NonZeroU64::new_unchecked(n))
}
pub const fn new_checked(n: u64) -> Option<Self> {
if let Some(n) = NonZeroU64::new(n) {
Some(Self::from_nonzero(n))
} else {
None
}
}
pub const fn get(self) -> u64 {
self.value.get()
}
pub const fn into_nonzero(self) -> NonZeroU64 {
self.value
}
pub const fn cast<New>(self) -> Id<New> {
Id::from_nonzero(self.value)
}
}
impl<T> Clone for Id<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Id<T> {}
impl<T> Debug for Id<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str("Id")?;
let type_name = any::type_name::<T>();
if let Some(position) = type_name.rfind("::") {
if let Some(slice) = type_name.get(position + 2..) {
f.write_str("<")?;
f.write_str(slice)?;
f.write_str(">")?;
}
}
f.write_str("(")?;
Debug::fmt(&self.value, f)?;
f.write_str(")")
}
}
impl<'de, T> Deserialize<'de> for Id<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct IdVisitor<T> {
phantom: PhantomData<T>,
}
impl<T> IdVisitor<T> {
const fn new() -> Self {
Self {
phantom: PhantomData,
}
}
}
impl<'de, T> Visitor<'de> for IdVisitor<T> {
type Value = Id<T>;
fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str("a discord snowflake")
}
fn visit_u64<E: DeError>(self, value: u64) -> Result<Self::Value, E> {
let value = NonZeroU64::new(value).ok_or_else(|| {
DeError::invalid_value(Unexpected::Unsigned(value), &"non zero u64")
})?;
Ok(Id::from(value))
}
fn visit_i64<E: DeError>(self, value: i64) -> Result<Self::Value, E> {
let unsigned = u64::try_from(value).map_err(|_| {
DeError::invalid_value(Unexpected::Signed(value), &"non zero u64")
})?;
self.visit_u64(unsigned)
}
fn visit_newtype_struct<D: Deserializer<'de>>(
self,
deserializer: D,
) -> Result<Self::Value, D::Error> {
deserializer.deserialize_any(IdVisitor::new())
}
fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
let value = v.parse().map_err(|_| {
let unexpected = Unexpected::Str(v);
DeError::invalid_value(unexpected, &"non zero u64 string")
})?;
self.visit_u64(value)
}
}
deserializer.deserialize_any(IdVisitor::new())
}
}
impl<T> Display for Id<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(&self.value.get(), f)
}
}
impl<T> From<Id<T>> for u64 {
fn from(id: Id<T>) -> Self {
id.get()
}
}
impl<T> From<NonZeroU64> for Id<T> {
fn from(id: NonZeroU64) -> Self {
Self::from_nonzero(id)
}
}
impl<T> From<Id<T>> for NonZeroU64 {
fn from(id: Id<T>) -> Self {
id.into_nonzero()
}
}
impl<T> FromStr for Id<T> {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NonZeroU64::from_str(s).map(Self::from_nonzero)
}
}
impl<T> Eq for Id<T> {}
impl<T> Hash for Id<T> {
fn hash<U: Hasher>(&self, state: &mut U) {
state.write_u64(self.value.get());
}
}
impl<T> Ord for Id<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}
impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> PartialEq<i64> for Id<T> {
fn eq(&self, other: &i64) -> bool {
u64::try_from(*other).is_ok_and(|v| v == self.value.get())
}
}
impl<T> PartialEq<Id<T>> for i64 {
fn eq(&self, other: &Id<T>) -> bool {
u64::try_from(*self).is_ok_and(|v| v == other.value.get())
}
}
impl<T> PartialEq<u64> for Id<T> {
fn eq(&self, other: &u64) -> bool {
self.value.get() == *other
}
}
impl<T> PartialEq<Id<T>> for u64 {
fn eq(&self, other: &Id<T>) -> bool {
other.value.get() == *self
}
}
impl<T> PartialOrd for Id<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T> Serialize for Id<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_newtype_struct("Id", &self.to_string())
}
}
impl<T> TryFrom<i64> for Id<T> {
type Error = TryFromIntError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
let signed_nonzero = NonZeroI64::try_from(value)?;
let unsigned_nonzero = NonZeroU64::try_from(signed_nonzero)?;
Ok(Self::from_nonzero(unsigned_nonzero))
}
}
impl<T> TryFrom<u64> for Id<T> {
type Error = TryFromIntError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let nonzero = NonZeroU64::try_from(value)?;
Ok(Self::from_nonzero(nonzero))
}
}
#[cfg(test)]
mod tests {
use super::{
marker::{
ApplicationMarker, AttachmentMarker, AuditLogEntryMarker, ChannelMarker, CommandMarker,
CommandVersionMarker, EmojiMarker, EntitlementMarker, GenericMarker, GuildMarker,
IntegrationMarker, InteractionMarker, MessageMarker, RoleMarker,
RoleSubscriptionSkuMarker, SkuMarker, StageMarker, UserMarker, WebhookMarker,
},
Id,
};
use serde::{Deserialize, Serialize};
use serde_test::Token;
use static_assertions::assert_impl_all;
use std::{
collections::hash_map::DefaultHasher,
error::Error,
fmt::{Debug, Display},
hash::{Hash, Hasher},
num::NonZeroU64,
str::FromStr,
};
assert_impl_all!(ApplicationMarker: Debug, Send, Sync);
assert_impl_all!(AttachmentMarker: Debug, Send, Sync);
assert_impl_all!(AuditLogEntryMarker: Debug, Send, Sync);
assert_impl_all!(ChannelMarker: Debug, Send, Sync);
assert_impl_all!(CommandMarker: Debug, Send, Sync);
assert_impl_all!(CommandVersionMarker: Debug, Send, Sync);
assert_impl_all!(EmojiMarker: Debug, Send, Sync);
assert_impl_all!(EntitlementMarker: Debug, Send, Sync);
assert_impl_all!(SkuMarker: Debug, Send, Sync);
assert_impl_all!(GenericMarker: Debug, Send, Sync);
assert_impl_all!(GuildMarker: Debug, Send, Sync);
assert_impl_all!(IntegrationMarker: Debug, Send, Sync);
assert_impl_all!(InteractionMarker: Debug, Send, Sync);
assert_impl_all!(MessageMarker: Debug, Send, Sync);
assert_impl_all!(RoleMarker: Debug, Send, Sync);
assert_impl_all!(RoleSubscriptionSkuMarker: Debug, Send, Sync);
assert_impl_all!(StageMarker: Debug, Send, Sync);
assert_impl_all!(UserMarker: Debug, Send, Sync);
assert_impl_all!(WebhookMarker: Debug, Send, Sync);
assert_impl_all!(Id<GenericMarker>:
Clone, Copy, Debug, Deserialize<'static>, Display, Eq, From<NonZeroU64>,
FromStr, Hash, Into<NonZeroU64>, Into<u64>, Ord, PartialEq, PartialEq<i64>, PartialEq<u64>, PartialOrd, Send, Serialize, Sync,
TryFrom<i64>, TryFrom<u64>
);
assert_impl_all!(Id<*const ()>: Send, Sync);
#[test]
fn initializers() -> Result<(), Box<dyn Error>> {
assert!(Id::<GenericMarker>::new_checked(0).is_none());
assert_eq!(Some(1), Id::<GenericMarker>::new_checked(1).map(Id::get));
assert_eq!(1, Id::<GenericMarker>::new(1).get());
assert_eq!(
123_u64,
Id::<GenericMarker>::from(NonZeroU64::new(123).expect("non zero"))
);
assert_eq!(123_u64, Id::<GenericMarker>::from_str("123")?);
assert!(Id::<GenericMarker>::from_str("0").is_err());
assert!(Id::<GenericMarker>::from_str("123a").is_err());
assert!(Id::<GenericMarker>::try_from(-123_i64).is_err());
assert!(Id::<GenericMarker>::try_from(0_i64).is_err());
assert_eq!(123_u64, Id::<GenericMarker>::try_from(123_i64)?);
assert!(Id::<GenericMarker>::try_from(0_u64).is_err());
assert_eq!(123_u64, Id::<GenericMarker>::try_from(123_u64)?);
Ok(())
}
#[test]
fn conversions() {
assert_eq!(1, u64::from(Id::<GenericMarker>::new(1)));
assert_eq!(
NonZeroU64::new(1).expect("non zero"),
NonZeroU64::from(Id::<GenericMarker>::new(1))
);
}
#[should_panic(expected = "value is zero")]
#[test]
const fn test_new_checked_zero() {
_ = Id::<GenericMarker>::new(0);
}
#[test]
fn cast() {
let id = Id::<GenericMarker>::new(123);
assert_eq!(123_u64, id.cast::<RoleMarker>());
}
#[test]
fn debug() {
let id = Id::<RoleMarker>::new(114_941_315_417_899_012);
assert_eq!("Id<RoleMarker>(114941315417899012)", format!("{id:?}"));
}
#[test]
fn display() {
let id = Id::<GenericMarker>::new(114_941_315_417_899_012);
assert_eq!("114941315417899012", id.to_string());
}
#[test]
fn hash() {
let id = Id::<GenericMarker>::new(123);
let mut id_hasher = DefaultHasher::new();
id.hash(&mut id_hasher);
let mut value_hasher = DefaultHasher::new();
123_u64.hash(&mut value_hasher);
assert_eq!(id_hasher.finish(), value_hasher.finish());
}
#[test]
fn ordering() {
let lesser = Id::<GenericMarker>::new(911_638_235_594_244_096);
let center = Id::<GenericMarker>::new(911_638_263_322_800_208);
let greater = Id::<GenericMarker>::new(911_638_287_939_166_208);
assert!(center.cmp(&greater).is_lt());
assert!(center.cmp(¢er).is_eq());
assert!(center.cmp(&lesser).is_gt());
}
#[allow(clippy::too_many_lines)]
#[test]
fn serde() {
serde_test::assert_tokens(
&Id::<ApplicationMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<ApplicationMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<AttachmentMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<AttachmentMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<AuditLogEntryMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<AuditLogEntryMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<ChannelMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<ChannelMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<CommandMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<CommandMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<CommandVersionMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<CommandVersionMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<EmojiMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<EmojiMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<EntitlementMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<EntitlementMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_tokens(
&Id::<SkuMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<SkuMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_tokens(
&Id::<GenericMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<GenericMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<GuildMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<GuildMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<IntegrationMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<IntegrationMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<InteractionMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<InteractionMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<MessageMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<MessageMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<RoleMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<RoleMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<RoleSubscriptionSkuMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_tokens(
&Id::<StageMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<StageMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<UserMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<UserMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_tokens(
&Id::<WebhookMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::Str("114941315417899012"),
],
);
serde_test::assert_de_tokens(
&Id::<WebhookMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::U64(114_941_315_417_899_012),
],
);
serde_test::assert_de_tokens(
&Id::<WebhookMarker>::new(114_941_315_417_899_012),
&[
Token::NewtypeStruct { name: "Id" },
Token::I64(114_941_315_417_899_012),
],
);
}
}