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