use std::cmp::Ordering;
use std::fmt::{self, Write};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::num::{NonZeroU64, ParseIntError, TryFromIntError};
use std::str::FromStr;
pub type GameId = Id<marker::GameMarker>;
pub type ModId = Id<marker::ModMarker>;
pub type FileId = Id<marker::FileMarker>;
pub type EventId = Id<marker::EventMarker>;
pub type CommentId = Id<marker::CommentMarker>;
pub type UserId = Id<marker::UserMarker>;
pub type MemberId = Id<marker::MemberMarker>;
pub type ResourceId = Id<marker::ResourceMarker>;
pub mod marker {
#[non_exhaustive]
pub struct GameMarker;
#[non_exhaustive]
pub struct ModMarker;
#[non_exhaustive]
pub struct FileMarker;
#[non_exhaustive]
pub struct EventMarker;
#[non_exhaustive]
pub struct CommentMarker;
#[non_exhaustive]
pub struct UserMarker;
#[non_exhaustive]
pub struct MemberMarker;
#[non_exhaustive]
pub struct ResourceMarker;
}
#[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(value: u64) -> Self {
if let Some(value) = Self::new_checked(value) {
value
} else {
panic!("value is zero")
}
}
pub const fn new_checked(value: u64) -> Option<Self> {
if let Some(value) = NonZeroU64::new(value) {
Some(Self::from_nonzero(value))
} else {
None
}
}
pub const fn get(self) -> u64 {
self.value.get()
}
pub const fn transform<New>(self) -> Id<New> {
Id::new(self.get())
}
}
impl<T> Clone for Id<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Id<T> {}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Id")?;
let name = std::any::type_name::<T>();
if let Some(pos) = name.rfind("::") {
if let Some(marker) = name.get(pos + 2..) {
f.write_char('<')?;
f.write_str(marker)?;
f.write_char('>')?;
}
}
f.write_char('(')?;
fmt::Debug::fmt(&self.value, f)?;
f.write_char(')')
}
}
impl<T> fmt::Display for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.value, f)
}
}
impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> Eq for Id<T> {}
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> Ord for Id<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}
impl<T> Hash for Id<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.value.get());
}
}
impl<T> From<Id<T>> for u64 {
fn from(value: Id<T>) -> Self {
value.get()
}
}
impl<T> From<NonZeroU64> for Id<T> {
fn from(value: NonZeroU64) -> Self {
Self::from_nonzero(value)
}
}
impl<T> TryFrom<u64> for Id<T> {
type Error = TryFromIntError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let value = NonZeroU64::try_from(value)?;
Ok(Id::from_nonzero(value))
}
}
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)
}
}
use serde::{Deserialize, Deserializer, Serialize, Serializer};
impl<'de, T> Deserialize<'de> for Id<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
NonZeroU64::deserialize(deserializer).map(Self::from_nonzero)
}
}
impl<T> Serialize for Id<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.value.serialize(serializer)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::marker::*;
use super::Id;
#[test]
fn from_str() {
assert_eq!(
Id::<GameMarker>::new(123),
Id::<GameMarker>::from_str("123").unwrap()
);
assert!(Id::<GameMarker>::from_str("0").is_err());
assert!(Id::<GameMarker>::from_str("123a").is_err());
}
}