use core::{fmt, marker::PhantomData};
use super::interface::Base32Ext;
use crate::{
base32::Error,
generator::Result,
id::{BeBytes, Id, UlidId},
};
pub trait Base32UlidExt: UlidId
where
Self::Ty: BeBytes,
{
#[must_use]
fn byte_array() -> <<Self as Id>::Ty as BeBytes>::ByteArray {
Self::inner_byte_array()
}
#[must_use]
fn base32_array() -> <<Self as Id>::Ty as BeBytes>::Base32Array {
Self::inner_base32_array()
}
fn encode(&self) -> Base32UlidFormatter<Self> {
Base32UlidFormatter::new(self)
}
fn encode_to_buf<'buf>(
&self,
buf: &'buf mut <<Self as Id>::Ty as BeBytes>::Base32Array,
) -> Base32UlidFormatterRef<'buf, Self> {
Base32UlidFormatterRef::new(self, buf)
}
fn decode(input: impl AsRef<[u8]>) -> Result<Self, Error<Self>> {
let decoded = Self::inner_decode(input)?;
if !decoded.is_valid() {
return Err(Error::DecodeOverflow { id: decoded });
}
Ok(decoded)
}
}
impl<ID> Base32UlidExt for ID
where
ID: UlidId,
ID::Ty: BeBytes,
{
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Base32UlidFormatter<T>
where
T: Base32UlidExt,
T::Ty: BeBytes,
{
_id: PhantomData<T>,
buf: <T::Ty as BeBytes>::Base32Array,
}
impl<T: Base32UlidExt> Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
pub fn new(id: &T) -> Self {
let mut buf = T::base32_array();
id.inner_encode_to_buf(&mut buf);
Self {
_id: PhantomData,
buf,
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.buf.as_ref()
}
#[must_use]
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn as_string(&self) -> alloc::string::String {
unsafe { alloc::string::String::from_utf8_unchecked(self.as_bytes().to_vec()) }
}
pub const fn into_inner(self) -> <T::Ty as BeBytes>::Base32Array {
self.buf
}
}
impl<T: Base32UlidExt> core::hash::Hash for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<T: Base32UlidExt> fmt::Display for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<T: Base32UlidExt> fmt::Debug for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Base32UlidFormatter")
.field(&self.as_str())
.finish()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32UlidExt> From<&Base32UlidFormatter<T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn from(formatter: &Base32UlidFormatter<T>) -> Self {
formatter.as_string()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32UlidExt> From<Base32UlidFormatter<T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn from(formatter: Base32UlidFormatter<T>) -> Self {
formatter.as_string()
}
}
impl<T: Base32UlidExt> AsRef<str> for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<T: Base32UlidExt> AsRef<[u8]> for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<T: Base32UlidExt> core::ops::Deref for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl<T: Base32UlidExt> core::borrow::Borrow<str> for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<T: Base32UlidExt> PartialEq<str> for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<T: Base32UlidExt> PartialEq<&str> for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
impl<T: Base32UlidExt> PartialEq<Base32UlidFormatter<T>> for &str
where
T::Ty: BeBytes,
{
fn eq(&self, other: &Base32UlidFormatter<T>) -> bool {
other == *self
}
}
#[cfg(feature = "alloc")]
impl<T: Base32UlidExt> PartialEq<alloc::string::String> for Base32UlidFormatter<T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &alloc::string::String) -> bool {
self.as_str() == other.as_str()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32UlidExt> PartialEq<Base32UlidFormatter<T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn eq(&self, other: &Base32UlidFormatter<T>) -> bool {
self.as_str() == other.as_str()
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Base32UlidFormatterRef<'a, T>
where
T: Base32UlidExt,
T::Ty: BeBytes,
{
_id: PhantomData<T>,
buf: &'a <T::Ty as BeBytes>::Base32Array,
}
impl<'a, T: Base32UlidExt> Base32UlidFormatterRef<'a, T>
where
T::Ty: BeBytes,
{
pub fn new(id: &T, buf: &'a mut <T::Ty as BeBytes>::Base32Array) -> Self {
id.inner_encode_to_buf(buf);
Self {
_id: PhantomData,
buf,
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.buf.as_ref()
}
#[must_use]
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn as_string(&self) -> alloc::string::String {
unsafe { alloc::string::String::from_utf8_unchecked(self.as_bytes().to_vec()) }
}
}
impl<T: Base32UlidExt> core::hash::Hash for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<T: Base32UlidExt> fmt::Display for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<T: Base32UlidExt> fmt::Debug for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Base32UlidFormatterRef")
.field(&self.as_str())
.finish()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32UlidExt> From<&Base32UlidFormatterRef<'_, T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn from(formatter: &Base32UlidFormatterRef<'_, T>) -> Self {
formatter.as_string()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32UlidExt> From<Base32UlidFormatterRef<'_, T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn from(formatter: Base32UlidFormatterRef<'_, T>) -> Self {
formatter.as_string()
}
}
impl<T: Base32UlidExt> AsRef<str> for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<T: Base32UlidExt> AsRef<[u8]> for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<T: Base32UlidExt> core::ops::Deref for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl<T: Base32UlidExt> core::borrow::Borrow<str> for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<T: Base32UlidExt> PartialEq<str> for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<T: Base32UlidExt> PartialEq<&str> for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
impl<T: Base32UlidExt> PartialEq<Base32UlidFormatterRef<'_, T>> for &str
where
T::Ty: BeBytes,
{
fn eq(&self, other: &Base32UlidFormatterRef<'_, T>) -> bool {
other == *self
}
}
#[cfg(feature = "alloc")]
impl<T: Base32UlidExt> PartialEq<alloc::string::String> for Base32UlidFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &alloc::string::String) -> bool {
self.as_str() == other.as_str()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32UlidExt> PartialEq<Base32UlidFormatterRef<'_, T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn eq(&self, other: &Base32UlidFormatterRef<'_, T>) -> bool {
self.as_str() == other.as_str()
}
}
#[cfg(all(test, feature = "alloc", feature = "ulid"))]
mod alloc_test {
use alloc::string::ToString;
use crate::{base32::Base32UlidExt, id::ULID};
#[test]
fn ulid_display() {
let ulid = ULID::decode("01ARZ3NDEKTSV4RRFFQ69G5FAV").unwrap();
assert_eq!(alloc::format!("{ulid}"), "01ARZ3NDEKTSV4RRFFQ69G5FAV");
assert_eq!(ulid.to_string(), "01ARZ3NDEKTSV4RRFFQ69G5FAV");
}
}
#[cfg(all(test, feature = "ulid"))]
mod test {
use crate::{
base32::{Base32UlidExt, Error},
id::ULID,
};
#[test]
fn ulid_try_from() {
let ulid = ULID::try_from("01ARZ3NDEKTSV4RRFFQ69G5FAV").unwrap();
let encoded = ulid.encode();
assert_eq!(encoded, "01ARZ3NDEKTSV4RRFFQ69G5FAV");
}
#[test]
fn ulid_from_str() {
use core::str::FromStr;
let ulid = ULID::from_str("01ARZ3NDEKTSV4RRFFQ69G5FAV").unwrap();
let encoded = ulid.encode();
assert_eq!(encoded, "01ARZ3NDEKTSV4RRFFQ69G5FAV");
}
#[test]
fn ulid_max() {
let id = ULID::from_components(ULID::max_timestamp(), ULID::max_random());
assert_eq!(id.timestamp(), ULID::max_timestamp());
assert_eq!(id.random(), ULID::max_random());
let encoded = id.encode();
assert_eq!(encoded, "7ZZZZZZZZZZZZZZZZZZZZZZZZZ");
let decoded = ULID::decode(&encoded).unwrap();
assert_eq!(decoded.timestamp(), ULID::max_timestamp());
assert_eq!(decoded.random(), ULID::max_random());
assert_eq!(id, decoded);
}
#[test]
fn ulid_known() {
let id = ULID::from_components(1_469_922_850_259, 1_012_768_647_078_601_740_696_923);
assert_eq!(id.timestamp(), 1_469_922_850_259);
assert_eq!(id.random(), 1_012_768_647_078_601_740_696_923);
let encoded = id.encode();
assert_eq!(encoded, "01ARZ3NDEKTSV4RRFFQ69G5FAV");
let decoded = ULID::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), 1_469_922_850_259);
assert_eq!(decoded.random(), 1_012_768_647_078_601_740_696_923);
assert_eq!(id, decoded);
let id = ULID::from_components(1_611_559_180_765, 885_339_478_614_498_720_052_741);
assert_eq!(id.timestamp(), 1_611_559_180_765);
assert_eq!(id.random(), 885_339_478_614_498_720_052_741);
let encoded = id.encode();
assert_eq!(encoded, "01EWW6K6EXQDX5JV0E9CAHPXG5");
let decoded = ULID::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), 1_611_559_180_765);
assert_eq!(decoded.random(), 885_339_478_614_498_720_052_741);
assert_eq!(id, decoded);
}
#[test]
fn ulid_zero() {
let id = ULID::from_components(0, 0);
assert_eq!(id.timestamp(), 0);
assert_eq!(id.random(), 0);
let encoded = id.encode();
assert_eq!(encoded, "00000000000000000000000000");
let decoded = ULID::decode(&encoded).unwrap();
assert_eq!(decoded.timestamp(), 0);
assert_eq!(decoded.random(), 0);
assert_eq!(id, decoded);
}
#[test]
fn decode_invalid_character_fails() {
let invalid = "000000000000@0000000000000";
let res = ULID::decode(invalid);
assert_eq!(
res.unwrap_err(),
Error::DecodeInvalidAscii {
byte: b'@',
index: 12,
}
);
}
#[test]
fn decode_invalid_length_fails() {
let too_short = "0123456789012345678901234";
let res = ULID::decode(too_short);
assert_eq!(res.unwrap_err(), Error::DecodeInvalidLen { len: 25 });
let too_long = "012345678901234567890123456";
let res = ULID::decode(too_long);
assert_eq!(res.unwrap_err(), Error::DecodeInvalidLen { len: 27 });
}
}