use base32::Alphabet;
use facet::{Facet, Shape};
use rand::{Rng, RngExt, rng};
use std::{fmt::Display, marker::PhantomData};
use tracing::{debug, error, info, warn};
#[derive(thiserror::Error, Debug)]
pub enum IdErrors {
#[error(transparent)]
StdIo(#[from] std::io::Error),
#[error("{0}")]
Custom(String),
}
impl From<String> for IdErrors {
fn from(value: String) -> Self {
Self::Custom(value)
}
}
impl From<&str> for IdErrors {
fn from(value: &str) -> Self {
Self::from(value.to_string())
}
}
pub type Res<T> = Result<T, IdErrors>;
#[derive(facet::Facet, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
pub struct Id<T, const N: usize = 12> {
_type: PhantomData<T>,
inner: [u8; N],
}
unsafe impl<T> Send for Id<T> {}
unsafe impl<T> Sync for Id<T> {}
impl<'facet, T: Facet<'facet>, const N: usize> TryFrom<String> for Id<T, N> {
type Error = IdErrors;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl<'facet, T: Facet<'facet>, const N: usize> TryFrom<&str> for Id<T, N> {
type Error = IdErrors;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let splt: Vec<&str> = value.split("/").collect();
if splt.len() == 2 {
let prefix = splt[0];
let inner_str = splt[1];
if prefix == T::SHAPE.type_identifier {
let a = Alphabet::Rfc4648HexLower { padding: false };
if let Some(o) = base32::decode(a, inner_str) {
let mut temp = [0u8; N];
temp.clone_from_slice(&o[0..N]);
Ok(Self::new(temp))
} else {
Err(format!("Failed to parse '{}' as b32hex without padding", inner_str).into())
}
} else {
let m = format!(
"Expected '{}' type prefix, but got '{prefix}' prefix",
T::SHAPE.type_identifier
);
warn!(m);
Err(m.into())
}
} else {
return Err(format!(
"Value contains <{}>, but it should be exactly 2",
splt.len()
)
.into());
}
}
}
impl<'facet, const N: usize, T: Facet<'facet>> Id<T, N> {
pub fn prefix_str() -> &'static str {
T::SHAPE.type_identifier
}
pub fn to_hex_strng(&self) -> String {
let mut o = format!("{}/", T::SHAPE.type_identifier);
for i in self.inner {
o = format!("{o}{i:0>2X}");
}
o
}
pub fn into_hex_strng(self) -> String {
let mut o = format!("{}/", T::SHAPE.type_identifier);
for i in self.inner {
o = format!("{o}{i:0>2X}");
}
o
}
pub fn from_hex_strng(s: &str) -> Res<Self> {
todo!()
}
pub fn prefix_match(s: &str) -> bool {
s.starts_with(Self::prefix_str())
}
fn strip_prefix(s: &str) -> Option<&str> {
if let Some(p) = s.strip_prefix(Self::prefix_str())
&& let Some(pp) = p.strip_prefix("/")
{
Some(p)
} else {
None
}
}
}
impl<const N: usize, T> Id<T, N> {
fn new(inner: [u8; N]) -> Self {
Self {
_type: PhantomData,
inner,
}
}
pub fn random() -> Self {
let mut rng = rng();
Self::new(rng.random())
}
pub fn random_rng<R: Rng>(rng: &mut R) -> Self {
Self::new(rng.random())
}
}
impl<'facet, T: Facet<'facet>> Display for Id<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let type_n = T::SHAPE.type_identifier;
let a = Alphabet::Rfc4648HexLower { padding: false };
let o = base32::encode(a, &self.inner);
write!(f, "{type_n}/{o}")
}
}