use std::fmt;
use std::num::{NonZeroI64, NonZeroU64};
use serde::de::Error;
use super::prelude::*;
macro_rules! newtype_display_impl {
($name:ident, |$this:ident| $inner:expr) => {
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&(|$this: $name| $inner)(*self), f)
}
}
};
}
macro_rules! forward_fromstr_impl {
($name:ident, $wrapper:path) => {
impl std::str::FromStr for $name {
type Err = <u64 as std::str::FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self($wrapper(s.parse()?)))
}
}
};
}
macro_rules! id_u64 {
($($name:ident: $doc:literal;)*) => {
$(
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
#[doc = $doc]
pub struct $name(InnerId);
impl $name {
#[doc = concat!("Creates a new ", stringify!($name), " from a u64.")]
#[inline]
#[must_use]
#[track_caller]
pub const fn new(id: u64) -> Self {
match NonZeroU64::new(id) {
Some(inner) => Self(InnerId(inner)),
None => panic!(concat!("Attempted to call ", stringify!($name), "::new with invalid (0) value"))
}
}
#[inline]
#[must_use]
pub const fn get(self) -> u64 {
self.0.0.get()
}
#[doc = concat!("Retrieves the time that the ", stringify!($name), " was created.")]
#[must_use]
pub fn created_at(&self) -> Timestamp {
Timestamp::from_discord_id(self.get())
}
}
impl Default for $name {
fn default() -> Self {
Self(InnerId(NonZeroU64::MIN))
}
}
impl AsRef<$name> for $name {
fn as_ref(&self) -> &Self {
self
}
}
impl<'a> From<&'a $name> for $name {
fn from(id: &'a $name) -> $name {
id.clone()
}
}
impl From<u64> for $name {
fn from(id: u64) -> $name {
$name::new(id)
}
}
impl From<NonZeroU64> for $name {
fn from(id: NonZeroU64) -> $name {
$name(InnerId(id))
}
}
impl PartialEq<u64> for $name {
fn eq(&self, u: &u64) -> bool {
self.get() == *u
}
}
impl From<$name> for NonZeroU64 {
fn from(id: $name) -> NonZeroU64 {
id.0.0
}
}
impl From<$name> for NonZeroI64 {
fn from(id: $name) -> NonZeroI64 {
unsafe {NonZeroI64::new_unchecked(id.get() as i64)}
}
}
impl From<$name> for u64 {
fn from(id: $name) -> u64 {
id.get()
}
}
impl From<$name> for i64 {
fn from(id: $name) -> i64 {
id.get() as i64
}
}
newtype_display_impl!($name, |this| this.0.0);
forward_fromstr_impl!($name, InnerId);
#[cfg(feature = "typesize")]
impl typesize::TypeSize for $name {}
)*
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(Rust, packed)]
pub(crate) struct InnerId(NonZeroU64);
impl fmt::Debug for InnerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner = self.0;
inner.fmt(f)
}
}
struct SnowflakeVisitor;
impl serde::de::Visitor<'_> for SnowflakeVisitor {
type Value = InnerId;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a string or integer snowflake that is not u64::MAX")
}
fn visit_i64<E: Error>(self, value: i64) -> Result<Self::Value, E> {
self.visit_u64(u64::try_from(value).map_err(Error::custom)?)
}
fn visit_u64<E: Error>(self, value: u64) -> Result<Self::Value, E> {
NonZeroU64::new(value)
.map(InnerId)
.ok_or_else(|| Error::custom("invalid value, expected non-max"))
}
fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
value.parse().map(InnerId).map_err(Error::custom)
}
}
impl<'de> serde::Deserialize<'de> for InnerId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<InnerId, D::Error> {
deserializer.deserialize_any(SnowflakeVisitor)
}
}
impl serde::Serialize for InnerId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(&{ self.0 })
}
}
id_u64! {
AttachmentId: "An identifier for an attachment.";
ApplicationId: "An identifier for an Application.";
ChannelId: "An identifier for a Channel";
EmojiId: "An identifier for an Emoji";
GenericId: "An identifier for an unspecific entity.";
GuildId: "An identifier for a Guild";
IntegrationId: "An identifier for an Integration";
MessageId: "An identifier for a Message";
RoleId: "An identifier for a Role";
ScheduledEventId: "An identifier for a Scheduled Event";
SoundId: "An identifier for a Soundboard sound";
StickerId: "An identifier for a sticker.";
StickerPackId: "An identifier for a sticker pack.";
StickerPackBannerId: "An identifier for a sticker pack banner.";
SkuId: "An identifier for a SKU.";
UserId: "An identifier for a User";
WebhookId: "An identifier for a [`Webhook`]";
AuditLogEntryId: "An identifier for an audit log entry.";
InteractionId: "An identifier for an interaction.";
CommandId: "An identifier for a slash command.";
CommandPermissionId: "An identifier for a slash command permission Id.";
CommandVersionId: "An identifier for a slash command version Id.";
TargetId: "An identifier for a slash command target Id.";
StageInstanceId: "An identifier for a stage channel instance.";
RuleId: "An identifier for an auto moderation rule";
ForumTagId: "An identifier for a forum tag.";
EntitlementId: "An identifier for an entitlement.";
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct ShardId(pub u32);
impl ShardId {
#[must_use]
pub fn get(self) -> u32 {
self.0
}
}
newtype_display_impl!(ShardId, |this| this.0);
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
#[repr(Rust, packed)]
pub struct AnswerId(u8);
impl AnswerId {
#[must_use]
pub fn get(self) -> u64 {
self.0.into()
}
}
newtype_display_impl!(AnswerId, |this| this.0);
forward_fromstr_impl!(AnswerId, std::convert::identity);
#[cfg(test)]
mod tests {
use std::num::NonZeroU64;
use super::{GuildId, InnerId};
#[test]
fn test_created_at() {
let id = GuildId::new(175928847299117063);
assert_eq!(id.created_at().unix_timestamp(), 1462015105);
assert_eq!(id.created_at().to_string(), "2016-04-30T11:18:25.796Z");
}
#[test]
fn test_id_serde() {
use serde::{Deserialize, Serialize};
use crate::json::{assert_json, json};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct S {
id: InnerId,
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct Opt {
id: Option<GuildId>,
}
let id = GuildId::new(17_5928_8472_9911_7063);
assert_json(&id, json!("175928847299117063"));
let s = S {
id: InnerId(NonZeroU64::new(17_5928_8472_9911_7063).unwrap()),
};
assert_json(&s, json!({"id": "175928847299117063"}));
let s = Opt {
id: Some(GuildId::new(17_5928_8472_9911_7063)),
};
assert_json(&s, json!({"id": "175928847299117063"}));
}
}