1use std::{
2 cell::Cell,
3 fmt::Debug,
4 marker::PhantomData,
5 str::FromStr,
6 sync::atomic::{AtomicU8, Ordering},
7 time::{Duration, SystemTime},
8};
9
10use bytemuck::{Pod, TransparentWrapper, Zeroable};
11use rapira::{Rapira, RapiraError};
12use rend::u64_be;
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
15
16use crate::{
17 IdStr, Typ,
18 enc::IdHasher,
19 error::ArmourError,
20 get_type::GetType,
21 key_part::{KeyPart, MILLISECOND_BITS},
22 key_type::KeyType,
23 num_ops::g8bits,
24};
25
26type Result<T, E = ArmourError> = core::result::Result<T, E>;
27
28const TS_DIFF: u64 = 0x187_6350_0000;
32
33const TS_BITS: u32 = 40;
34const TS_MAX: u64 = (1 << TS_BITS) - 1;
35const TS_SHIFT: u32 = u64::BITS - TS_BITS;
37
38pub(crate) const SHARD_BITS: u32 = 8;
40pub const SHARD_ID_MAX: u64 = (1 << SHARD_BITS) - 1;
42
43const SEQ_BITS: u32 = u64::BITS - TS_BITS - SHARD_BITS;
45const SEQ_MAX: u64 = (1 << SEQ_BITS) - 1;
47
48const SHARD_INST_BITS: u32 = 3;
50pub const SHARD_INSTANCE_ID_MAX: u64 = (1 << SHARD_INST_BITS) - 1;
52const SHARD_INSTANCE_SHIFT: u32 = SHARD_THREAD_BITS + SEQ_BITS;
54
55pub const SHARD_THREAD_BITS: u32 = 5;
57pub const SHARD_THREAD_ID_MAX: u64 = (1 << SHARD_THREAD_BITS) - 1;
59
60#[derive(Debug, Clone, Copy)]
61struct SeqForMs {
62 ts: u64,
64 seq: u64,
65 count: u64,
66}
67
68impl SeqForMs {
69 #[cfg(feature = "std")]
70 #[inline]
71 fn with_ts_and_rand_seq(ts: u64) -> Self {
72 use rand::{RngExt, rng};
73
74 let seq = rng().random_range(0..=SEQ_MAX);
75
76 SeqForMs { ts, seq, count: 0 }
77 }
78
79 #[cold]
80 fn wait_inc(self, ts: u64, duration: Duration) -> Self {
81 tracing::info!(?duration, "wait_inc");
82 let nanos = duration.subsec_nanos() % 1_000_000;
83 let wait_nanos = 1_000_000 - nanos;
84 std::thread::sleep(Duration::from_nanos(wait_nanos as u64));
85
86 SeqForMs::with_ts_and_rand_seq(ts + 1)
87 }
88
89 #[inline]
90 fn increment(mut self, ts: u64, duration: Duration) -> Self {
91 if self.count == SEQ_MAX {
92 self.wait_inc(ts, duration)
93 } else {
94 if self.seq == SEQ_MAX {
95 self.seq = 0;
96 } else {
97 self.seq += 1;
98 }
99 self.count += 1;
100 self
101 }
102 }
103}
104
105static THREAD_SEQ: AtomicU8 = AtomicU8::new(0);
106
107thread_local! {
108 static SEQ_CHECK: Cell<SeqForMs> = Cell::new(SeqForMs::with_ts_and_rand_seq(0));
109 static THREAD_ID: Cell<u8> = Cell::new(THREAD_SEQ.fetch_add(1, Ordering::Relaxed));
110}
111
112#[derive(Hash, IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
124#[cfg_attr(
125 feature = "rkyv",
126 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
127)]
128#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
129#[repr(transparent)]
130#[transparent(u64_be)]
131pub struct Fuid<H>(pub u64_be, PhantomData<H>);
132
133unsafe impl<T> Zeroable for Fuid<T> {}
134unsafe impl<T: 'static> Pod for Fuid<T> {}
135
136impl<T> Clone for Fuid<T> {
137 fn clone(&self) -> Self {
138 *self
139 }
140}
141
142impl<T> Copy for Fuid<T> {}
143
144impl<H> Fuid<H> {
145 pub fn from_thread() -> Self {
146 let thread_id = THREAD_ID.get();
147 Self::with_shard(thread_id as u64)
148 }
149
150 #[cfg(feature = "std")]
154 #[inline]
155 pub fn with_inst_thread(instance_id: u64, thread_id: u64) -> Self {
156 assert!(instance_id <= SHARD_INSTANCE_ID_MAX);
157 assert!(thread_id <= SHARD_THREAD_ID_MAX);
158
159 let shard_id = (instance_id << SHARD_THREAD_BITS) | thread_id;
160 Self::with_shard(shard_id)
161 }
162
163 #[cfg(feature = "std")]
166 pub fn with_shard(shard_id: u64) -> Self {
167 assert!(shard_id <= SHARD_ID_MAX);
168
169 let now = SystemTime::now();
170 #[allow(clippy::unwrap_used)]
172 let duration = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
173 let timestamp = duration.as_millis() as u64;
174
175 let new_epoch_ts = timestamp - TS_DIFF;
176
177 let id = new_epoch_ts << TS_SHIFT;
178
179 let id = id | (shard_id << SEQ_BITS);
180
181 let mut seq_check = SEQ_CHECK.get();
182
183 if seq_check.ts == timestamp {
184 seq_check = seq_check.increment(timestamp, duration);
185 } else {
186 seq_check = SeqForMs::with_ts_and_rand_seq(timestamp);
187 }
188 SEQ_CHECK.set(seq_check);
189
190 let id = id | seq_check.seq;
191
192 Self(u64_be::from_native(id), PhantomData)
193 }
194
195 #[inline]
197 pub fn timestamp(&self) -> u64 {
198 let ts = self.0.to_native() >> TS_SHIFT;
199 ts + TS_DIFF
200 }
201
202 pub fn date(&self) -> time::OffsetDateTime {
203 let ts = self.timestamp();
204 let ts = ts as i128;
205 let ts = ts * 1_000_000;
206 let dt = time::OffsetDateTime::from_unix_timestamp_nanos(ts);
207 dt.expect("invalid timestamp")
208 }
209
210 #[inline]
212 pub fn get(&self) -> u64 {
213 self.0.to_native()
214 }
215
216 #[inline]
217 pub fn to_be_bytes(self) -> [u8; 8] {
218 zerocopy::transmute!(self)
219 }
220
221 #[inline]
222 pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
223 zerocopy::transmute!(bytes)
224 }
225
226 #[inline]
228 pub fn to_bytes(self) -> [u8; 8] {
229 zerocopy::transmute!(self)
230 }
231
232 #[inline]
234 pub fn from_bytes(bytes: [u8; 8]) -> Self {
235 zerocopy::transmute!(bytes)
236 }
237
238 #[inline]
240 pub fn to_u64(self) -> u64 {
241 zerocopy::transmute!(self)
242 }
243
244 #[inline]
246 pub fn from_u64(bytes: u64) -> Self {
247 zerocopy::transmute!(bytes)
248 }
249
250 #[inline]
251 pub fn to_le_bytes(self) -> [u8; 8] {
252 self.0.to_native().to_le_bytes()
253 }
254
255 #[inline]
256 pub fn instance_id(&self) -> u64 {
257 let ts = self.0.to_native() >> SHARD_INSTANCE_SHIFT;
258 ts & SHARD_INSTANCE_ID_MAX
259 }
260
261 #[inline]
262 pub fn thread_id(&self) -> u64 {
263 let ts = self.0.to_native() >> SEQ_BITS;
264 ts & SHARD_THREAD_ID_MAX
265 }
266
267 #[inline]
268 pub fn shard_id(&self) -> u64 {
269 let ts = self.0.to_native() >> SEQ_BITS;
270 ts & SHARD_ID_MAX
271 }
272
273 #[inline]
274 fn seq(&self) -> u64 {
275 let ts = self.0.to_native();
276 ts & SEQ_MAX
277 }
278
279 #[cfg(feature = "std")]
281 pub fn format<W>(&self, w: &mut W) -> core::result::Result<(), core::fmt::Error>
282 where
283 W: std::fmt::Write,
284 {
285 let id = self.0.to_native();
286 let dt = self.date();
287 let instance_id = self.instance_id();
288 let thread_id = self.thread_id();
289 let seq = self.seq();
290 write!(
291 w,
292 "id: {id:#x}; {id:#b}; {dt}-{instance_id}-{thread_id}-{seq}"
293 )
294 }
295
296 pub fn date_prefix(ms: u64) -> [u8; 8] {
299 let ts = (ms - TS_DIFF) << TS_SHIFT;
300 ts.to_be_bytes()
301 }
302
303 pub fn check(&self) -> core::result::Result<(), ArmourError> {
304 let ts = self.0.to_native() >> TS_SHIFT;
305 if ts > TS_MAX {
306 return Err(ArmourError::IdDecodeError);
307 }
308 Ok(())
309 }
310
311 pub fn increment(mut self) -> Self {
313 self.0 += 1;
314 self
315 }
316
317 #[inline]
319 pub fn group_id(&self) -> u32 {
320 g8bits(self.0.to_native(), MILLISECOND_BITS)
321 }
322}
323
324impl<H: IdHasher> Fuid<H> {
325 pub fn ser(self) -> IdStr {
326 let u: u64 = zerocopy::transmute!(self);
327 H::ser(u)
328 }
329
330 pub fn deser(id: &str) -> Result<Self> {
331 let id = H::deser(id)?;
332 Ok(zerocopy::transmute!(id))
333 }
334}
335
336impl Fuid<()> {
337 pub fn ser(self) -> IdStr {
338 let u: [u8; 8] = zerocopy::transmute!(self);
339 crate::enc::encode(&u)
340 }
341
342 pub fn deser(id: &str) -> Result<Self> {
343 let id = crate::enc::decode(id)?;
344 Ok(zerocopy::transmute!(id))
345 }
346}
347
348impl<T> PartialOrd for Fuid<T> {
349 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
350 Some(self.cmp(other))
351 }
352}
353
354impl<T> Ord for Fuid<T> {
355 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
356 self.0.cmp(&other.0)
357 }
358}
359
360impl<T> PartialEq for Fuid<T> {
361 fn eq(&self, other: &Self) -> bool {
362 self.0 == other.0
363 }
364}
365
366impl<T> Eq for Fuid<T> {}
367
368impl<T> PartialEq<[u8; 8]> for Fuid<T> {
369 fn eq(&self, other: &[u8; 8]) -> bool {
370 let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
371 bytes == other
372 }
373}
374
375impl<T> PartialEq<Fuid<T>> for [u8; 8] {
376 fn eq(&self, other: &Fuid<T>) -> bool {
377 let bytes: &[u8; 8] = zerocopy::transmute_ref!(other);
378 bytes == self
379 }
380}
381
382impl<T> PartialEq<Fuid<T>> for Option<Fuid<T>> {
383 fn eq(&self, other: &Fuid<T>) -> bool {
384 match self {
385 Some(id) => id == other,
386 None => false,
387 }
388 }
389}
390
391impl<T> PartialEq<Option<Fuid<T>>> for Fuid<T> {
392 fn eq(&self, other: &Option<Fuid<T>>) -> bool {
393 match other {
394 Some(id) => self == id,
395 None => false,
396 }
397 }
398}
399
400impl<T> AsRef<Self> for Fuid<T> {
401 fn as_ref(&self) -> &Self {
402 self
403 }
404}
405
406impl<T> AsRef<[u8; 8]> for Fuid<T> {
407 fn as_ref(&self) -> &[u8; 8] {
408 zerocopy::transmute_ref!(self)
409 }
410}
411
412impl<H: IdHasher> Rapira for Fuid<H> {
414 const STATIC_SIZE: Option<usize> = Some(8);
415 const MIN_SIZE: usize = 8;
416
417 #[inline]
418 fn size(&self) -> usize {
419 8
420 }
421
422 #[inline]
423 fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
424 let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;
425
426 const ZERO_BYTES: [u8; 8] = [0u8; 8];
427
428 if bytes == ZERO_BYTES {
429 return Err(RapiraError::NonZero);
430 }
431
432 *slice = unsafe { slice.get_unchecked(8..) };
433 Ok(())
434 }
435
436 #[inline]
437 fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
438 where
439 Self: Sized,
440 {
441 let bytes = <[u8; 8]>::from_slice(slice)?;
442 let id = Self::from_bytes(bytes);
443 Ok(id)
444 }
445
446 #[inline]
447 fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
448 let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
449 bytes.convert_to_bytes(slice, cursor);
450 }
451
452 #[inline]
453 fn convert_to_bytes_ctx(
454 &self,
455 slice: &mut [u8],
456 cursor: &mut usize,
457 flags: rapira::RapiraFlags,
458 ) {
459 if flags.has(crate::ID_ENC_FLAG) {
460 let u: u64 = zerocopy::transmute!(*self);
461 let id = H::encrypt(u);
462 id.convert_to_bytes(slice, cursor);
463 } else {
464 self.convert_to_bytes(slice, cursor)
465 }
466 }
467
468 #[inline]
469 fn from_slice_ctx(slice: &mut &[u8], flags: rapira::RapiraFlags) -> rapira::Result<Self>
470 where
471 Self: Sized,
472 {
473 if flags.has(crate::ID_ENC_FLAG) {
474 let id = u64::from_slice(slice)?;
475 let id = H::decrypt(id);
476 Ok(zerocopy::transmute!(id))
477 } else {
478 Self::from_slice(slice)
479 }
480 }
481}
482
483impl Rapira for Fuid<()> {
484 const STATIC_SIZE: Option<usize> = Some(8);
485 const MIN_SIZE: usize = 8;
486
487 #[inline]
488 fn size(&self) -> usize {
489 8
490 }
491
492 #[inline]
493 fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
494 let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;
495
496 const ZERO_BYTES: [u8; 8] = [0u8; 8];
497
498 if bytes == ZERO_BYTES {
499 return Err(RapiraError::NonZero);
500 }
501
502 *slice = unsafe { slice.get_unchecked(8..) };
503 Ok(())
504 }
505
506 #[inline]
507 fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
508 where
509 Self: Sized,
510 {
511 let bytes = <[u8; 8]>::from_slice(slice)?;
512 let id = Self::from_bytes(bytes);
513 Ok(id)
514 }
515
516 #[inline]
517 fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
518 let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
519 bytes.convert_to_bytes(slice, cursor);
520 }
521}
522
523impl<H> GetType for Fuid<H> {
524 const TYPE: Typ = Typ::Fuid;
525}
526
527impl<H> KeyPart for Fuid<H> {
528 const TY: KeyType = KeyType::Fuid;
529 const PREFIX_BITS: u32 = MILLISECOND_BITS;
530}
531
532impl<H: IdHasher> std::fmt::Display for Fuid<H> {
533 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
534 write!(f, "{}", self.ser())
535 }
536}
537
538impl std::fmt::Display for Fuid<()> {
539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540 write!(f, "{}", self.ser())
541 }
542}
543
544impl<H> Debug for Fuid<H> {
545 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
546 let id = self.0.to_native();
547 let id = format!("{id:#X}");
548 f.debug_tuple("Fuid").field(&id).finish()
549 }
550}
551
552impl<T: IdHasher> TryFrom<&str> for Fuid<T> {
553 type Error = ArmourError;
554 fn try_from(val: &str) -> Result<Self> {
555 Self::deser(val)
556 }
557}
558
559impl TryFrom<&str> for Fuid<()> {
560 type Error = ArmourError;
561 fn try_from(val: &str) -> Result<Self> {
562 Self::deser(val)
563 }
564}
565
566impl<T: IdHasher> FromStr for Fuid<T> {
567 type Err = ArmourError;
568 fn from_str(s: &str) -> Result<Self> {
569 Self::deser(s)
570 }
571}
572
573impl FromStr for Fuid<()> {
574 type Err = ArmourError;
575 fn from_str(s: &str) -> Result<Self> {
576 Self::deser(s)
577 }
578}
579
580#[cfg(feature = "std")]
581impl<T: IdHasher> Serialize for Fuid<T> {
582 fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
583 let s = self.ser();
584 serializer.serialize_str(&s)
585 }
586}
587
588#[cfg(feature = "std")]
589impl<'de, T: IdHasher> Deserialize<'de> for Fuid<T> {
590 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
591 where
592 D: Deserializer<'de>,
593 {
594 use serde::de::Error;
595
596 let s: &str = Deserialize::deserialize(deserializer)?;
597 let a = Fuid::<T>::deser(s).map_err(|err| {
598 tracing::error!("id value error: {err}");
599 D::Error::custom("id value error")
600 })?;
601 Ok(a)
602 }
603}
604
605#[cfg(feature = "std")]
606impl Serialize for Fuid<()> {
607 fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
608 let s = self.ser();
609 serializer.serialize_str(&s)
610 }
611}
612
613#[cfg(feature = "std")]
614impl<'de> Deserialize<'de> for Fuid<()> {
615 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
616 where
617 D: Deserializer<'de>,
618 {
619 use serde::de::Error;
620
621 let s: &str = Deserialize::deserialize(deserializer)?;
622 let a = Fuid::<()>::deser(s).map_err(|err| {
623 tracing::error!("id value error: {err}");
624 D::Error::custom("id value error")
625 })?;
626 Ok(a)
627 }
628}
629
630#[cfg(feature = "ts-rs")]
631impl<H> ts_rs::TS for Fuid<H> {
632 type WithoutGenerics = Fuid<()>;
633 type OptionInnerType = Self;
634 fn name(_: &ts_rs::Config) -> String {
635 "Fuid".to_owned()
636 }
637 fn decl_concrete(c: &ts_rs::Config) -> String {
638 format!("type Fuid = {};", Self::inline(c))
639 }
640 fn decl(c: &ts_rs::Config) -> String {
641 let inline = <Fuid<()> as ::ts_rs::TS>::inline(c);
642 format!("type Fuid = {inline};")
643 }
644 fn inline(_: &ts_rs::Config) -> String {
645 "string".to_owned()
646 }
647 fn inline_flattened(c: &ts_rs::Config) -> String {
648 panic!("{} cannot be flattened", Self::name(c))
649 }
650 fn output_path() -> Option<std::path::PathBuf> {
651 Some(std::path::PathBuf::from("fuid.ts"))
652 }
653}
654
655#[cfg(feature = "facet")]
656unsafe impl<'facet, H: 'static> facet::Facet<'facet> for Fuid<H> {
657 const SHAPE: &'static facet::Shape = &const {
658 const VTABLE: facet::VTableDirect = facet::vtable_direct!(Fuid<()> =>
659 Debug,
660 Hash,
661 PartialEq,
662 PartialOrd,
663 Ord,
664 );
665
666 facet::ShapeBuilder::for_sized::<Fuid<H>>("Fuid")
667 .ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
668 repr: facet::Repr::transparent(),
669 kind: facet::StructKind::TupleStruct,
670 fields: &const {
671 [facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
672 },
673 })))
674 .inner(<u64 as facet::Facet>::SHAPE)
675 .def(facet::Def::Scalar)
676 .vtable_direct(&VTABLE)
677 .eq()
678 .copy()
679 .send()
680 .sync()
681 .build()
682 };
683}
684
685#[cfg(feature = "fake")]
686impl<H, T> fake::Dummy<T> for Fuid<H> {
687 fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
688 Fuid::from_thread()
689 }
690}
691
692#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
710#[cfg_attr(
711 feature = "rkyv",
712 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
713)]
714#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
715#[repr(transparent)]
716#[transparent(u64_be)]
717pub struct OptFuid<H>(u64_be, PhantomData<H>);
718
719unsafe impl<T> Zeroable for OptFuid<T> {}
720unsafe impl<T: 'static> Pod for OptFuid<T> {}
721
722impl<T> Clone for OptFuid<T> {
723 fn clone(&self) -> Self {
724 *self
725 }
726}
727
728impl<T> Copy for OptFuid<T> {}
729
730impl<H> OptFuid<H> {
731 pub const NONE: Self = unsafe { core::mem::zeroed() };
733
734 #[inline]
735 pub fn some(id: Fuid<H>) -> Self {
736 Self(id.0, PhantomData)
737 }
738
739 #[inline]
740 pub fn get(self) -> Option<Fuid<H>> {
741 if self.0.to_native() == 0 {
742 None
743 } else {
744 Some(Fuid(self.0, PhantomData))
745 }
746 }
747
748 #[inline]
749 pub fn is_some(self) -> bool {
750 self.0.to_native() != 0
751 }
752
753 #[inline]
754 pub fn is_none(self) -> bool {
755 self.0.to_native() == 0
756 }
757
758 #[inline]
760 pub fn get_raw(self) -> u64 {
761 self.0.to_native()
762 }
763
764 #[inline]
766 pub fn to_bytes(self) -> [u8; 8] {
767 zerocopy::transmute!(self)
768 }
769
770 #[inline]
772 pub fn from_bytes(bytes: [u8; 8]) -> Self {
773 zerocopy::transmute!(bytes)
774 }
775
776 #[inline]
777 pub fn to_be_bytes(self) -> [u8; 8] {
778 zerocopy::transmute!(self)
779 }
780
781 #[inline]
782 pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
783 zerocopy::transmute!(bytes)
784 }
785}
786
787impl<T> Default for OptFuid<T> {
788 fn default() -> Self {
789 Self::NONE
790 }
791}
792
793impl<H> From<Fuid<H>> for OptFuid<H> {
794 fn from(id: Fuid<H>) -> Self {
795 Self::some(id)
796 }
797}
798
799impl<H> From<Option<Fuid<H>>> for OptFuid<H> {
800 fn from(opt: Option<Fuid<H>>) -> Self {
801 match opt {
802 Some(id) => Self::some(id),
803 None => Self::NONE,
804 }
805 }
806}
807
808impl<H> From<OptFuid<H>> for Option<Fuid<H>> {
809 fn from(opt: OptFuid<H>) -> Self {
810 opt.get()
811 }
812}
813
814impl<T> PartialEq for OptFuid<T> {
815 fn eq(&self, other: &Self) -> bool {
816 self.0 == other.0
817 }
818}
819
820impl<T> Eq for OptFuid<T> {}
821
822impl<T> PartialOrd for OptFuid<T> {
823 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
824 Some(self.cmp(other))
825 }
826}
827
828impl<T> Ord for OptFuid<T> {
829 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
830 self.0.cmp(&other.0)
831 }
832}
833
834impl<T> core::hash::Hash for OptFuid<T> {
835 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
836 self.0.hash(state);
837 }
838}
839
840impl<T> PartialEq<Fuid<T>> for OptFuid<T> {
841 fn eq(&self, other: &Fuid<T>) -> bool {
842 self.0 == other.0
843 }
844}
845
846impl<T> PartialEq<OptFuid<T>> for Fuid<T> {
847 fn eq(&self, other: &OptFuid<T>) -> bool {
848 self.0 == other.0
849 }
850}
851
852impl<T> AsRef<[u8; 8]> for OptFuid<T> {
853 fn as_ref(&self) -> &[u8; 8] {
854 zerocopy::transmute_ref!(self)
855 }
856}
857
858impl<H> Debug for OptFuid<H> {
859 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
860 match self.0.to_native() {
861 0 => write!(f, "OptFuid(None)"),
862 id => {
863 let id = format!("{id:#X}");
864 f.debug_tuple("OptFuid").field(&id).finish()
865 }
866 }
867 }
868}
869
870impl<H: IdHasher> Rapira for OptFuid<H> {
871 const STATIC_SIZE: Option<usize> = Some(8);
872 const MIN_SIZE: usize = 8;
873
874 #[inline]
875 fn size(&self) -> usize {
876 8
877 }
878
879 #[inline]
880 fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
881 if slice.len() < 8 {
882 return Err(RapiraError::SliceLen);
883 }
884 *slice = unsafe { slice.get_unchecked(8..) };
885 Ok(())
886 }
887
888 #[inline]
889 fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
890 where
891 Self: Sized,
892 {
893 let bytes = <[u8; 8]>::from_slice(slice)?;
894 Ok(Self::from_bytes(bytes))
895 }
896
897 #[inline]
898 fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
899 let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
900 bytes.convert_to_bytes(slice, cursor);
901 }
902
903 #[inline]
904 fn convert_to_bytes_ctx(
905 &self,
906 slice: &mut [u8],
907 cursor: &mut usize,
908 flags: rapira::RapiraFlags,
909 ) {
910 if flags.has(crate::ID_ENC_FLAG) && self.is_some() {
911 let u: u64 = zerocopy::transmute!(*self);
912 let id = H::encrypt(u);
913 id.convert_to_bytes(slice, cursor);
914 } else {
915 self.convert_to_bytes(slice, cursor)
916 }
917 }
918
919 #[inline]
920 fn from_slice_ctx(slice: &mut &[u8], flags: rapira::RapiraFlags) -> rapira::Result<Self>
921 where
922 Self: Sized,
923 {
924 if flags.has(crate::ID_ENC_FLAG) {
925 let bytes = <[u8; 8]>::from_slice(slice)?;
926 if bytes == [0u8; 8] {
927 Ok(Self::NONE)
928 } else {
929 let id = u64::from_le_bytes(bytes);
930 let id = H::decrypt(id);
931 Ok(zerocopy::transmute!(id))
932 }
933 } else {
934 Self::from_slice(slice)
935 }
936 }
937}
938
939impl Rapira for OptFuid<()> {
940 const STATIC_SIZE: Option<usize> = Some(8);
941 const MIN_SIZE: usize = 8;
942
943 #[inline]
944 fn size(&self) -> usize {
945 8
946 }
947
948 #[inline]
949 fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
950 if slice.len() < 8 {
951 return Err(RapiraError::SliceLen);
952 }
953 *slice = unsafe { slice.get_unchecked(8..) };
954 Ok(())
955 }
956
957 #[inline]
958 fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
959 where
960 Self: Sized,
961 {
962 let bytes = <[u8; 8]>::from_slice(slice)?;
963 Ok(Self::from_bytes(bytes))
964 }
965
966 #[inline]
967 fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
968 let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
969 bytes.convert_to_bytes(slice, cursor);
970 }
971}
972
973impl<H> GetType for OptFuid<H> {
974 const TYPE: Typ = Typ::Fuid;
975}
976
977#[cfg(feature = "std")]
978impl<H: IdHasher> Serialize for OptFuid<H> {
979 fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
980 match self.get() {
981 Some(id) => serializer.serialize_str(&id.ser()),
982 None => serializer.serialize_none(),
983 }
984 }
985}
986
987#[cfg(feature = "std")]
988impl<'de, H: IdHasher> Deserialize<'de> for OptFuid<H> {
989 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
990 where
991 D: Deserializer<'de>,
992 {
993 use serde::de::Error;
994 let s: Option<&str> = Deserialize::deserialize(deserializer)?;
995 match s {
996 Some(s) => {
997 let id = Fuid::<H>::deser(s).map_err(|err| {
998 tracing::error!("id value error: {err}");
999 D::Error::custom("id value error")
1000 })?;
1001 Ok(Self::some(id))
1002 }
1003 None => Ok(Self::NONE),
1004 }
1005 }
1006}
1007
1008#[cfg(feature = "std")]
1009impl Serialize for OptFuid<()> {
1010 fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
1011 match self.get() {
1012 Some(id) => serializer.serialize_str(&id.ser()),
1013 None => serializer.serialize_none(),
1014 }
1015 }
1016}
1017
1018#[cfg(feature = "std")]
1019impl<'de> Deserialize<'de> for OptFuid<()> {
1020 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
1021 where
1022 D: Deserializer<'de>,
1023 {
1024 use serde::de::Error;
1025 let s: Option<&str> = Deserialize::deserialize(deserializer)?;
1026 match s {
1027 Some(s) => {
1028 let id = Fuid::<()>::deser(s).map_err(|err| {
1029 tracing::error!("id value error: {err}");
1030 D::Error::custom("id value error")
1031 })?;
1032 Ok(Self::some(id))
1033 }
1034 None => Ok(Self::NONE),
1035 }
1036 }
1037}
1038
1039#[cfg(feature = "ts-rs")]
1040impl<H> ts_rs::TS for OptFuid<H> {
1041 type WithoutGenerics = OptFuid<()>;
1042 type OptionInnerType = Self;
1043 fn name(_: &ts_rs::Config) -> String {
1044 "OptFuid".to_owned()
1045 }
1046 fn decl_concrete(c: &ts_rs::Config) -> String {
1047 format!("type OptFuid = {};", Self::inline(c))
1048 }
1049 fn decl(c: &ts_rs::Config) -> String {
1050 let inline = <OptFuid<()> as ::ts_rs::TS>::inline(c);
1051 format!("type OptFuid = {inline};")
1052 }
1053 fn inline(_: &ts_rs::Config) -> String {
1054 "string | null".to_owned()
1055 }
1056 fn inline_flattened(c: &ts_rs::Config) -> String {
1057 panic!("{} cannot be flattened", Self::name(c))
1058 }
1059 fn output_path() -> Option<std::path::PathBuf> {
1060 Some(std::path::PathBuf::from("opt_fuid.ts"))
1061 }
1062}
1063
1064#[cfg(feature = "facet")]
1065unsafe impl<'facet, H: 'static> facet::Facet<'facet> for OptFuid<H> {
1066 const SHAPE: &'static facet::Shape = &const {
1067 const VTABLE: facet::VTableDirect = facet::vtable_direct!(OptFuid<()> =>
1068 Debug,
1069 Hash,
1070 PartialEq,
1071 PartialOrd,
1072 Ord,
1073 );
1074
1075 facet::ShapeBuilder::for_sized::<OptFuid<H>>("OptFuid")
1076 .ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
1077 repr: facet::Repr::transparent(),
1078 kind: facet::StructKind::TupleStruct,
1079 fields: &const {
1080 [facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
1081 },
1082 })))
1083 .inner(<u64 as facet::Facet>::SHAPE)
1084 .def(facet::Def::Scalar)
1085 .vtable_direct(&VTABLE)
1086 .eq()
1087 .copy()
1088 .send()
1089 .sync()
1090 .build()
1091 };
1092}
1093
1094#[cfg(feature = "fake")]
1095impl<H, T> fake::Dummy<T> for OptFuid<H> {
1096 fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
1097 Self::some(Fuid::from_thread())
1098 }
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103 use super::*;
1104 use crate::enc::Cipher;
1105
1106 #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
1107 pub struct Hasher;
1108
1109 impl IdHasher for Hasher {
1110 const HASHER: Cipher = Cipher::new(
1111 "_mKbKGF2IrkGvIJvl97HuCgWjgt6QRZ7Ye8DHBQ2anvyi18BdMz8uN6Ej3YJApooY6qDu0obqq4",
1112 );
1113 }
1114
1115 #[test]
1116 fn test_fuid_creation() {
1117 let fuid = Fuid::<Hasher>::with_inst_thread(1, 2);
1118 assert_eq!(fuid.instance_id(), 1);
1119 assert_eq!(fuid.thread_id(), 2);
1120
1121 let fuid = Fuid::<Hasher>::with_shard(55);
1122 assert_eq!(fuid.shard_id(), 55);
1123 }
1124
1125 #[test]
1126 fn test_fuid_ser_deser() {
1127 let fuid = Fuid::<Hasher>::with_shard(111);
1128 let id_str = fuid.ser();
1129 #[allow(clippy::unwrap_used)]
1130 let deserialized_fuid = Fuid::<Hasher>::deser(&id_str).unwrap();
1131 assert_eq!(fuid, deserialized_fuid);
1132 }
1133
1134 #[test]
1137 fn test_opt_fuid_none() {
1138 let opt = OptFuid::<Hasher>::NONE;
1139 assert!(opt.is_none());
1140 assert!(!opt.is_some());
1141 assert_eq!(opt.get(), None);
1142 assert_eq!(opt.to_bytes(), [0u8; 8]);
1143 }
1144
1145 #[test]
1146 fn test_opt_fuid_some() {
1147 let fuid = Fuid::<Hasher>::with_shard(1);
1148 let opt = OptFuid::some(fuid);
1149 assert!(opt.is_some());
1150 assert!(!opt.is_none());
1151 assert_eq!(opt.get(), Some(fuid));
1152 }
1153
1154 #[test]
1155 fn test_opt_fuid_conversions() {
1156 let fuid = Fuid::<Hasher>::with_shard(1);
1157
1158 let opt: OptFuid<Hasher> = fuid.into();
1159 assert_eq!(opt.get(), Some(fuid));
1160
1161 let opt: OptFuid<Hasher> = None.into();
1162 assert!(opt.is_none());
1163
1164 let opt: OptFuid<Hasher> = Some(fuid).into();
1165 let back: Option<Fuid<Hasher>> = opt.into();
1166 assert_eq!(back, Some(fuid));
1167 }
1168
1169 #[test]
1170 fn test_opt_fuid_size() {
1171 assert_eq!(size_of::<OptFuid<Hasher>>(), 8);
1172 assert_eq!(size_of::<OptFuid<Hasher>>(), size_of::<Fuid<Hasher>>());
1173 }
1174
1175 #[test]
1176 fn test_opt_fuid_default() {
1177 let opt = OptFuid::<Hasher>::default();
1178 assert!(opt.is_none());
1179 }
1180
1181 #[test]
1182 fn test_opt_fuid_eq_with_fuid() {
1183 let fuid = Fuid::<Hasher>::with_shard(1);
1184 let opt = OptFuid::some(fuid);
1185 assert_eq!(opt, fuid);
1186 assert_eq!(fuid, opt);
1187 }
1188
1189 #[test]
1192 fn test_fuid_ctx_roundtrip() {
1193 let id = Fuid::<Hasher>::with_shard(1);
1194 let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
1195
1196 let encrypted = rapira::serialize_ctx(&id, flags);
1197 let plain = rapira::serialize(&id);
1198 assert_ne!(encrypted, plain);
1199
1200 let decoded: Fuid<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
1201 assert_eq!(decoded, id);
1202 }
1203
1204 #[test]
1205 fn test_fuid_ctx_no_flag_same_as_plain() {
1206 let id = Fuid::<Hasher>::with_shard(1);
1207 let plain = rapira::serialize(&id);
1208 let ctx_none = rapira::serialize_ctx(&id, rapira::RapiraFlags::NONE);
1209 assert_eq!(plain, ctx_none);
1210 }
1211
1212 #[test]
1213 fn test_opt_fuid_ctx_none_roundtrip() {
1214 let opt = OptFuid::<Hasher>::NONE;
1215 let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
1216 let bytes = rapira::serialize_ctx(&opt, flags);
1217 assert_eq!(bytes, [0u8; 8]);
1218 let decoded: OptFuid<Hasher> = rapira::deserialize_ctx(&bytes, flags).unwrap();
1219 assert!(decoded.is_none());
1220 }
1221
1222 #[test]
1223 fn test_opt_fuid_ctx_some_roundtrip() {
1224 let id = Fuid::<Hasher>::with_shard(1);
1225 let opt = OptFuid::some(id);
1226 let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
1227
1228 let encrypted = rapira::serialize_ctx(&opt, flags);
1229 let plain = rapira::serialize(&opt);
1230 assert_ne!(encrypted, plain);
1231
1232 let decoded: OptFuid<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
1233 assert_eq!(decoded.get(), Some(id));
1234 }
1235}