Skip to main content

armour_core/
id64.rs

1use core::{hash::Hash, str::FromStr};
2use std::{fmt::Debug, marker::PhantomData};
3
4use bytemuck::{Pod, TransparentWrapper, Zeroable};
5use rapira::{Rapira, RapiraError};
6use rend::u64_be;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
9
10use crate::{
11    IdStr, Typ,
12    enc::IdHasher,
13    error::ArmourError,
14    get_type::GetType,
15    key_part::{KeyPart, SEQ64_BITS},
16    key_type::KeyType,
17    num_ops::g8bits,
18};
19
20type Result<T, E = ArmourError> = core::result::Result<T, E>;
21
22/// - 64bit sequence id
23/// - Blowfish for encryption (64bit block)
24/// - ZBASE32 for encoding into 13 length string
25#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
26#[cfg_attr(
27    feature = "rkyv",
28    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
29)]
30#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
31#[repr(transparent)]
32#[transparent(u64_be)]
33pub struct Id64<T>(pub u64_be, PhantomData<T>);
34
35unsafe impl<T> Zeroable for Id64<T> {}
36unsafe impl<T: 'static> Pod for Id64<T> {}
37
38impl<T> Clone for Id64<T> {
39    fn clone(&self) -> Self {
40        *self
41    }
42}
43
44impl<T> Copy for Id64<T> {}
45
46impl<T> PartialOrd for Id64<T> {
47    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
48        Some(self.cmp(other))
49    }
50}
51
52impl<T> Ord for Id64<T> {
53    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
54        self.0.cmp(&other.0)
55    }
56}
57
58impl<T> PartialEq for Id64<T> {
59    fn eq(&self, other: &Self) -> bool {
60        self.0 == other.0
61    }
62}
63
64impl<T> Eq for Id64<T> {}
65
66impl<T> PartialEq<[u8; 8]> for Id64<T> {
67    fn eq(&self, other: &[u8; 8]) -> bool {
68        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
69        bytes == other
70    }
71}
72
73impl<T> PartialEq<Id64<T>> for [u8; 8] {
74    fn eq(&self, other: &Id64<T>) -> bool {
75        let bytes: &[u8; 8] = zerocopy::transmute_ref!(other);
76        bytes == self
77    }
78}
79
80impl<T> PartialEq<Id64<T>> for Option<Id64<T>> {
81    fn eq(&self, other: &Id64<T>) -> bool {
82        match self {
83            Some(id) => id == other,
84            None => false,
85        }
86    }
87}
88
89impl<T> PartialEq<Option<Id64<T>>> for Id64<T> {
90    fn eq(&self, other: &Option<Id64<T>>) -> bool {
91        match other {
92            Some(id) => id == self,
93            None => false,
94        }
95    }
96}
97
98impl<T> AsRef<Self> for Id64<T> {
99    fn as_ref(&self) -> &Self {
100        self
101    }
102}
103
104impl<T> AsRef<[u8; 8]> for Id64<T> {
105    fn as_ref(&self) -> &[u8; 8] {
106        zerocopy::transmute_ref!(self)
107    }
108}
109
110impl<T> Id64<T> {
111    /// get LE u64
112    #[inline]
113    pub fn get(self) -> u64 {
114        self.0.to_native()
115    }
116
117    /// Create a new ID64 from a native (LE) u64.
118    #[inline]
119    pub fn new(id: u64) -> Self {
120        Id64(u64_be::from_native(id), PhantomData)
121    }
122
123    #[inline]
124    pub fn to_le_bytes(self) -> [u8; 8] {
125        self.0.to_native().to_le_bytes()
126    }
127
128    #[inline]
129    pub fn to_be_bytes(self) -> [u8; 8] {
130        zerocopy::transmute!(self)
131    }
132
133    #[inline]
134    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
135        zerocopy::transmute!(bytes)
136    }
137
138    /// to BE bytes
139    #[inline]
140    pub fn to_bytes(self) -> [u8; 8] {
141        zerocopy::transmute!(self)
142    }
143
144    /// from BE bytes
145    #[inline]
146    pub fn from_bytes(bytes: [u8; 8]) -> Self {
147        zerocopy::transmute!(bytes)
148    }
149
150    /// to BE u64
151    #[inline]
152    pub fn to_u64(self) -> u64 {
153        zerocopy::transmute!(self)
154    }
155
156    /// from BE u64
157    #[inline]
158    pub fn from_u64(bytes: u64) -> Self {
159        zerocopy::transmute!(bytes)
160    }
161
162    /// get native (LE) u32
163    #[inline]
164    pub fn get_u32(self) -> Option<u32> {
165        let native = self.0.to_native();
166        if native > u32::MAX as u64 {
167            None
168        } else {
169            Some(native as u32)
170        }
171    }
172
173    #[inline]
174    pub fn increment(mut self) -> Self {
175        self.0 += 1;
176        self
177    }
178
179    /// group_id for this Id64
180    #[inline]
181    pub fn group_id(&self) -> u32 {
182        let id = self.get();
183        g8bits(id, SEQ64_BITS)
184    }
185}
186
187impl<T: IdHasher> Id64<T> {
188    #[inline]
189    pub fn deser(s: &str) -> Result<Id64<T>> {
190        let v = T::deser(s)?;
191        Ok(zerocopy::transmute!(v))
192    }
193
194    #[inline]
195    pub fn ser(self) -> IdStr {
196        let u: u64 = zerocopy::transmute!(self);
197        T::ser(u)
198    }
199}
200
201impl<T: IdHasher> TryFrom<&str> for Id64<T> {
202    type Error = ArmourError;
203    fn try_from(val: &str) -> Result<Id64<T>> {
204        Self::deser(val)
205    }
206}
207
208impl TryFrom<&str> for Id64<()> {
209    type Error = ArmourError;
210    fn try_from(val: &str) -> Result<Self> {
211        let val = u64::from_str(val)?;
212        Ok(Self(u64_be::from_native(val), PhantomData))
213    }
214}
215
216impl<T: IdHasher> FromStr for Id64<T> {
217    type Err = ArmourError;
218    fn from_str(s: &str) -> Result<Self> {
219        Self::deser(s)
220    }
221}
222
223impl FromStr for Id64<()> {
224    type Err = ArmourError;
225    fn from_str(s: &str) -> Result<Self> {
226        let val = u64::from_str(s)?;
227        Ok(Self(u64_be::from_native(val), PhantomData))
228    }
229}
230
231impl<T> From<Id64<T>> for u64 {
232    #[inline(always)]
233    fn from(id: Id64<T>) -> Self {
234        id.get()
235    }
236}
237
238impl<T: IdHasher> std::fmt::Display for Id64<T> {
239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240        write!(f, "{}", self.ser())
241    }
242}
243
244impl<T> Debug for Id64<T> {
245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246        f.debug_tuple("ID").field(&self.0).finish()
247    }
248}
249
250impl<T> Hash for Id64<T> {
251    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
252        self.0.hash(state);
253    }
254}
255
256#[cfg(feature = "std")]
257impl<T: IdHasher> Serialize for Id64<T> {
258    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
259        let s = self.ser();
260        serializer.serialize_str(&s)
261    }
262}
263
264#[cfg(feature = "std")]
265impl<'de, T: IdHasher> Deserialize<'de> for Id64<T> {
266    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
267    where
268        D: Deserializer<'de>,
269    {
270        use serde::de::Error;
271        let s: &str = Deserialize::deserialize(deserializer)?;
272        let a = Id64::<T>::deser(s).map_err(|_| D::Error::custom("id value error"))?;
273        Ok(a)
274    }
275}
276
277#[cfg(feature = "std")]
278impl Serialize for Id64<()> {
279    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
280        let s = self.get();
281        serializer.serialize_u64(s)
282    }
283}
284
285#[cfg(feature = "std")]
286impl<'de> Deserialize<'de> for Id64<()> {
287    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
288    where
289        D: Deserializer<'de>,
290    {
291        let s: u64 = Deserialize::deserialize(deserializer)?;
292        Ok(Id64(u64_be::from_native(s), PhantomData))
293    }
294}
295
296impl<T: IdHasher> Rapira for Id64<T> {
297    const STATIC_SIZE: Option<usize> = Some(8);
298    const MIN_SIZE: usize = 8;
299
300    #[inline]
301    fn size(&self) -> usize {
302        8
303    }
304
305    #[inline]
306    fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
307    where
308        Self: Sized,
309    {
310        let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;
311
312        if bytes == [0u8; 8] {
313            return Err(RapiraError::NonZero);
314        }
315
316        *slice = unsafe { slice.get_unchecked(8..) };
317        Ok(())
318    }
319
320    #[inline]
321    fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
322    where
323        Self: Sized,
324    {
325        let bytes = <[u8; 8]>::from_slice(slice)?;
326        let id = Self::from_bytes(bytes);
327        Ok(id)
328    }
329
330    #[inline]
331    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
332        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
333        bytes.convert_to_bytes(slice, cursor);
334    }
335
336    #[inline]
337    fn convert_to_bytes_ctx(
338        &self,
339        slice: &mut [u8],
340        cursor: &mut usize,
341        flags: rapira::RapiraFlags,
342    ) {
343        if flags.has(crate::ID_ENC_FLAG) {
344            let u: u64 = zerocopy::transmute!(*self);
345            let id = T::encrypt(u);
346            id.convert_to_bytes(slice, cursor);
347        } else {
348            self.convert_to_bytes(slice, cursor)
349        }
350    }
351
352    #[inline]
353    fn from_slice_ctx(slice: &mut &[u8], flags: rapira::RapiraFlags) -> rapira::Result<Self>
354    where
355        Self: Sized,
356    {
357        if flags.has(crate::ID_ENC_FLAG) {
358            let id = u64::from_slice(slice)?;
359            let id = T::decrypt(id);
360            Ok(zerocopy::transmute!(id))
361        } else {
362            Self::from_slice(slice)
363        }
364    }
365}
366
367impl Rapira for Id64<()> {
368    const STATIC_SIZE: Option<usize> = Some(8);
369    const MIN_SIZE: usize = 8;
370
371    #[inline]
372    fn size(&self) -> usize {
373        8
374    }
375
376    #[inline]
377    fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
378    where
379        Self: Sized,
380    {
381        let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;
382
383        if bytes == [0u8; 8] {
384            return Err(RapiraError::NonZero);
385        }
386
387        *slice = unsafe { slice.get_unchecked(8..) };
388        Ok(())
389    }
390
391    #[inline]
392    fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
393    where
394        Self: Sized,
395    {
396        let bytes = <[u8; 8]>::from_slice(slice)?;
397        let id = Self::from_bytes(bytes);
398        Ok(id)
399    }
400
401    #[inline]
402    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
403        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
404        bytes.convert_to_bytes(slice, cursor);
405    }
406}
407
408impl<T> GetType for Id64<T> {
409    const TYPE: Typ = Typ::Id64;
410}
411
412impl<H> KeyPart for Id64<H> {
413    const TY: KeyType = KeyType::Fuid;
414    const PREFIX_BITS: u32 = SEQ64_BITS;
415}
416
417#[cfg(feature = "ts-rs")]
418impl<T> ts_rs::TS for Id64<T> {
419    type WithoutGenerics = Id64<()>;
420    type OptionInnerType = Self;
421    fn name(_: &ts_rs::Config) -> String {
422        "Id64".to_owned()
423    }
424    fn decl_concrete(c: &ts_rs::Config) -> String {
425        format!("type {} = {};", Self::name(c), Self::inline(c))
426    }
427    fn decl(c: &ts_rs::Config) -> String {
428        let inline = <Id64<()> as ::ts_rs::TS>::inline(c);
429        format!("type {} = {};", Self::name(c), inline)
430    }
431    fn inline(_: &ts_rs::Config) -> String {
432        "string".to_owned()
433    }
434    fn inline_flattened(c: &ts_rs::Config) -> String {
435        panic!("{} cannot be flattened", Self::name(c))
436    }
437    fn output_path() -> Option<std::path::PathBuf> {
438        Some(std::path::PathBuf::from("id64.ts"))
439    }
440}
441
442#[cfg(feature = "facet")]
443unsafe impl<'facet, T: 'static> facet::Facet<'facet> for Id64<T> {
444    const SHAPE: &'static facet::Shape = &const {
445        const VTABLE: facet::VTableDirect = facet::vtable_direct!(Id64<()> =>
446            Debug,
447            Hash,
448            PartialEq,
449            PartialOrd,
450            Ord,
451        );
452
453        facet::ShapeBuilder::for_sized::<Id64<T>>("Id64")
454            .ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
455                repr: facet::Repr::transparent(),
456                kind: facet::StructKind::TupleStruct,
457                fields: &const {
458                    [facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
459                },
460            })))
461            .inner(<u64 as facet::Facet>::SHAPE)
462            .def(facet::Def::Scalar)
463            .vtable_direct(&VTABLE)
464            .eq()
465            .copy()
466            .send()
467            .sync()
468            .build()
469    };
470}
471
472#[cfg(feature = "fake")]
473impl<H, T> fake::Dummy<T> for Id64<H> {
474    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
475        use fake::Fake;
476
477        let u = (0..100_000_000).fake::<u64>();
478        Self::new(u)
479    }
480}
481
482// ---------------------------------------------------------------------------
483// OptId64
484// ---------------------------------------------------------------------------
485
486/// Optional 64-bit ID with the same 8-byte representation as [`Id64`].
487///
488/// Zero (`[0u8; 8]`) is interpreted as `None`. Use in zerocopy-compatible
489/// structs where `Option<Id64>` would break alignment (16+ bytes).
490///
491/// ```ignore
492/// #[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Copy, Clone)]
493/// #[repr(C)]
494/// struct Follower {
495///     follow: Id64<H>,       // 8 bytes
496///     follower: OptId64<H>,  // 8 bytes, zero = None
497/// }
498/// ```
499#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
500#[cfg_attr(
501    feature = "rkyv",
502    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
503)]
504#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
505#[repr(transparent)]
506#[transparent(u64_be)]
507pub struct OptId64<T>(u64_be, PhantomData<T>);
508
509unsafe impl<T> Zeroable for OptId64<T> {}
510unsafe impl<T: 'static> Pod for OptId64<T> {}
511
512impl<T> Clone for OptId64<T> {
513    fn clone(&self) -> Self {
514        *self
515    }
516}
517
518impl<T> Copy for OptId64<T> {}
519
520impl<T> OptId64<T> {
521    // SAFETY: OptId64 is Zeroable — all-zero bytes is valid and means None.
522    pub const NONE: Self = unsafe { core::mem::zeroed() };
523
524    #[inline]
525    pub fn some(id: Id64<T>) -> Self {
526        Self(id.0, PhantomData)
527    }
528
529    #[inline]
530    pub fn get(self) -> Option<Id64<T>> {
531        if self.0.to_native() == 0 {
532            None
533        } else {
534            Some(Id64(self.0, PhantomData))
535        }
536    }
537
538    #[inline]
539    pub fn is_some(self) -> bool {
540        self.0.to_native() != 0
541    }
542
543    #[inline]
544    pub fn is_none(self) -> bool {
545        self.0.to_native() == 0
546    }
547
548    /// get LE u64 (0 for None)
549    #[inline]
550    pub fn get_raw(self) -> u64 {
551        self.0.to_native()
552    }
553
554    /// to BE bytes
555    #[inline]
556    pub fn to_bytes(self) -> [u8; 8] {
557        zerocopy::transmute!(self)
558    }
559
560    /// from BE bytes
561    #[inline]
562    pub fn from_bytes(bytes: [u8; 8]) -> Self {
563        zerocopy::transmute!(bytes)
564    }
565
566    #[inline]
567    pub fn to_be_bytes(self) -> [u8; 8] {
568        zerocopy::transmute!(self)
569    }
570
571    #[inline]
572    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
573        zerocopy::transmute!(bytes)
574    }
575}
576
577impl<T> Default for OptId64<T> {
578    fn default() -> Self {
579        Self::NONE
580    }
581}
582
583impl<T> From<Id64<T>> for OptId64<T> {
584    fn from(id: Id64<T>) -> Self {
585        Self::some(id)
586    }
587}
588
589impl<T> From<Option<Id64<T>>> for OptId64<T> {
590    fn from(opt: Option<Id64<T>>) -> Self {
591        match opt {
592            Some(id) => Self::some(id),
593            None => Self::NONE,
594        }
595    }
596}
597
598impl<T> From<OptId64<T>> for Option<Id64<T>> {
599    fn from(opt: OptId64<T>) -> Self {
600        opt.get()
601    }
602}
603
604impl<T> PartialEq for OptId64<T> {
605    fn eq(&self, other: &Self) -> bool {
606        self.0 == other.0
607    }
608}
609
610impl<T> Eq for OptId64<T> {}
611
612impl<T> PartialOrd for OptId64<T> {
613    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
614        Some(self.cmp(other))
615    }
616}
617
618impl<T> Ord for OptId64<T> {
619    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
620        self.0.cmp(&other.0)
621    }
622}
623
624impl<T> Hash for OptId64<T> {
625    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
626        self.0.hash(state);
627    }
628}
629
630impl<T> PartialEq<Id64<T>> for OptId64<T> {
631    fn eq(&self, other: &Id64<T>) -> bool {
632        self.0 == other.0
633    }
634}
635
636impl<T> PartialEq<OptId64<T>> for Id64<T> {
637    fn eq(&self, other: &OptId64<T>) -> bool {
638        self.0 == other.0
639    }
640}
641
642impl<T> AsRef<[u8; 8]> for OptId64<T> {
643    fn as_ref(&self) -> &[u8; 8] {
644        zerocopy::transmute_ref!(self)
645    }
646}
647
648impl<T> Debug for OptId64<T> {
649    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
650        match self.0.to_native() {
651            0 => write!(f, "OptID(None)"),
652            _ => f.debug_tuple("OptID").field(&self.0).finish(),
653        }
654    }
655}
656
657impl<T: IdHasher> Rapira for OptId64<T> {
658    const STATIC_SIZE: Option<usize> = Some(8);
659    const MIN_SIZE: usize = 8;
660
661    #[inline]
662    fn size(&self) -> usize {
663        8
664    }
665
666    #[inline]
667    fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
668    where
669        Self: Sized,
670    {
671        if slice.len() < 8 {
672            return Err(RapiraError::SliceLen);
673        }
674        *slice = unsafe { slice.get_unchecked(8..) };
675        Ok(())
676    }
677
678    #[inline]
679    fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
680    where
681        Self: Sized,
682    {
683        let bytes = <[u8; 8]>::from_slice(slice)?;
684        Ok(Self::from_bytes(bytes))
685    }
686
687    #[inline]
688    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
689        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
690        bytes.convert_to_bytes(slice, cursor);
691    }
692
693    #[inline]
694    fn convert_to_bytes_ctx(
695        &self,
696        slice: &mut [u8],
697        cursor: &mut usize,
698        flags: rapira::RapiraFlags,
699    ) {
700        if flags.has(crate::ID_ENC_FLAG) && self.is_some() {
701            let u: u64 = zerocopy::transmute!(*self);
702            let id = T::encrypt(u);
703            id.convert_to_bytes(slice, cursor);
704        } else {
705            self.convert_to_bytes(slice, cursor)
706        }
707    }
708
709    #[inline]
710    fn from_slice_ctx(
711        slice: &mut &[u8],
712        flags: rapira::RapiraFlags,
713    ) -> core::result::Result<Self, rapira::RapiraError>
714    where
715        Self: Sized,
716    {
717        if flags.has(crate::ID_ENC_FLAG) {
718            let bytes = <[u8; 8]>::from_slice(slice)?;
719            if bytes == [0u8; 8] {
720                Ok(Self::NONE)
721            } else {
722                let id = u64::from_le_bytes(bytes);
723                let id = T::decrypt(id);
724                Ok(zerocopy::transmute!(id))
725            }
726        } else {
727            Self::from_slice(slice)
728        }
729    }
730}
731
732impl Rapira for OptId64<()> {
733    const STATIC_SIZE: Option<usize> = Some(8);
734    const MIN_SIZE: usize = 8;
735
736    #[inline]
737    fn size(&self) -> usize {
738        8
739    }
740
741    #[inline]
742    fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
743    where
744        Self: Sized,
745    {
746        if slice.len() < 8 {
747            return Err(RapiraError::SliceLen);
748        }
749        *slice = unsafe { slice.get_unchecked(8..) };
750        Ok(())
751    }
752
753    #[inline]
754    fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
755    where
756        Self: Sized,
757    {
758        let bytes = <[u8; 8]>::from_slice(slice)?;
759        Ok(Self::from_bytes(bytes))
760    }
761
762    #[inline]
763    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
764        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
765        bytes.convert_to_bytes(slice, cursor);
766    }
767}
768
769impl<T> GetType for OptId64<T> {
770    const TYPE: Typ = Typ::Id64;
771}
772
773#[cfg(feature = "std")]
774impl<T: IdHasher> Serialize for OptId64<T> {
775    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
776        match self.get() {
777            Some(id) => serializer.serialize_str(&id.ser()),
778            None => serializer.serialize_none(),
779        }
780    }
781}
782
783#[cfg(feature = "std")]
784impl<'de, T: IdHasher> Deserialize<'de> for OptId64<T> {
785    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
786    where
787        D: Deserializer<'de>,
788    {
789        use serde::de::Error;
790        let s: Option<&str> = Deserialize::deserialize(deserializer)?;
791        match s {
792            Some(s) => {
793                let id = Id64::<T>::deser(s).map_err(|_| D::Error::custom("id value error"))?;
794                Ok(Self::some(id))
795            }
796            None => Ok(Self::NONE),
797        }
798    }
799}
800
801#[cfg(feature = "std")]
802impl Serialize for OptId64<()> {
803    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
804        match self.get() {
805            Some(id) => serializer.serialize_some(&id.get()),
806            None => serializer.serialize_none(),
807        }
808    }
809}
810
811#[cfg(feature = "std")]
812impl<'de> Deserialize<'de> for OptId64<()> {
813    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
814    where
815        D: Deserializer<'de>,
816    {
817        let s: Option<u64> = Deserialize::deserialize(deserializer)?;
818        match s {
819            Some(v) => Ok(Self::some(Id64::new(v))),
820            None => Ok(Self::NONE),
821        }
822    }
823}
824
825#[cfg(feature = "ts-rs")]
826impl<T> ts_rs::TS for OptId64<T> {
827    type WithoutGenerics = OptId64<()>;
828    type OptionInnerType = Self;
829    fn name(_: &ts_rs::Config) -> String {
830        "OptId64".to_owned()
831    }
832    fn decl_concrete(c: &ts_rs::Config) -> String {
833        format!("type OptId64 = {};", Self::inline(c))
834    }
835    fn decl(c: &ts_rs::Config) -> String {
836        let inline = <OptId64<()> as ::ts_rs::TS>::inline(c);
837        format!("type OptId64 = {inline};")
838    }
839    fn inline(_: &ts_rs::Config) -> String {
840        "string | null".to_owned()
841    }
842    fn inline_flattened(c: &ts_rs::Config) -> String {
843        panic!("{} cannot be flattened", Self::name(c))
844    }
845    fn output_path() -> Option<std::path::PathBuf> {
846        Some(std::path::PathBuf::from("opt_id64.ts"))
847    }
848}
849
850#[cfg(feature = "facet")]
851unsafe impl<'facet, T: 'static> facet::Facet<'facet> for OptId64<T> {
852    const SHAPE: &'static facet::Shape = &const {
853        const VTABLE: facet::VTableDirect = facet::vtable_direct!(OptId64<()> =>
854            Debug,
855            Hash,
856            PartialEq,
857            PartialOrd,
858            Ord,
859        );
860
861        facet::ShapeBuilder::for_sized::<OptId64<T>>("OptId64")
862            .ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
863                repr: facet::Repr::transparent(),
864                kind: facet::StructKind::TupleStruct,
865                fields: &const {
866                    [facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
867                },
868            })))
869            .inner(<u64 as facet::Facet>::SHAPE)
870            .def(facet::Def::Scalar)
871            .vtable_direct(&VTABLE)
872            .eq()
873            .copy()
874            .send()
875            .sync()
876            .build()
877    };
878}
879
880#[cfg(feature = "fake")]
881impl<H, T> fake::Dummy<T> for OptId64<H> {
882    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
883        use fake::Fake;
884        let u = (0..100_000_000).fake::<u64>();
885        Self::some(Id64::new(u))
886    }
887}
888
889#[cfg(test)]
890mod tests {
891    use super::*;
892
893    #[test]
894    fn test_increment_basic() {
895        let id = Id64::<()>::new(42);
896        let incremented = id.increment();
897        assert_eq!(incremented.get(), 43);
898    }
899
900    #[test]
901    fn test_increment_multiple() {
902        let id = Id64::<()>::new(100);
903        let id = id.increment();
904        assert_eq!(id.get(), 101);
905        let id = id.increment();
906        assert_eq!(id.get(), 102);
907        let id = id.increment();
908        assert_eq!(id.get(), 103);
909    }
910
911    #[test]
912    fn test_increment_zero() {
913        let id = Id64::<()>::new(0);
914        let incremented = id.increment();
915        assert_eq!(incremented.get(), 1);
916    }
917
918    #[test]
919    fn test_increment_large_value() {
920        let id = Id64::<()>::new(u64::MAX - 1);
921        let incremented = id.increment();
922        assert_eq!(incremented.get(), u64::MAX);
923    }
924
925    #[test]
926    #[cfg(debug_assertions)]
927    #[should_panic(expected = "attempt to add with overflow")]
928    fn test_increment_overflow_debug() {
929        let id = Id64::<()>::new(u64::MAX);
930        let _ = id.increment(); // This will panic in debug mode
931    }
932
933    #[test]
934    #[cfg(not(debug_assertions))]
935    fn test_increment_overflow_release() {
936        let id = Id64::<()>::new(u64::MAX);
937        let incremented = id.increment();
938        // In release mode, this will wrap around to 0
939        assert_eq!(incremented.get(), 0);
940    }
941
942    #[test]
943    fn test_increment_endianness() {
944        // Test that increment works correctly with big-endian representation
945        let id = Id64::<()>::new(255); // 0xFF
946        let incremented = id.increment();
947        assert_eq!(incremented.get(), 256); // 0x100
948
949        // Verify the big-endian bytes are correct
950        let bytes = incremented.to_be_bytes();
951        assert_eq!(bytes, [0, 0, 0, 0, 0, 0, 1, 0]);
952    }
953
954    // -- OptId64 tests --
955
956    #[test]
957    fn test_opt_id64_none() {
958        let opt = OptId64::<()>::NONE;
959        assert!(opt.is_none());
960        assert!(!opt.is_some());
961        assert_eq!(opt.get(), None);
962        assert_eq!(opt.to_bytes(), [0u8; 8]);
963    }
964
965    #[test]
966    fn test_opt_id64_some() {
967        let id = Id64::<()>::new(42);
968        let opt = OptId64::some(id);
969        assert!(opt.is_some());
970        assert!(!opt.is_none());
971        assert_eq!(opt.get(), Some(id));
972    }
973
974    #[test]
975    fn test_opt_id64_conversions() {
976        let id = Id64::<()>::new(42);
977
978        let opt: OptId64<()> = id.into();
979        assert_eq!(opt.get(), Some(id));
980
981        let opt: OptId64<()> = None.into();
982        assert!(opt.is_none());
983
984        let opt: OptId64<()> = Some(id).into();
985        let back: Option<Id64<()>> = opt.into();
986        assert_eq!(back, Some(id));
987    }
988
989    #[test]
990    fn test_opt_id64_size() {
991        assert_eq!(size_of::<OptId64<()>>(), 8);
992        assert_eq!(size_of::<OptId64<()>>(), size_of::<Id64<()>>());
993    }
994
995    #[test]
996    fn test_opt_id64_eq_with_id64() {
997        let id = Id64::<()>::new(42);
998        let opt = OptId64::some(id);
999        assert_eq!(opt, id);
1000        assert_eq!(id, opt);
1001    }
1002
1003    #[test]
1004    fn test_opt_id64_default() {
1005        let opt = OptId64::<()>::default();
1006        assert!(opt.is_none());
1007    }
1008
1009    // -- Rapira _ctx round-trip tests --
1010
1011    use crate::enc::{Cipher, IdHasher};
1012
1013    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
1014    pub struct Hasher;
1015
1016    impl IdHasher for Hasher {
1017        const HASHER: Cipher = Cipher::new(
1018            "_mKbKGF2IrkGvIJvl97HuCgWjgt6QRZ7Ye8DHBQ2anvyi18BdMz8uN6Ej3YJApooY6qDu0obqq4",
1019        );
1020    }
1021
1022    #[test]
1023    fn test_id64_ctx_roundtrip() {
1024        let id = Id64::<Hasher>::new(123456789);
1025        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
1026
1027        let encrypted = rapira::serialize_ctx(&id, flags);
1028        let plain = rapira::serialize(&id);
1029        assert_ne!(encrypted, plain);
1030
1031        let decoded: Id64<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
1032        assert_eq!(decoded, id);
1033    }
1034
1035    #[test]
1036    fn test_id64_ctx_no_flag_same_as_plain() {
1037        let id = Id64::<Hasher>::new(123456789);
1038        let plain = rapira::serialize(&id);
1039        let ctx_none = rapira::serialize_ctx(&id, rapira::RapiraFlags::NONE);
1040        assert_eq!(plain, ctx_none);
1041    }
1042
1043    #[test]
1044    fn test_opt_id64_ctx_none_roundtrip() {
1045        let opt = OptId64::<Hasher>::NONE;
1046        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
1047        let bytes = rapira::serialize_ctx(&opt, flags);
1048        assert_eq!(bytes, [0u8; 8]);
1049        let decoded: OptId64<Hasher> = rapira::deserialize_ctx(&bytes, flags).unwrap();
1050        assert!(decoded.is_none());
1051    }
1052
1053    #[test]
1054    fn test_opt_id64_ctx_some_roundtrip() {
1055        let id = Id64::<Hasher>::new(123456789);
1056        let opt = OptId64::some(id);
1057        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
1058
1059        let encrypted = rapira::serialize_ctx(&opt, flags);
1060        let plain = rapira::serialize(&opt);
1061        assert_ne!(encrypted, plain);
1062
1063        let decoded: OptId64<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
1064        assert_eq!(decoded.get(), Some(id));
1065    }
1066}