#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate core;
use std::fmt::{Display, Formatter};
use chrono::Utc;
use rand::Rng;
use thiserror::Error;
use crate::Error::Decoding;
#[derive(Debug, Clone, Copy, Default, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
pub struct Uid(i64);
impl Uid {
pub fn new() -> Self {
let timestamp = Utc::now().timestamp() << 24;
let mut random_bytes = [0u8; 8];
rand::thread_rng().fill(&mut random_bytes[5..8]);
let random_bytes = i64::from_be_bytes(random_bytes);
Uid(timestamp | random_bytes)
}
}
impl From<Uid> for i64 {
fn from(value: Uid) -> Self {
value.0
}
}
impl From<i64> for Uid {
fn from(value: i64) -> Self {
Uid(value)
}
}
impl From<Uid> for [u8; 8] {
fn from(value: Uid) -> Self {
value.0.to_be_bytes()
}
}
impl TryFrom<&[u8]> for Uid {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != 8 {
let err = format!("expected len to be 8, but was {}", value.len());
return Err(Decoding(err));
}
let mut arr = [0u8; 8];
arr.copy_from_slice(value);
let id = i64::from_be_bytes(arr);
Ok(Uid(id))
}
}
impl Display for Uid {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "base16")]
#[cfg_attr(docsrs, doc(cfg(feature = "base16")))]
impl Uid {
pub fn to_base16(&self) -> String {
_faster_hex::hex_string(&self.0.to_be_bytes())
}
pub fn to_hex(&self) -> String {
self.to_base16()
}
pub fn from_base16(value: &str) -> Result<Self, Error> {
let mut arr = [0u8; 8];
_faster_hex::hex_decode(value.as_bytes(), &mut arr)
.map_err(|err| Decoding(err.to_string()))?;
let id = i64::from_be_bytes(arr);
Ok(Uid(id))
}
pub fn from_hex(value: &str) -> Result<Self, Error> {
Self::from_base16(value)
}
}
#[cfg(feature = "base32")]
#[cfg_attr(docsrs, doc(cfg(feature = "base32")))]
impl Uid {
pub fn to_base32(&self) -> String {
_data_encoding::BASE32.encode(&self.0.to_be_bytes())
}
pub fn to_unpadded_base32(&self) -> String {
_data_encoding::BASE32_NOPAD.encode(&self.0.to_be_bytes())
}
pub fn from_base32(value: &str) -> Result<Self, Error> {
let bytes = _data_encoding::BASE32
.decode(value.as_bytes())
.map_err(|err| Decoding(err.to_string()))?;
Uid::try_from(bytes.as_slice())
}
pub fn from_unpadded_base32(value: &str) -> Result<Self, Error> {
let bytes = _data_encoding::BASE32_NOPAD
.decode(value.as_bytes())
.map_err(|err| Decoding(err.to_string()))?;
Uid::try_from(bytes.as_slice())
}
}
#[cfg(feature = "base58")]
#[cfg_attr(docsrs, doc(cfg(feature = "base58")))]
impl Uid {
pub fn to_base58(&self) -> String {
_bs58::encode(self.0.to_be_bytes())
.with_alphabet(_bs58::Alphabet::BITCOIN)
.into_string()
}
pub fn from_base58(value: &str) -> Result<Self, Error> {
let bytes = _bs58::decode(value)
.into_vec()
.map_err(|err| Decoding(err.to_string()))?;
Uid::try_from(bytes.as_slice())
}
}
#[cfg(feature = "base64")]
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
impl Uid {
pub fn to_base64(&self) -> String {
_data_encoding::BASE64.encode(&self.0.to_be_bytes())
}
pub fn to_unpadded_base64(&self) -> String {
_data_encoding::BASE64_NOPAD.encode(&self.0.to_be_bytes())
}
pub fn from_base64(value: &str) -> Result<Self, Error> {
let bytes = _data_encoding::BASE64
.decode(value.as_bytes())
.map_err(|err| Decoding(err.to_string()))?;
Uid::try_from(bytes.as_slice())
}
pub fn from_unpadded_base64(value: &str) -> Result<Self, Error> {
let bytes = _data_encoding::BASE64_NOPAD
.decode(value.as_bytes())
.map_err(|err| Decoding(err.to_string()))?;
Uid::try_from(bytes.as_slice())
}
}
#[derive(Debug, Clone, PartialEq, Error)]
#[non_exhaustive]
pub enum Error {
#[error("{0}")]
Decoding(String),
}