use base32ct::{Base32, Encoding};
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)]
pub struct Id<T, const N: usize = 12> {
_type: PhantomData<T>,
inner: [u8; N],
}
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 prefix = value.split("/");
if let Some(inner_str) = Self::strip_prefix(value)
&& inner_str.len() == (N * 2)
{
let mut inner = [0u8; N];
for i in (1usize..N).step_by(2) {
let s = &inner_str[i - 1..=i];
if let Ok(b) = u8::from_str_radix(s, 16) {
inner[i / 2] = b;
} else {
return Err(format!("Failed to parse '{s}' chars at '{i}' ").into());
};
}
Ok(Self::new(inner))
} else {
let m = format!(
"Expected '{}' type prefix, but got '' prefix",
T::SHAPE.type_identifier
);
warn!(m);
Err(m.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("/")
{
assert!(
pp.len() == (s.len() - (Self::prefix_str().len() - 1)),
"Length is wrong {}, it should be {}",
pp.len(),
(s.len() - (Self::prefix_str().len() - 1))
);
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 mut temp = vec![0u8; self.inner.len()];
if let Ok(o) = Base32::encode(&self.inner, &mut temp) {
write!(f, "{type_n}/{o}")
} else {
let m = "Failed to format id as string";
warn!("{m}");
panic!("{m}");
}
}
}