use core::{hash::Hash, str::FromStr};
use std::{fmt::Debug, marker::PhantomData};
use bytemuck::{Pod, TransparentWrapper, Zeroable};
use rapira::{Rapira, RapiraError};
use rend::u64_be;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::{
IdStr, Typ,
enc::IdHasher,
error::ArmourError,
get_type::GetType,
key_part::{KeyPart, SEQ64_BITS},
key_type::KeyType,
num_ops::g8bits,
};
type Result<T, E = ArmourError> = core::result::Result<T, E>;
#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
#[repr(transparent)]
#[transparent(u64_be)]
pub struct Id64<T>(pub u64_be, PhantomData<T>);
unsafe impl<T> Zeroable for Id64<T> {}
unsafe impl<T: 'static> Pod for Id64<T> {}
impl<T> Clone for Id64<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Id64<T> {}
impl<T> PartialOrd for Id64<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for Id64<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T> PartialEq for Id64<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for Id64<T> {}
impl<T> PartialEq<[u8; 8]> for Id64<T> {
fn eq(&self, other: &[u8; 8]) -> bool {
let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
bytes == other
}
}
impl<T> PartialEq<Id64<T>> for [u8; 8] {
fn eq(&self, other: &Id64<T>) -> bool {
let bytes: &[u8; 8] = zerocopy::transmute_ref!(other);
bytes == self
}
}
impl<T> PartialEq<Id64<T>> for Option<Id64<T>> {
fn eq(&self, other: &Id64<T>) -> bool {
match self {
Some(id) => id == other,
None => false,
}
}
}
impl<T> PartialEq<Option<Id64<T>>> for Id64<T> {
fn eq(&self, other: &Option<Id64<T>>) -> bool {
match other {
Some(id) => id == self,
None => false,
}
}
}
impl<T> AsRef<Self> for Id64<T> {
fn as_ref(&self) -> &Self {
self
}
}
impl<T> AsRef<[u8; 8]> for Id64<T> {
fn as_ref(&self) -> &[u8; 8] {
zerocopy::transmute_ref!(self)
}
}
impl<T> Id64<T> {
#[inline]
pub fn get(self) -> u64 {
self.0.to_native()
}
#[inline]
pub fn new(id: u64) -> Self {
Id64(u64_be::from_native(id), PhantomData)
}
#[inline]
pub fn to_le_bytes(self) -> [u8; 8] {
self.0.to_native().to_le_bytes()
}
#[inline]
pub fn to_be_bytes(self) -> [u8; 8] {
zerocopy::transmute!(self)
}
#[inline]
pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
zerocopy::transmute!(bytes)
}
#[inline]
pub fn to_bytes(self) -> [u8; 8] {
zerocopy::transmute!(self)
}
#[inline]
pub fn from_bytes(bytes: [u8; 8]) -> Self {
zerocopy::transmute!(bytes)
}
#[inline]
pub fn to_u64(self) -> u64 {
zerocopy::transmute!(self)
}
#[inline]
pub fn from_u64(bytes: u64) -> Self {
zerocopy::transmute!(bytes)
}
#[inline]
pub fn get_u32(self) -> Option<u32> {
let native = self.0.to_native();
if native > u32::MAX as u64 {
None
} else {
Some(native as u32)
}
}
#[inline]
pub fn increment(mut self) -> Self {
self.0 += 1;
self
}
#[inline]
pub fn group_id(&self) -> u32 {
let id = self.get();
g8bits(id, SEQ64_BITS)
}
}
impl<T: IdHasher> Id64<T> {
#[inline]
pub fn deser(s: &str) -> Result<Id64<T>> {
let v = T::deser(s)?;
Ok(zerocopy::transmute!(v))
}
#[inline]
pub fn ser(self) -> IdStr {
let u: u64 = zerocopy::transmute!(self);
T::ser(u)
}
}
impl<T: IdHasher> TryFrom<&str> for Id64<T> {
type Error = ArmourError;
fn try_from(val: &str) -> Result<Id64<T>> {
Self::deser(val)
}
}
impl TryFrom<&str> for Id64<()> {
type Error = ArmourError;
fn try_from(val: &str) -> Result<Self> {
let val = u64::from_str(val)?;
Ok(Self(u64_be::from_native(val), PhantomData))
}
}
impl<T: IdHasher> FromStr for Id64<T> {
type Err = ArmourError;
fn from_str(s: &str) -> Result<Self> {
Self::deser(s)
}
}
impl FromStr for Id64<()> {
type Err = ArmourError;
fn from_str(s: &str) -> Result<Self> {
let val = u64::from_str(s)?;
Ok(Self(u64_be::from_native(val), PhantomData))
}
}
impl<T> From<Id64<T>> for u64 {
#[inline(always)]
fn from(id: Id64<T>) -> Self {
id.get()
}
}
impl<T: IdHasher> std::fmt::Display for Id64<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.ser())
}
}
impl<T> Debug for Id64<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ID").field(&self.0).finish()
}
}
impl<T> Hash for Id64<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
#[cfg(feature = "std")]
impl<T: IdHasher> Serialize for Id64<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
let s = self.ser();
serializer.serialize_str(&s)
}
}
#[cfg(feature = "std")]
impl<'de, T: IdHasher> Deserialize<'de> for Id64<T> {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let s: &str = Deserialize::deserialize(deserializer)?;
let a = Id64::<T>::deser(s).map_err(|_| D::Error::custom("id value error"))?;
Ok(a)
}
}
#[cfg(feature = "std")]
impl Serialize for Id64<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
let s = self.get();
serializer.serialize_u64(s)
}
}
#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for Id64<()> {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: u64 = Deserialize::deserialize(deserializer)?;
Ok(Id64(u64_be::from_native(s), PhantomData))
}
}
impl<T: IdHasher> Rapira for Id64<T> {
const STATIC_SIZE: Option<usize> = Some(8);
const MIN_SIZE: usize = 8;
#[inline]
fn size(&self) -> usize {
8
}
#[inline]
fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
where
Self: Sized,
{
let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;
if bytes == [0u8; 8] {
return Err(RapiraError::NonZero);
}
*slice = unsafe { slice.get_unchecked(8..) };
Ok(())
}
#[inline]
fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
where
Self: Sized,
{
let bytes = <[u8; 8]>::from_slice(slice)?;
let id = Self::from_bytes(bytes);
Ok(id)
}
#[inline]
fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
bytes.convert_to_bytes(slice, cursor);
}
#[inline]
fn convert_to_bytes_ctx(
&self,
slice: &mut [u8],
cursor: &mut usize,
flags: rapira::RapiraFlags,
) {
if flags.has(crate::ID_ENC_FLAG) {
let u: u64 = zerocopy::transmute!(*self);
let id = T::encrypt(u);
id.convert_to_bytes(slice, cursor);
} else {
self.convert_to_bytes(slice, cursor)
}
}
#[inline]
fn from_slice_ctx(slice: &mut &[u8], flags: rapira::RapiraFlags) -> rapira::Result<Self>
where
Self: Sized,
{
if flags.has(crate::ID_ENC_FLAG) {
let id = u64::from_slice(slice)?;
let id = T::decrypt(id);
Ok(zerocopy::transmute!(id))
} else {
Self::from_slice(slice)
}
}
}
impl Rapira for Id64<()> {
const STATIC_SIZE: Option<usize> = Some(8);
const MIN_SIZE: usize = 8;
#[inline]
fn size(&self) -> usize {
8
}
#[inline]
fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
where
Self: Sized,
{
let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;
if bytes == [0u8; 8] {
return Err(RapiraError::NonZero);
}
*slice = unsafe { slice.get_unchecked(8..) };
Ok(())
}
#[inline]
fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
where
Self: Sized,
{
let bytes = <[u8; 8]>::from_slice(slice)?;
let id = Self::from_bytes(bytes);
Ok(id)
}
#[inline]
fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
bytes.convert_to_bytes(slice, cursor);
}
}
impl<T> GetType for Id64<T> {
const TYPE: Typ = Typ::Id64;
}
impl<H> KeyPart for Id64<H> {
const TY: KeyType = KeyType::Fuid;
const PREFIX_BITS: u32 = SEQ64_BITS;
}
#[cfg(feature = "ts-rs")]
impl<T> ts_rs::TS for Id64<T> {
type WithoutGenerics = Id64<()>;
type OptionInnerType = Self;
fn name(_: &ts_rs::Config) -> String {
"Id64".to_owned()
}
fn decl_concrete(c: &ts_rs::Config) -> String {
format!("type {} = {};", Self::name(c), Self::inline(c))
}
fn decl(c: &ts_rs::Config) -> String {
let inline = <Id64<()> as ::ts_rs::TS>::inline(c);
format!("type {} = {};", Self::name(c), inline)
}
fn inline(_: &ts_rs::Config) -> String {
"string".to_owned()
}
fn inline_flattened(c: &ts_rs::Config) -> String {
panic!("{} cannot be flattened", Self::name(c))
}
fn output_path() -> Option<std::path::PathBuf> {
Some(std::path::PathBuf::from("id64.ts"))
}
}
#[cfg(feature = "facet")]
unsafe impl<'facet, T: 'static> facet::Facet<'facet> for Id64<T> {
const SHAPE: &'static facet::Shape = &const {
const VTABLE: facet::VTableDirect = facet::vtable_direct!(Id64<()> =>
Debug,
Hash,
PartialEq,
PartialOrd,
Ord,
);
facet::ShapeBuilder::for_sized::<Id64<T>>("Id64")
.ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
repr: facet::Repr::transparent(),
kind: facet::StructKind::TupleStruct,
fields: &const {
[facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
},
})))
.inner(<u64 as facet::Facet>::SHAPE)
.def(facet::Def::Scalar)
.vtable_direct(&VTABLE)
.eq()
.copy()
.send()
.sync()
.build()
};
}
#[cfg(feature = "fake")]
impl<H, T> fake::Dummy<T> for Id64<H> {
fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
use fake::Fake;
let u = (0..100_000_000).fake::<u64>();
Self::new(u)
}
}
#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
#[repr(transparent)]
#[transparent(u64_be)]
pub struct OptId64<T>(u64_be, PhantomData<T>);
unsafe impl<T> Zeroable for OptId64<T> {}
unsafe impl<T: 'static> Pod for OptId64<T> {}
impl<T> Clone for OptId64<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for OptId64<T> {}
impl<T> OptId64<T> {
pub const NONE: Self = unsafe { core::mem::zeroed() };
#[inline]
pub fn some(id: Id64<T>) -> Self {
Self(id.0, PhantomData)
}
#[inline]
pub fn get(self) -> Option<Id64<T>> {
if self.0.to_native() == 0 {
None
} else {
Some(Id64(self.0, PhantomData))
}
}
#[inline]
pub fn is_some(self) -> bool {
self.0.to_native() != 0
}
#[inline]
pub fn is_none(self) -> bool {
self.0.to_native() == 0
}
#[inline]
pub fn get_raw(self) -> u64 {
self.0.to_native()
}
#[inline]
pub fn to_bytes(self) -> [u8; 8] {
zerocopy::transmute!(self)
}
#[inline]
pub fn from_bytes(bytes: [u8; 8]) -> Self {
zerocopy::transmute!(bytes)
}
#[inline]
pub fn to_be_bytes(self) -> [u8; 8] {
zerocopy::transmute!(self)
}
#[inline]
pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
zerocopy::transmute!(bytes)
}
}
impl<T> Default for OptId64<T> {
fn default() -> Self {
Self::NONE
}
}
impl<T> From<Id64<T>> for OptId64<T> {
fn from(id: Id64<T>) -> Self {
Self::some(id)
}
}
impl<T> From<Option<Id64<T>>> for OptId64<T> {
fn from(opt: Option<Id64<T>>) -> Self {
match opt {
Some(id) => Self::some(id),
None => Self::NONE,
}
}
}
impl<T> From<OptId64<T>> for Option<Id64<T>> {
fn from(opt: OptId64<T>) -> Self {
opt.get()
}
}
impl<T> PartialEq for OptId64<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for OptId64<T> {}
impl<T> PartialOrd for OptId64<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for OptId64<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T> Hash for OptId64<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T> PartialEq<Id64<T>> for OptId64<T> {
fn eq(&self, other: &Id64<T>) -> bool {
self.0 == other.0
}
}
impl<T> PartialEq<OptId64<T>> for Id64<T> {
fn eq(&self, other: &OptId64<T>) -> bool {
self.0 == other.0
}
}
impl<T> AsRef<[u8; 8]> for OptId64<T> {
fn as_ref(&self) -> &[u8; 8] {
zerocopy::transmute_ref!(self)
}
}
impl<T> Debug for OptId64<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0.to_native() {
0 => write!(f, "OptID(None)"),
_ => f.debug_tuple("OptID").field(&self.0).finish(),
}
}
}
impl<T: IdHasher> Rapira for OptId64<T> {
const STATIC_SIZE: Option<usize> = Some(8);
const MIN_SIZE: usize = 8;
#[inline]
fn size(&self) -> usize {
8
}
#[inline]
fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
where
Self: Sized,
{
if slice.len() < 8 {
return Err(RapiraError::SliceLen);
}
*slice = unsafe { slice.get_unchecked(8..) };
Ok(())
}
#[inline]
fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
where
Self: Sized,
{
let bytes = <[u8; 8]>::from_slice(slice)?;
Ok(Self::from_bytes(bytes))
}
#[inline]
fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
bytes.convert_to_bytes(slice, cursor);
}
#[inline]
fn convert_to_bytes_ctx(
&self,
slice: &mut [u8],
cursor: &mut usize,
flags: rapira::RapiraFlags,
) {
if flags.has(crate::ID_ENC_FLAG) && self.is_some() {
let u: u64 = zerocopy::transmute!(*self);
let id = T::encrypt(u);
id.convert_to_bytes(slice, cursor);
} else {
self.convert_to_bytes(slice, cursor)
}
}
#[inline]
fn from_slice_ctx(
slice: &mut &[u8],
flags: rapira::RapiraFlags,
) -> core::result::Result<Self, rapira::RapiraError>
where
Self: Sized,
{
if flags.has(crate::ID_ENC_FLAG) {
let bytes = <[u8; 8]>::from_slice(slice)?;
if bytes == [0u8; 8] {
Ok(Self::NONE)
} else {
let id = u64::from_le_bytes(bytes);
let id = T::decrypt(id);
Ok(zerocopy::transmute!(id))
}
} else {
Self::from_slice(slice)
}
}
}
impl Rapira for OptId64<()> {
const STATIC_SIZE: Option<usize> = Some(8);
const MIN_SIZE: usize = 8;
#[inline]
fn size(&self) -> usize {
8
}
#[inline]
fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
where
Self: Sized,
{
if slice.len() < 8 {
return Err(RapiraError::SliceLen);
}
*slice = unsafe { slice.get_unchecked(8..) };
Ok(())
}
#[inline]
fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
where
Self: Sized,
{
let bytes = <[u8; 8]>::from_slice(slice)?;
Ok(Self::from_bytes(bytes))
}
#[inline]
fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
bytes.convert_to_bytes(slice, cursor);
}
}
impl<T> GetType for OptId64<T> {
const TYPE: Typ = Typ::Id64;
}
#[cfg(feature = "std")]
impl<T: IdHasher> Serialize for OptId64<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
match self.get() {
Some(id) => serializer.serialize_str(&id.ser()),
None => serializer.serialize_none(),
}
}
}
#[cfg(feature = "std")]
impl<'de, T: IdHasher> Deserialize<'de> for OptId64<T> {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let s: Option<&str> = Deserialize::deserialize(deserializer)?;
match s {
Some(s) => {
let id = Id64::<T>::deser(s).map_err(|_| D::Error::custom("id value error"))?;
Ok(Self::some(id))
}
None => Ok(Self::NONE),
}
}
}
#[cfg(feature = "std")]
impl Serialize for OptId64<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
match self.get() {
Some(id) => serializer.serialize_some(&id.get()),
None => serializer.serialize_none(),
}
}
}
#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for OptId64<()> {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<u64> = Deserialize::deserialize(deserializer)?;
match s {
Some(v) => Ok(Self::some(Id64::new(v))),
None => Ok(Self::NONE),
}
}
}
#[cfg(feature = "ts-rs")]
impl<T> ts_rs::TS for OptId64<T> {
type WithoutGenerics = OptId64<()>;
type OptionInnerType = Self;
fn name(_: &ts_rs::Config) -> String {
"OptId64".to_owned()
}
fn decl_concrete(c: &ts_rs::Config) -> String {
format!("type OptId64 = {};", Self::inline(c))
}
fn decl(c: &ts_rs::Config) -> String {
let inline = <OptId64<()> as ::ts_rs::TS>::inline(c);
format!("type OptId64 = {inline};")
}
fn inline(_: &ts_rs::Config) -> String {
"string | null".to_owned()
}
fn inline_flattened(c: &ts_rs::Config) -> String {
panic!("{} cannot be flattened", Self::name(c))
}
fn output_path() -> Option<std::path::PathBuf> {
Some(std::path::PathBuf::from("opt_id64.ts"))
}
}
#[cfg(feature = "facet")]
unsafe impl<'facet, T: 'static> facet::Facet<'facet> for OptId64<T> {
const SHAPE: &'static facet::Shape = &const {
const VTABLE: facet::VTableDirect = facet::vtable_direct!(OptId64<()> =>
Debug,
Hash,
PartialEq,
PartialOrd,
Ord,
);
facet::ShapeBuilder::for_sized::<OptId64<T>>("OptId64")
.ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
repr: facet::Repr::transparent(),
kind: facet::StructKind::TupleStruct,
fields: &const {
[facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
},
})))
.inner(<u64 as facet::Facet>::SHAPE)
.def(facet::Def::Scalar)
.vtable_direct(&VTABLE)
.eq()
.copy()
.send()
.sync()
.build()
};
}
#[cfg(feature = "fake")]
impl<H, T> fake::Dummy<T> for OptId64<H> {
fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
use fake::Fake;
let u = (0..100_000_000).fake::<u64>();
Self::some(Id64::new(u))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_increment_basic() {
let id = Id64::<()>::new(42);
let incremented = id.increment();
assert_eq!(incremented.get(), 43);
}
#[test]
fn test_increment_multiple() {
let id = Id64::<()>::new(100);
let id = id.increment();
assert_eq!(id.get(), 101);
let id = id.increment();
assert_eq!(id.get(), 102);
let id = id.increment();
assert_eq!(id.get(), 103);
}
#[test]
fn test_increment_zero() {
let id = Id64::<()>::new(0);
let incremented = id.increment();
assert_eq!(incremented.get(), 1);
}
#[test]
fn test_increment_large_value() {
let id = Id64::<()>::new(u64::MAX - 1);
let incremented = id.increment();
assert_eq!(incremented.get(), u64::MAX);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "attempt to add with overflow")]
fn test_increment_overflow_debug() {
let id = Id64::<()>::new(u64::MAX);
let _ = id.increment(); }
#[test]
#[cfg(not(debug_assertions))]
fn test_increment_overflow_release() {
let id = Id64::<()>::new(u64::MAX);
let incremented = id.increment();
assert_eq!(incremented.get(), 0);
}
#[test]
fn test_increment_endianness() {
let id = Id64::<()>::new(255); let incremented = id.increment();
assert_eq!(incremented.get(), 256);
let bytes = incremented.to_be_bytes();
assert_eq!(bytes, [0, 0, 0, 0, 0, 0, 1, 0]);
}
#[test]
fn test_opt_id64_none() {
let opt = OptId64::<()>::NONE;
assert!(opt.is_none());
assert!(!opt.is_some());
assert_eq!(opt.get(), None);
assert_eq!(opt.to_bytes(), [0u8; 8]);
}
#[test]
fn test_opt_id64_some() {
let id = Id64::<()>::new(42);
let opt = OptId64::some(id);
assert!(opt.is_some());
assert!(!opt.is_none());
assert_eq!(opt.get(), Some(id));
}
#[test]
fn test_opt_id64_conversions() {
let id = Id64::<()>::new(42);
let opt: OptId64<()> = id.into();
assert_eq!(opt.get(), Some(id));
let opt: OptId64<()> = None.into();
assert!(opt.is_none());
let opt: OptId64<()> = Some(id).into();
let back: Option<Id64<()>> = opt.into();
assert_eq!(back, Some(id));
}
#[test]
fn test_opt_id64_size() {
assert_eq!(size_of::<OptId64<()>>(), 8);
assert_eq!(size_of::<OptId64<()>>(), size_of::<Id64<()>>());
}
#[test]
fn test_opt_id64_eq_with_id64() {
let id = Id64::<()>::new(42);
let opt = OptId64::some(id);
assert_eq!(opt, id);
assert_eq!(id, opt);
}
#[test]
fn test_opt_id64_default() {
let opt = OptId64::<()>::default();
assert!(opt.is_none());
}
use crate::enc::{Cipher, IdHasher};
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
pub struct Hasher;
impl IdHasher for Hasher {
const HASHER: Cipher = Cipher::new(
"_mKbKGF2IrkGvIJvl97HuCgWjgt6QRZ7Ye8DHBQ2anvyi18BdMz8uN6Ej3YJApooY6qDu0obqq4",
);
}
#[test]
fn test_id64_ctx_roundtrip() {
let id = Id64::<Hasher>::new(123456789);
let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
let encrypted = rapira::serialize_ctx(&id, flags);
let plain = rapira::serialize(&id);
assert_ne!(encrypted, plain);
let decoded: Id64<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
assert_eq!(decoded, id);
}
#[test]
fn test_id64_ctx_no_flag_same_as_plain() {
let id = Id64::<Hasher>::new(123456789);
let plain = rapira::serialize(&id);
let ctx_none = rapira::serialize_ctx(&id, rapira::RapiraFlags::NONE);
assert_eq!(plain, ctx_none);
}
#[test]
fn test_opt_id64_ctx_none_roundtrip() {
let opt = OptId64::<Hasher>::NONE;
let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
let bytes = rapira::serialize_ctx(&opt, flags);
assert_eq!(bytes, [0u8; 8]);
let decoded: OptId64<Hasher> = rapira::deserialize_ctx(&bytes, flags).unwrap();
assert!(decoded.is_none());
}
#[test]
fn test_opt_id64_ctx_some_roundtrip() {
let id = Id64::<Hasher>::new(123456789);
let opt = OptId64::some(id);
let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
let encrypted = rapira::serialize_ctx(&opt, flags);
let plain = rapira::serialize(&opt);
assert_ne!(encrypted, plain);
let decoded: OptId64<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
assert_eq!(decoded.get(), Some(id));
}
}