use core::{fmt, marker::PhantomData};
use super::interface::Base32Ext;
use crate::{
base32::Error,
generator::Result,
id::{BeBytes, Id, SnowflakeId},
};
pub trait Base32SnowExt: SnowflakeId
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) -> Base32SnowFormatter<Self> {
Base32SnowFormatter::new(self)
}
fn encode_to_buf<'buf>(
&self,
buf: &'buf mut <<Self as Id>::Ty as BeBytes>::Base32Array,
) -> Base32SnowFormatterRef<'buf, Self> {
Base32SnowFormatterRef::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> Base32SnowExt for ID
where
ID: SnowflakeId,
ID::Ty: BeBytes,
{
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Base32SnowFormatter<T>
where
T: Base32SnowExt,
T::Ty: BeBytes,
{
_id: PhantomData<T>,
buf: <T::Ty as BeBytes>::Base32Array,
}
impl<T: Base32SnowExt> Base32SnowFormatter<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: Base32SnowExt> core::hash::Hash for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<T: Base32SnowExt> fmt::Display for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<T: Base32SnowExt> fmt::Debug for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Base32SnowFormatter")
.field(&self.as_str())
.finish()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32SnowExt> From<&Base32SnowFormatter<T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn from(formatter: &Base32SnowFormatter<T>) -> Self {
formatter.as_string()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32SnowExt> From<Base32SnowFormatter<T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn from(formatter: Base32SnowFormatter<T>) -> Self {
formatter.as_string()
}
}
impl<T: Base32SnowExt> AsRef<str> for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<T: Base32SnowExt> AsRef<[u8]> for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<T: Base32SnowExt> core::ops::Deref for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl<T: Base32SnowExt> core::borrow::Borrow<str> for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<T: Base32SnowExt> PartialEq<str> for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<T: Base32SnowExt> PartialEq<&str> for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
impl<T: Base32SnowExt> PartialEq<Base32SnowFormatter<T>> for &str
where
T::Ty: BeBytes,
{
fn eq(&self, other: &Base32SnowFormatter<T>) -> bool {
other == *self
}
}
#[cfg(feature = "alloc")]
impl<T: Base32SnowExt> PartialEq<alloc::string::String> for Base32SnowFormatter<T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &alloc::string::String) -> bool {
self.as_str() == other.as_str()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32SnowExt> PartialEq<Base32SnowFormatter<T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn eq(&self, other: &Base32SnowFormatter<T>) -> bool {
self.as_str() == other.as_str()
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Base32SnowFormatterRef<'a, T>
where
T: Base32SnowExt,
T::Ty: BeBytes,
{
_id: PhantomData<T>,
buf: &'a <T::Ty as BeBytes>::Base32Array,
}
impl<'a, T: Base32SnowExt> Base32SnowFormatterRef<'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: Base32SnowExt> core::hash::Hash for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<T: Base32SnowExt> fmt::Display for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<T: Base32SnowExt> fmt::Debug for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Base32SnowFormatterRef")
.field(&self.as_str())
.finish()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32SnowExt> From<&Base32SnowFormatterRef<'_, T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn from(formatter: &Base32SnowFormatterRef<'_, T>) -> Self {
formatter.as_string()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32SnowExt> From<Base32SnowFormatterRef<'_, T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn from(formatter: Base32SnowFormatterRef<'_, T>) -> Self {
formatter.as_string()
}
}
impl<T: Base32SnowExt> AsRef<str> for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<T: Base32SnowExt> AsRef<[u8]> for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<T: Base32SnowExt> core::ops::Deref for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl<T: Base32SnowExt> core::borrow::Borrow<str> for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<T: Base32SnowExt> PartialEq<str> for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<T: Base32SnowExt> PartialEq<&str> for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
impl<T: Base32SnowExt> PartialEq<Base32SnowFormatterRef<'_, T>> for &str
where
T::Ty: BeBytes,
{
fn eq(&self, other: &Base32SnowFormatterRef<'_, T>) -> bool {
other == *self
}
}
#[cfg(feature = "alloc")]
impl<T: Base32SnowExt> PartialEq<alloc::string::String> for Base32SnowFormatterRef<'_, T>
where
T::Ty: BeBytes,
{
fn eq(&self, other: &alloc::string::String) -> bool {
self.as_str() == other.as_str()
}
}
#[cfg(feature = "alloc")]
impl<T: Base32SnowExt> PartialEq<Base32SnowFormatterRef<'_, T>> for alloc::string::String
where
T::Ty: BeBytes,
{
fn eq(&self, other: &Base32SnowFormatterRef<'_, T>) -> bool {
self.as_str() == other.as_str()
}
}
#[cfg(all(test, feature = "alloc", feature = "snowflake"))]
mod alloc_test {
use alloc::string::ToString;
use crate::{
base32::Base32SnowExt,
id::{SnowflakeDiscordId, SnowflakeInstagramId, SnowflakeMastodonId, SnowflakeTwitterId},
};
#[test]
fn twitter_display() {
let id = SnowflakeTwitterId::decode("01ARZ3NDEKTSV").unwrap();
assert_eq!(alloc::format!("{id}"), "01ARZ3NDEKTSV");
assert_eq!(id.to_string(), "01ARZ3NDEKTSV");
}
#[test]
fn instagram_display() {
let id = SnowflakeInstagramId::decode("01ARZ3NDEKTSV").unwrap();
assert_eq!(alloc::format!("{id}"), "01ARZ3NDEKTSV");
assert_eq!(id.to_string(), "01ARZ3NDEKTSV");
}
#[test]
fn mastodon_display() {
let id = SnowflakeMastodonId::decode("01ARZ3NDEKTSV").unwrap();
assert_eq!(alloc::format!("{id}"), "01ARZ3NDEKTSV");
assert_eq!(id.to_string(), "01ARZ3NDEKTSV");
}
#[test]
fn discord_display() {
let id = SnowflakeDiscordId::decode("01ARZ3NDEKTSV").unwrap();
assert_eq!(alloc::format!("{id}"), "01ARZ3NDEKTSV");
assert_eq!(id.to_string(), "01ARZ3NDEKTSV");
}
}
#[cfg(all(test, feature = "snowflake"))]
mod test {
use crate::{
base32::{Base32SnowExt, Error},
id::{SnowflakeDiscordId, SnowflakeInstagramId, SnowflakeMastodonId, SnowflakeTwitterId},
};
#[test]
fn snow_try_from() {
let id = SnowflakeTwitterId::try_from("01ARZ3NDEKTSV").unwrap();
let encoded = id.encode();
assert_eq!(encoded, "01ARZ3NDEKTSV");
}
#[test]
fn snow_from_str() {
use core::str::FromStr;
let id = SnowflakeTwitterId::from_str("01ARZ3NDEKTSV").unwrap();
let encoded = id.encode();
assert_eq!(encoded, "01ARZ3NDEKTSV");
}
#[test]
fn twitter_max() {
let id = SnowflakeTwitterId::from_components(
SnowflakeTwitterId::max_timestamp(),
SnowflakeTwitterId::max_machine_id(),
SnowflakeTwitterId::max_sequence(),
);
assert_eq!(id.timestamp(), SnowflakeTwitterId::max_timestamp());
assert_eq!(id.machine_id(), SnowflakeTwitterId::max_machine_id());
assert_eq!(id.sequence(), SnowflakeTwitterId::max_sequence());
let encoded = id.encode();
assert_eq!(encoded, "7ZZZZZZZZZZZZ");
let decoded = SnowflakeTwitterId::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), SnowflakeTwitterId::max_timestamp());
assert_eq!(decoded.machine_id(), SnowflakeTwitterId::max_machine_id());
assert_eq!(decoded.sequence(), SnowflakeTwitterId::max_sequence());
assert_eq!(id, decoded);
}
#[test]
fn twitter_zero() {
let id = SnowflakeTwitterId::from_components(0, 0, 0);
assert_eq!(id.timestamp(), 0);
assert_eq!(id.machine_id(), 0);
assert_eq!(id.sequence(), 0);
let encoded = id.encode();
assert_eq!(encoded, "0000000000000");
let decoded = SnowflakeTwitterId::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), 0);
assert_eq!(decoded.machine_id(), 0);
assert_eq!(decoded.sequence(), 0);
assert_eq!(id, decoded);
}
#[test]
fn discord_max() {
let id = SnowflakeDiscordId::from_components(
SnowflakeDiscordId::max_timestamp(),
SnowflakeDiscordId::max_machine_id(),
SnowflakeDiscordId::max_sequence(),
);
assert_eq!(id.timestamp(), SnowflakeDiscordId::max_timestamp());
assert_eq!(id.machine_id(), SnowflakeDiscordId::max_machine_id());
assert_eq!(id.sequence(), SnowflakeDiscordId::max_sequence());
let encoded = id.encode();
assert_eq!(encoded, "FZZZZZZZZZZZZ");
let decoded = SnowflakeDiscordId::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), SnowflakeDiscordId::max_timestamp());
assert_eq!(decoded.machine_id(), SnowflakeDiscordId::max_machine_id());
assert_eq!(decoded.sequence(), SnowflakeDiscordId::max_sequence());
assert_eq!(id, decoded);
}
#[test]
fn discord_zero() {
let id = SnowflakeDiscordId::from_components(0, 0, 0);
assert_eq!(id.timestamp(), 0);
assert_eq!(id.machine_id(), 0);
assert_eq!(id.sequence(), 0);
let encoded = id.encode();
assert_eq!(encoded, "0000000000000");
let decoded = SnowflakeDiscordId::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), 0);
assert_eq!(decoded.machine_id(), 0);
assert_eq!(decoded.sequence(), 0);
assert_eq!(id, decoded);
}
#[test]
fn instagram_max() {
let id = SnowflakeInstagramId::from_components(
SnowflakeInstagramId::max_timestamp(),
SnowflakeInstagramId::max_machine_id(),
SnowflakeInstagramId::max_sequence(),
);
assert_eq!(id.timestamp(), SnowflakeInstagramId::max_timestamp());
assert_eq!(id.machine_id(), SnowflakeInstagramId::max_machine_id());
assert_eq!(id.sequence(), SnowflakeInstagramId::max_sequence());
let encoded = id.encode();
assert_eq!(encoded, "FZZZZZZZZZZZZ");
let decoded = SnowflakeInstagramId::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), SnowflakeInstagramId::max_timestamp());
assert_eq!(decoded.machine_id(), SnowflakeInstagramId::max_machine_id());
assert_eq!(decoded.sequence(), SnowflakeInstagramId::max_sequence());
assert_eq!(id, decoded);
}
#[test]
fn instagram_zero() {
let id = SnowflakeInstagramId::from_components(0, 0, 0);
assert_eq!(id.timestamp(), 0);
assert_eq!(id.machine_id(), 0);
assert_eq!(id.sequence(), 0);
let encoded = id.encode();
assert_eq!(encoded, "0000000000000");
let decoded = SnowflakeInstagramId::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), 0);
assert_eq!(decoded.machine_id(), 0);
assert_eq!(decoded.sequence(), 0);
assert_eq!(id, decoded);
}
#[test]
fn mastodon_max() {
let id = SnowflakeMastodonId::from_components(
SnowflakeMastodonId::max_timestamp(),
SnowflakeMastodonId::max_machine_id(),
SnowflakeMastodonId::max_sequence(),
);
assert_eq!(id.timestamp(), SnowflakeMastodonId::max_timestamp());
assert_eq!(id.machine_id(), SnowflakeMastodonId::max_machine_id());
assert_eq!(id.sequence(), SnowflakeMastodonId::max_sequence());
let encoded = id.encode();
assert_eq!(encoded, "FZZZZZZZZZZZZ");
let decoded = SnowflakeMastodonId::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), SnowflakeMastodonId::max_timestamp());
assert_eq!(decoded.machine_id(), SnowflakeMastodonId::max_machine_id());
assert_eq!(decoded.sequence(), SnowflakeMastodonId::max_sequence());
assert_eq!(id, decoded);
}
#[test]
fn mastodon_zero() {
let id = SnowflakeMastodonId::from_components(0, 0, 0);
assert_eq!(id.timestamp(), 0);
assert_eq!(id.machine_id(), 0);
assert_eq!(id.sequence(), 0);
let encoded = id.encode();
assert_eq!(encoded, "0000000000000");
let decoded = SnowflakeMastodonId::decode(encoded).unwrap();
assert_eq!(decoded.timestamp(), 0);
assert_eq!(decoded.machine_id(), 0);
assert_eq!(decoded.sequence(), 0);
assert_eq!(id, decoded);
}
#[test]
fn decode_invalid_character_fails() {
let invalid = "000000@000000";
let res = SnowflakeTwitterId::decode(invalid);
assert_eq!(
res.unwrap_err(),
Error::DecodeInvalidAscii {
byte: b'@',
index: 6,
}
);
}
#[test]
fn decode_invalid_length_fails() {
let too_short = "012345678901";
let res = SnowflakeTwitterId::decode(too_short);
assert_eq!(res.unwrap_err(), Error::DecodeInvalidLen { len: 12 });
let too_long = "01234567890123";
let res = SnowflakeTwitterId::decode(too_long);
assert_eq!(res.unwrap_err(), Error::DecodeInvalidLen { len: 14 });
}
}