use std::{
cell::Cell,
fmt::Debug,
marker::PhantomData,
str::FromStr,
sync::atomic::{AtomicU8, Ordering},
time::{Duration, SystemTime},
};
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, MILLISECOND_BITS},
key_type::KeyType,
num_ops::g8bits,
};
type Result<T, E = ArmourError> = core::result::Result<T, E>;
const TS_DIFF: u64 = 0x187_6350_0000;
const TS_BITS: u32 = 40;
const TS_MAX: u64 = (1 << TS_BITS) - 1;
const TS_SHIFT: u32 = u64::BITS - TS_BITS;
pub(crate) const SHARD_BITS: u32 = 8;
pub const SHARD_ID_MAX: u64 = (1 << SHARD_BITS) - 1;
const SEQ_BITS: u32 = u64::BITS - TS_BITS - SHARD_BITS;
const SEQ_MAX: u64 = (1 << SEQ_BITS) - 1;
const SHARD_INST_BITS: u32 = 3;
pub const SHARD_INSTANCE_ID_MAX: u64 = (1 << SHARD_INST_BITS) - 1;
const SHARD_INSTANCE_SHIFT: u32 = SHARD_THREAD_BITS + SEQ_BITS;
pub const SHARD_THREAD_BITS: u32 = 5;
pub const SHARD_THREAD_ID_MAX: u64 = (1 << SHARD_THREAD_BITS) - 1;
#[derive(Debug, Clone, Copy)]
struct SeqForMs {
ts: u64,
seq: u64,
count: u64,
}
impl SeqForMs {
#[cfg(feature = "std")]
#[inline]
fn with_ts_and_rand_seq(ts: u64) -> Self {
use rand::{RngExt, rng};
let seq = rng().random_range(0..=SEQ_MAX);
SeqForMs { ts, seq, count: 0 }
}
#[cold]
fn wait_inc(self, ts: u64, duration: Duration) -> Self {
tracing::info!(?duration, "wait_inc");
let nanos = duration.subsec_nanos() % 1_000_000;
let wait_nanos = 1_000_000 - nanos;
std::thread::sleep(Duration::from_nanos(wait_nanos as u64));
SeqForMs::with_ts_and_rand_seq(ts + 1)
}
#[inline]
fn increment(mut self, ts: u64, duration: Duration) -> Self {
if self.count == SEQ_MAX {
self.wait_inc(ts, duration)
} else {
if self.seq == SEQ_MAX {
self.seq = 0;
} else {
self.seq += 1;
}
self.count += 1;
self
}
}
}
static THREAD_SEQ: AtomicU8 = AtomicU8::new(0);
thread_local! {
static SEQ_CHECK: Cell<SeqForMs> = Cell::new(SeqForMs::with_ts_and_rand_seq(0));
static THREAD_ID: Cell<u8> = Cell::new(THREAD_SEQ.fetch_add(1, Ordering::Relaxed));
}
#[derive(Hash, 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 Fuid<H>(pub u64_be, PhantomData<H>);
unsafe impl<T> Zeroable for Fuid<T> {}
unsafe impl<T: 'static> Pod for Fuid<T> {}
impl<T> Clone for Fuid<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Fuid<T> {}
impl<H> Fuid<H> {
pub fn from_thread() -> Self {
let thread_id = THREAD_ID.get();
Self::with_shard(thread_id as u64)
}
#[cfg(feature = "std")]
#[inline]
pub fn with_inst_thread(instance_id: u64, thread_id: u64) -> Self {
assert!(instance_id <= SHARD_INSTANCE_ID_MAX);
assert!(thread_id <= SHARD_THREAD_ID_MAX);
let shard_id = (instance_id << SHARD_THREAD_BITS) | thread_id;
Self::with_shard(shard_id)
}
#[cfg(feature = "std")]
pub fn with_shard(shard_id: u64) -> Self {
assert!(shard_id <= SHARD_ID_MAX);
let now = SystemTime::now();
#[allow(clippy::unwrap_used)]
let duration = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
let timestamp = duration.as_millis() as u64;
let new_epoch_ts = timestamp - TS_DIFF;
let id = new_epoch_ts << TS_SHIFT;
let id = id | (shard_id << SEQ_BITS);
let mut seq_check = SEQ_CHECK.get();
if seq_check.ts == timestamp {
seq_check = seq_check.increment(timestamp, duration);
} else {
seq_check = SeqForMs::with_ts_and_rand_seq(timestamp);
}
SEQ_CHECK.set(seq_check);
let id = id | seq_check.seq;
Self(u64_be::from_native(id), PhantomData)
}
#[inline]
pub fn timestamp(&self) -> u64 {
let ts = self.0.to_native() >> TS_SHIFT;
ts + TS_DIFF
}
pub fn date(&self) -> time::OffsetDateTime {
let ts = self.timestamp();
let ts = ts as i128;
let ts = ts * 1_000_000;
let dt = time::OffsetDateTime::from_unix_timestamp_nanos(ts);
dt.expect("invalid timestamp")
}
#[inline]
pub fn get(&self) -> u64 {
self.0.to_native()
}
#[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 to_le_bytes(self) -> [u8; 8] {
self.0.to_native().to_le_bytes()
}
#[inline]
pub fn instance_id(&self) -> u64 {
let ts = self.0.to_native() >> SHARD_INSTANCE_SHIFT;
ts & SHARD_INSTANCE_ID_MAX
}
#[inline]
pub fn thread_id(&self) -> u64 {
let ts = self.0.to_native() >> SEQ_BITS;
ts & SHARD_THREAD_ID_MAX
}
#[inline]
pub fn shard_id(&self) -> u64 {
let ts = self.0.to_native() >> SEQ_BITS;
ts & SHARD_ID_MAX
}
#[inline]
fn seq(&self) -> u64 {
let ts = self.0.to_native();
ts & SEQ_MAX
}
#[cfg(feature = "std")]
pub fn format<W>(&self, w: &mut W) -> core::result::Result<(), core::fmt::Error>
where
W: std::fmt::Write,
{
let id = self.0.to_native();
let dt = self.date();
let instance_id = self.instance_id();
let thread_id = self.thread_id();
let seq = self.seq();
write!(
w,
"id: {id:#x}; {id:#b}; {dt}-{instance_id}-{thread_id}-{seq}"
)
}
pub fn date_prefix(ms: u64) -> [u8; 8] {
let ts = (ms - TS_DIFF) << TS_SHIFT;
ts.to_be_bytes()
}
pub fn check(&self) -> core::result::Result<(), ArmourError> {
let ts = self.0.to_native() >> TS_SHIFT;
if ts > TS_MAX {
return Err(ArmourError::IdDecodeError);
}
Ok(())
}
pub fn increment(mut self) -> Self {
self.0 += 1;
self
}
#[inline]
pub fn group_id(&self) -> u32 {
g8bits(self.0.to_native(), MILLISECOND_BITS)
}
}
impl<H: IdHasher> Fuid<H> {
pub fn ser(self) -> IdStr {
let u: u64 = zerocopy::transmute!(self);
H::ser(u)
}
pub fn deser(id: &str) -> Result<Self> {
let id = H::deser(id)?;
Ok(zerocopy::transmute!(id))
}
}
impl Fuid<()> {
pub fn ser(self) -> IdStr {
let u: [u8; 8] = zerocopy::transmute!(self);
crate::enc::encode(&u)
}
pub fn deser(id: &str) -> Result<Self> {
let id = crate::enc::decode(id)?;
Ok(zerocopy::transmute!(id))
}
}
impl<T> PartialOrd for Fuid<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for Fuid<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T> PartialEq for Fuid<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for Fuid<T> {}
impl<T> PartialEq<[u8; 8]> for Fuid<T> {
fn eq(&self, other: &[u8; 8]) -> bool {
let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
bytes == other
}
}
impl<T> PartialEq<Fuid<T>> for [u8; 8] {
fn eq(&self, other: &Fuid<T>) -> bool {
let bytes: &[u8; 8] = zerocopy::transmute_ref!(other);
bytes == self
}
}
impl<T> PartialEq<Fuid<T>> for Option<Fuid<T>> {
fn eq(&self, other: &Fuid<T>) -> bool {
match self {
Some(id) => id == other,
None => false,
}
}
}
impl<T> PartialEq<Option<Fuid<T>>> for Fuid<T> {
fn eq(&self, other: &Option<Fuid<T>>) -> bool {
match other {
Some(id) => self == id,
None => false,
}
}
}
impl<T> AsRef<Self> for Fuid<T> {
fn as_ref(&self) -> &Self {
self
}
}
impl<T> AsRef<[u8; 8]> for Fuid<T> {
fn as_ref(&self) -> &[u8; 8] {
zerocopy::transmute_ref!(self)
}
}
impl<H> Rapira for Fuid<H> {
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]) -> rapira::Result<()> {
let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;
const ZERO_BYTES: [u8; 8] = [0u8; 8];
if bytes == ZERO_BYTES {
return Err(RapiraError::NonZero);
}
*slice = unsafe { slice.get_unchecked(8..) };
Ok(())
}
#[inline]
fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
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<H> GetType for Fuid<H> {
const TYPE: Typ = Typ::Fuid;
}
impl<H> KeyPart for Fuid<H> {
const TY: KeyType = KeyType::Fuid;
const PREFIX_BITS: u32 = MILLISECOND_BITS;
}
impl<H: IdHasher> std::fmt::Display for Fuid<H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.ser())
}
}
impl std::fmt::Display for Fuid<()> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.ser())
}
}
impl<H> Debug for Fuid<H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let id = self.0.to_native();
let id = format!("{id:#X}");
f.debug_tuple("Fuid").field(&id).finish()
}
}
impl<T: IdHasher> TryFrom<&str> for Fuid<T> {
type Error = ArmourError;
fn try_from(val: &str) -> Result<Self> {
Self::deser(val)
}
}
impl TryFrom<&str> for Fuid<()> {
type Error = ArmourError;
fn try_from(val: &str) -> Result<Self> {
Self::deser(val)
}
}
impl<T: IdHasher> FromStr for Fuid<T> {
type Err = ArmourError;
fn from_str(s: &str) -> Result<Self> {
Self::deser(s)
}
}
impl FromStr for Fuid<()> {
type Err = ArmourError;
fn from_str(s: &str) -> Result<Self> {
Self::deser(s)
}
}
#[cfg(feature = "std")]
impl<T: IdHasher> Serialize for Fuid<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 Fuid<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 = Fuid::<T>::deser(s).map_err(|err| {
tracing::error!("id value error: {err}");
D::Error::custom("id value error")
})?;
Ok(a)
}
}
#[cfg(feature = "std")]
impl Serialize for Fuid<()> {
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> Deserialize<'de> for Fuid<()> {
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 = Fuid::<()>::deser(s).map_err(|err| {
tracing::error!("id value error: {err}");
D::Error::custom("id value error")
})?;
Ok(a)
}
}
#[cfg(feature = "ts-rs")]
impl<H> ts_rs::TS for Fuid<H> {
type WithoutGenerics = Fuid<()>;
type OptionInnerType = Self;
fn name(_: &ts_rs::Config) -> String {
"Fuid".to_owned()
}
fn decl_concrete(c: &ts_rs::Config) -> String {
format!("type Fuid = {};", Self::inline(c))
}
fn decl(c: &ts_rs::Config) -> String {
let inline = <Fuid<()> as ::ts_rs::TS>::inline(c);
format!("type Fuid = {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("fuid.ts"))
}
}
#[cfg(feature = "facet")]
unsafe impl<'facet, H: 'static> facet::Facet<'facet> for Fuid<H> {
const SHAPE: &'static facet::Shape = &const {
const VTABLE: facet::VTableDirect = facet::vtable_direct!(Fuid<()> =>
Debug,
Hash,
PartialEq,
PartialOrd,
Ord,
);
facet::ShapeBuilder::for_sized::<Fuid<H>>("Fuid")
.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 Fuid<H> {
fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
Fuid::from_thread()
}
}
#[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 OptFuid<H>(u64_be, PhantomData<H>);
unsafe impl<T> Zeroable for OptFuid<T> {}
unsafe impl<T: 'static> Pod for OptFuid<T> {}
impl<T> Clone for OptFuid<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for OptFuid<T> {}
impl<H> OptFuid<H> {
pub const NONE: Self = unsafe { core::mem::zeroed() };
#[inline]
pub fn some(id: Fuid<H>) -> Self {
Self(id.0, PhantomData)
}
#[inline]
pub fn get(self) -> Option<Fuid<H>> {
if self.0.to_native() == 0 {
None
} else {
Some(Fuid(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 OptFuid<T> {
fn default() -> Self {
Self::NONE
}
}
impl<H> From<Fuid<H>> for OptFuid<H> {
fn from(id: Fuid<H>) -> Self {
Self::some(id)
}
}
impl<H> From<Option<Fuid<H>>> for OptFuid<H> {
fn from(opt: Option<Fuid<H>>) -> Self {
match opt {
Some(id) => Self::some(id),
None => Self::NONE,
}
}
}
impl<H> From<OptFuid<H>> for Option<Fuid<H>> {
fn from(opt: OptFuid<H>) -> Self {
opt.get()
}
}
impl<T> PartialEq for OptFuid<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for OptFuid<T> {}
impl<T> PartialOrd for OptFuid<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for OptFuid<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T> core::hash::Hash for OptFuid<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T> PartialEq<Fuid<T>> for OptFuid<T> {
fn eq(&self, other: &Fuid<T>) -> bool {
self.0 == other.0
}
}
impl<T> PartialEq<OptFuid<T>> for Fuid<T> {
fn eq(&self, other: &OptFuid<T>) -> bool {
self.0 == other.0
}
}
impl<T> AsRef<[u8; 8]> for OptFuid<T> {
fn as_ref(&self) -> &[u8; 8] {
zerocopy::transmute_ref!(self)
}
}
impl<H> Debug for OptFuid<H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0.to_native() {
0 => write!(f, "OptFuid(None)"),
id => {
let id = format!("{id:#X}");
f.debug_tuple("OptFuid").field(&id).finish()
}
}
}
}
impl<H> Rapira for OptFuid<H> {
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]) -> rapira::Result<()> {
if slice.len() < 8 {
return Err(RapiraError::SliceLen);
}
*slice = unsafe { slice.get_unchecked(8..) };
Ok(())
}
#[inline]
fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
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<H> GetType for OptFuid<H> {
const TYPE: Typ = Typ::Fuid;
}
#[cfg(feature = "std")]
impl<H: IdHasher> Serialize for OptFuid<H> {
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, H: IdHasher> Deserialize<'de> for OptFuid<H> {
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 = Fuid::<H>::deser(s).map_err(|err| {
tracing::error!("id value error: {err}");
D::Error::custom("id value error")
})?;
Ok(Self::some(id))
}
None => Ok(Self::NONE),
}
}
}
#[cfg(feature = "std")]
impl Serialize for OptFuid<()> {
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> Deserialize<'de> for OptFuid<()> {
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 = Fuid::<()>::deser(s).map_err(|err| {
tracing::error!("id value error: {err}");
D::Error::custom("id value error")
})?;
Ok(Self::some(id))
}
None => Ok(Self::NONE),
}
}
}
#[cfg(feature = "ts-rs")]
impl<H> ts_rs::TS for OptFuid<H> {
type WithoutGenerics = OptFuid<()>;
type OptionInnerType = Self;
fn name(_: &ts_rs::Config) -> String {
"OptFuid".to_owned()
}
fn decl_concrete(c: &ts_rs::Config) -> String {
format!("type OptFuid = {};", Self::inline(c))
}
fn decl(c: &ts_rs::Config) -> String {
let inline = <OptFuid<()> as ::ts_rs::TS>::inline(c);
format!("type OptFuid = {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_fuid.ts"))
}
}
#[cfg(feature = "facet")]
unsafe impl<'facet, H: 'static> facet::Facet<'facet> for OptFuid<H> {
const SHAPE: &'static facet::Shape = &const {
const VTABLE: facet::VTableDirect = facet::vtable_direct!(OptFuid<()> =>
Debug,
Hash,
PartialEq,
PartialOrd,
Ord,
);
facet::ShapeBuilder::for_sized::<OptFuid<H>>("OptFuid")
.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 OptFuid<H> {
fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
Self::some(Fuid::from_thread())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::enc::Cipher;
#[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_fuid_creation() {
let fuid = Fuid::<Hasher>::with_inst_thread(1, 2);
assert_eq!(fuid.instance_id(), 1);
assert_eq!(fuid.thread_id(), 2);
let fuid = Fuid::<Hasher>::with_shard(55);
assert_eq!(fuid.shard_id(), 55);
}
#[test]
fn test_fuid_ser_deser() {
let fuid = Fuid::<Hasher>::with_shard(111);
let id_str = fuid.ser();
#[allow(clippy::unwrap_used)]
let deserialized_fuid = Fuid::<Hasher>::deser(&id_str).unwrap();
assert_eq!(fuid, deserialized_fuid);
}
#[test]
fn test_opt_fuid_none() {
let opt = OptFuid::<Hasher>::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_fuid_some() {
let fuid = Fuid::<Hasher>::with_shard(1);
let opt = OptFuid::some(fuid);
assert!(opt.is_some());
assert!(!opt.is_none());
assert_eq!(opt.get(), Some(fuid));
}
#[test]
fn test_opt_fuid_conversions() {
let fuid = Fuid::<Hasher>::with_shard(1);
let opt: OptFuid<Hasher> = fuid.into();
assert_eq!(opt.get(), Some(fuid));
let opt: OptFuid<Hasher> = None.into();
assert!(opt.is_none());
let opt: OptFuid<Hasher> = Some(fuid).into();
let back: Option<Fuid<Hasher>> = opt.into();
assert_eq!(back, Some(fuid));
}
#[test]
fn test_opt_fuid_size() {
assert_eq!(size_of::<OptFuid<Hasher>>(), 8);
assert_eq!(size_of::<OptFuid<Hasher>>(), size_of::<Fuid<Hasher>>());
}
#[test]
fn test_opt_fuid_default() {
let opt = OptFuid::<Hasher>::default();
assert!(opt.is_none());
}
#[test]
fn test_opt_fuid_eq_with_fuid() {
let fuid = Fuid::<Hasher>::with_shard(1);
let opt = OptFuid::some(fuid);
assert_eq!(opt, fuid);
assert_eq!(fuid, opt);
}
}