1use std::cmp::Ordering;
4use std::fmt::{self, Write};
5use std::hash::{Hash, Hasher};
6use std::marker::PhantomData;
7use std::num::{NonZeroI64, NonZeroU64, ParseIntError, TryFromIntError};
8use std::str::FromStr;
9
10pub type GameId = Id<marker::GameMarker>;
12pub type ModId = Id<marker::ModMarker>;
14pub type FileId = Id<marker::FileMarker>;
16pub type EventId = Id<marker::EventMarker>;
18pub type CommentId = Id<marker::CommentMarker>;
20pub type UserId = Id<marker::UserMarker>;
22pub type MemberId = Id<marker::MemberMarker>;
24pub type ResourceId = Id<marker::ResourceMarker>;
26
27pub mod marker {
29 #[non_exhaustive]
31 pub struct GameMarker;
32
33 #[non_exhaustive]
35 pub struct ModMarker;
36
37 #[non_exhaustive]
39 pub struct FileMarker;
40
41 #[non_exhaustive]
43 pub struct EventMarker;
44
45 #[non_exhaustive]
47 pub struct CommentMarker;
48
49 #[non_exhaustive]
51 pub struct UserMarker;
52
53 #[non_exhaustive]
55 pub struct MemberMarker;
56
57 #[non_exhaustive]
59 pub struct ResourceMarker;
60}
61
62#[repr(transparent)]
67pub struct Id<T> {
68 phantom: PhantomData<fn(T) -> T>,
69 value: NonZeroU64,
70}
71
72impl<T> Id<T> {
73 const fn from_nonzero(value: NonZeroU64) -> Self {
74 Self {
75 phantom: PhantomData,
76 value,
77 }
78 }
79
80 #[track_caller]
102 pub const fn new(value: u64) -> Self {
103 if let Some(value) = Self::new_checked(value) {
104 value
105 } else {
106 panic!("value is zero")
107 }
108 }
109
110 pub const fn new_checked(value: u64) -> Option<Self> {
112 if let Some(value) = NonZeroU64::new(value) {
113 Some(Self::from_nonzero(value))
114 } else {
115 None
116 }
117 }
118
119 pub const fn get(self) -> u64 {
120 self.value.get()
121 }
122
123 pub const fn transform<New>(self) -> Id<New> {
124 Id::new(self.get())
125 }
126}
127
128impl<T> Clone for Id<T> {
129 fn clone(&self) -> Self {
130 *self
131 }
132}
133
134impl<T> Copy for Id<T> {}
135
136impl<T> fmt::Debug for Id<T> {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 f.write_str("Id")?;
139
140 let name = std::any::type_name::<T>();
141 if let Some(pos) = name.rfind("::") {
142 if let Some(marker) = name.get(pos + 2..) {
143 f.write_char('<')?;
144 f.write_str(marker)?;
145 f.write_char('>')?;
146 }
147 }
148
149 f.write_char('(')?;
150 fmt::Debug::fmt(&self.value, f)?;
151 f.write_char(')')
152 }
153}
154
155impl<T> fmt::Display for Id<T> {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 fmt::Display::fmt(&self.value, f)
158 }
159}
160
161impl<T> PartialEq for Id<T> {
162 fn eq(&self, other: &Self) -> bool {
163 self.value == other.value
164 }
165}
166
167impl<T> Eq for Id<T> {}
168
169impl<T> PartialEq<u64> for Id<T> {
170 fn eq(&self, other: &u64) -> bool {
171 self.value.get() == *other
172 }
173}
174
175impl<T> PartialEq<Id<T>> for u64 {
176 fn eq(&self, other: &Id<T>) -> bool {
177 other.value.get() == *self
178 }
179}
180
181impl<T> PartialOrd for Id<T> {
182 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
183 Some(self.cmp(other))
184 }
185}
186
187impl<T> Ord for Id<T> {
188 fn cmp(&self, other: &Self) -> Ordering {
189 self.value.cmp(&other.value)
190 }
191}
192
193impl<T> Hash for Id<T> {
194 fn hash<H: Hasher>(&self, state: &mut H) {
195 state.write_u64(self.value.get());
196 }
197}
198
199impl<T> From<Id<T>> for u64 {
200 fn from(value: Id<T>) -> Self {
201 value.get()
202 }
203}
204
205impl<T> From<NonZeroU64> for Id<T> {
206 fn from(value: NonZeroU64) -> Self {
207 Self::from_nonzero(value)
208 }
209}
210
211impl<T> TryFrom<i64> for Id<T> {
212 type Error = TryFromIntError;
213
214 fn try_from(value: i64) -> Result<Self, Self::Error> {
215 let value = NonZeroI64::try_from(value)?;
216 let value = NonZeroU64::try_from(value)?;
217 Ok(Self::from_nonzero(value))
218 }
219}
220
221impl<T> TryFrom<u64> for Id<T> {
222 type Error = TryFromIntError;
223
224 fn try_from(value: u64) -> Result<Self, Self::Error> {
225 let value = NonZeroU64::try_from(value)?;
226 Ok(Id::from_nonzero(value))
227 }
228}
229
230impl<T> FromStr for Id<T> {
231 type Err = ParseIntError;
232
233 fn from_str(s: &str) -> Result<Self, Self::Err> {
234 NonZeroU64::from_str(s).map(Self::from_nonzero)
235 }
236}
237
238use serde::de::{Deserialize, Deserializer};
239use serde::ser::{Serialize, Serializer};
240
241impl<'de, T> Deserialize<'de> for Id<T> {
242 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
243 NonZeroU64::deserialize(deserializer).map(Self::from_nonzero)
244 }
245}
246
247impl<T> Serialize for Id<T> {
248 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
249 self.value.serialize(serializer)
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use std::str::FromStr;
256
257 use super::marker::*;
258 use super::Id;
259
260 #[test]
261 fn from_str() {
262 assert_eq!(
263 Id::<GameMarker>::new(123),
264 Id::<GameMarker>::from_str("123").unwrap()
265 );
266 assert!(Id::<GameMarker>::from_str("0").is_err());
267 assert!(Id::<GameMarker>::from_str("123a").is_err());
268 }
269
270 #[test]
271 fn try_from() {
272 assert!(Id::<GameMarker>::try_from(-123i64).is_err());
273 assert!(Id::<GameMarker>::try_from(0i64).is_err());
274 assert_eq!(123u64, Id::<GameMarker>::try_from(123i64).unwrap());
275
276 assert!(Id::<GameMarker>::try_from(0u64).is_err());
277 assert_eq!(123u64, Id::<GameMarker>::try_from(123u64).unwrap());
278 }
279}