use crate::encoding::{parse_base32, stringify_base32};
use crate::{Error, Type};
use std::fmt::{Debug, Display, Formatter};
use std::marker::PhantomData;
use std::str::FromStr;
pub type IdBytes = [u8; 16];
#[cfg_attr(
feature = "diesel",
derive(::diesel::AsExpression, ::diesel::FromSqlRow)
)]
#[cfg_attr(feature = "diesel", diesel(sql_type = ::diesel::sql_types::Int8))]
pub struct Id<T: Type + ?Sized> {
marker: PhantomData<T>,
value: IdBytes,
}
impl<T: Type + ?Sized> Id<T> {
pub fn new(value: [u8; 16]) -> Self {
Self {
marker: PhantomData,
value,
}
}
pub fn as_bytes(&self) -> &[u8; 16] {
&self.value
}
pub fn to_bytes(self) -> [u8; 16] {
self.value
}
pub fn to_u128(self) -> u128 {
u128::from_be_bytes(self.value)
}
pub fn to_i128(self) -> i128 {
i128::from_be_bytes(self.value)
}
pub fn test(value: &str) -> bool {
Self::parse(value).is_ok()
}
pub fn parse(value: &str) -> Result<Self, Error> {
let (prefix, value) = value.split_once('_').ok_or(Error::InvalidData)?;
if prefix != T::PREFIX {
return Err(Error::PrefixMismatch {
expected: T::PREFIX,
actual: String::from(prefix),
});
}
Ok(Self::new(parse_base32(value)?))
}
pub fn prefix(self) -> &'static str {
T::PREFIX
}
pub const fn cast<U: Type + ?Sized>(self) -> Id<U> {
Id {
marker: PhantomData,
value: self.value,
}
}
}
impl<T: Type + ?Sized> Copy for Id<T> {}
impl<T: Type + ?Sized> Clone for Id<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Type + ?Sized> std::hash::Hash for Id<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<T: Type + ?Sized> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T: Type + ?Sized> Eq for Id<T> {}
impl<T: Type + ?Sized> PartialOrd for Id<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.cmp(other).into()
}
}
impl<T: Type + ?Sized> Ord for Id<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl<T: Type + ?Sized> Debug for Id<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl<T: Type + ?Sized> Display for Id<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}_{}",
T::PREFIX,
stringify_base32(self.value).expect("id value to stringify correctly")
)
}
}
impl<T: Type + ?Sized> FromStr for Id<T> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl<T: Type + ?Sized> From<Id<T>> for u128 {
fn from(value: Id<T>) -> Self {
value.to_u128()
}
}
impl<T: Type + ?Sized> From<Id<T>> for i128 {
fn from(value: Id<T>) -> Self {
value.to_i128()
}
}
impl<T: Type + ?Sized> From<Id<T>> for IdBytes {
fn from(value: Id<T>) -> Self {
value.to_bytes()
}
}
impl<T: Type + ?Sized> From<u128> for Id<T> {
fn from(value: u128) -> Self {
Self::new(value.to_be_bytes())
}
}
impl<T: Type + ?Sized> From<i128> for Id<T> {
fn from(value: i128) -> Self {
Self::new(value.to_be_bytes())
}
}
impl<T: Type + ?Sized> From<IdBytes> for Id<T> {
fn from(value: IdBytes) -> Self {
Self::new(value)
}
}
impl<T: Type + ?Sized> TryFrom<&[u8]> for Id<T> {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Ok(Self::new(value.try_into().map_err(|_| Error::InvalidData)?))
}
}