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