1use core::{fmt, marker::PhantomData};
2
3use super::interface::Base32Ext;
4use crate::{
5 base32::Error,
6 generator::Result,
7 id::{BeBytes, Id, SnowflakeId},
8};
9
10pub trait Base32SnowExt: SnowflakeId
16where
17 Self::Ty: BeBytes,
18{
19 #[must_use]
24 fn byte_array() -> <<Self as Id>::Ty as BeBytes>::ByteArray {
25 Self::inner_byte_array()
26 }
27
28 #[must_use]
38 fn base32_array() -> <<Self as Id>::Ty as BeBytes>::Base32Array {
39 Self::inner_base32_array()
40 }
41 fn encode(&self) -> Base32SnowFormatter<Self> {
61 Base32SnowFormatter::new(self)
62 }
63 fn encode_to_buf<'buf>(
94 &self,
95 buf: &'buf mut <<Self as Id>::Ty as BeBytes>::Base32Array,
96 ) -> Base32SnowFormatterRef<'buf, Self> {
97 Base32SnowFormatterRef::new(self, buf)
98 }
99 fn decode(input: impl AsRef<[u8]>) -> Result<Self, Error<Self>> {
187 let decoded = Self::inner_decode(input)?;
188 if !decoded.is_valid() {
189 return Err(Error::DecodeOverflow { id: decoded });
190 }
191 Ok(decoded)
192 }
193}
194
195impl<ID> Base32SnowExt for ID
196where
197 ID: SnowflakeId,
198 ID::Ty: BeBytes,
199{
200}
201
202#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
204pub struct Base32SnowFormatter<T>
205where
206 T: Base32SnowExt,
207 T::Ty: BeBytes,
208{
209 _id: PhantomData<T>,
210 buf: <T::Ty as BeBytes>::Base32Array,
211}
212
213impl<T: Base32SnowExt> Base32SnowFormatter<T>
214where
215 T::Ty: BeBytes,
216{
217 pub fn new(id: &T) -> Self {
218 let mut buf = T::base32_array();
219 id.inner_encode_to_buf(&mut buf);
220 Self {
221 _id: PhantomData,
222 buf,
223 }
224 }
225
226 #[must_use]
228 pub fn as_bytes(&self) -> &[u8] {
229 self.buf.as_ref()
230 }
231
232 #[must_use]
234 pub fn as_str(&self) -> &str {
235 unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
237 }
238
239 #[cfg(feature = "alloc")]
241 #[must_use]
242 pub fn as_string(&self) -> alloc::string::String {
243 unsafe { alloc::string::String::from_utf8_unchecked(self.as_bytes().to_vec()) }
245 }
246
247 pub const fn into_inner(self) -> <T::Ty as BeBytes>::Base32Array {
249 self.buf
250 }
251}
252
253impl<T: Base32SnowExt> core::hash::Hash for Base32SnowFormatter<T>
254where
255 T::Ty: BeBytes,
256{
257 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
258 self.as_str().hash(state);
259 }
260}
261
262impl<T: Base32SnowExt> fmt::Display for Base32SnowFormatter<T>
263where
264 T::Ty: BeBytes,
265{
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 f.write_str(self.as_str())
268 }
269}
270
271impl<T: Base32SnowExt> fmt::Debug for Base32SnowFormatter<T>
272where
273 T::Ty: BeBytes,
274{
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 f.debug_tuple("Base32SnowFormatter")
277 .field(&self.as_str())
278 .finish()
279 }
280}
281
282#[cfg(feature = "alloc")]
283impl<T: Base32SnowExt> From<&Base32SnowFormatter<T>> for alloc::string::String
284where
285 T::Ty: BeBytes,
286{
287 fn from(formatter: &Base32SnowFormatter<T>) -> Self {
288 formatter.as_string()
289 }
290}
291#[cfg(feature = "alloc")]
292impl<T: Base32SnowExt> From<Base32SnowFormatter<T>> for alloc::string::String
293where
294 T::Ty: BeBytes,
295{
296 fn from(formatter: Base32SnowFormatter<T>) -> Self {
297 formatter.as_string()
298 }
299}
300
301impl<T: Base32SnowExt> AsRef<str> for Base32SnowFormatter<T>
302where
303 T::Ty: BeBytes,
304{
305 fn as_ref(&self) -> &str {
306 self.as_str()
307 }
308}
309
310impl<T: Base32SnowExt> AsRef<[u8]> for Base32SnowFormatter<T>
311where
312 T::Ty: BeBytes,
313{
314 fn as_ref(&self) -> &[u8] {
315 self.as_bytes()
316 }
317}
318
319impl<T: Base32SnowExt> core::ops::Deref for Base32SnowFormatter<T>
320where
321 T::Ty: BeBytes,
322{
323 type Target = str;
324
325 fn deref(&self) -> &str {
326 self.as_str()
327 }
328}
329
330impl<T: Base32SnowExt> core::borrow::Borrow<str> for Base32SnowFormatter<T>
331where
332 T::Ty: BeBytes,
333{
334 fn borrow(&self) -> &str {
335 self.as_str()
336 }
337}
338
339impl<T: Base32SnowExt> PartialEq<str> for Base32SnowFormatter<T>
340where
341 T::Ty: BeBytes,
342{
343 fn eq(&self, other: &str) -> bool {
344 self.as_str() == other
345 }
346}
347
348impl<T: Base32SnowExt> PartialEq<&str> for Base32SnowFormatter<T>
349where
350 T::Ty: BeBytes,
351{
352 fn eq(&self, other: &&str) -> bool {
353 self == *other
354 }
355}
356
357impl<T: Base32SnowExt> PartialEq<Base32SnowFormatter<T>> for &str
358where
359 T::Ty: BeBytes,
360{
361 fn eq(&self, other: &Base32SnowFormatter<T>) -> bool {
362 other == *self
363 }
364}
365
366#[cfg(feature = "alloc")]
367impl<T: Base32SnowExt> PartialEq<alloc::string::String> for Base32SnowFormatter<T>
368where
369 T::Ty: BeBytes,
370{
371 fn eq(&self, other: &alloc::string::String) -> bool {
372 self.as_str() == other.as_str()
373 }
374}
375#[cfg(feature = "alloc")]
376impl<T: Base32SnowExt> PartialEq<Base32SnowFormatter<T>> for alloc::string::String
377where
378 T::Ty: BeBytes,
379{
380 fn eq(&self, other: &Base32SnowFormatter<T>) -> bool {
381 self.as_str() == other.as_str()
382 }
383}
384
385#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
387pub struct Base32SnowFormatterRef<'a, T>
388where
389 T: Base32SnowExt,
390 T::Ty: BeBytes,
391{
392 _id: PhantomData<T>,
393 buf: &'a <T::Ty as BeBytes>::Base32Array,
394}
395
396impl<'a, T: Base32SnowExt> Base32SnowFormatterRef<'a, T>
397where
398 T::Ty: BeBytes,
399{
400 pub fn new(id: &T, buf: &'a mut <T::Ty as BeBytes>::Base32Array) -> Self {
401 id.inner_encode_to_buf(buf);
402 Self {
403 _id: PhantomData,
404 buf,
405 }
406 }
407
408 #[must_use]
410 pub fn as_bytes(&self) -> &[u8] {
411 self.buf.as_ref()
412 }
413
414 #[must_use]
416 pub fn as_str(&self) -> &str {
417 unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
419 }
420
421 #[cfg(feature = "alloc")]
423 #[must_use]
424 pub fn as_string(&self) -> alloc::string::String {
425 unsafe { alloc::string::String::from_utf8_unchecked(self.as_bytes().to_vec()) }
427 }
428}
429
430impl<T: Base32SnowExt> core::hash::Hash for Base32SnowFormatterRef<'_, T>
431where
432 T::Ty: BeBytes,
433{
434 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
435 self.as_str().hash(state);
436 }
437}
438
439impl<T: Base32SnowExt> fmt::Display for Base32SnowFormatterRef<'_, T>
440where
441 T::Ty: BeBytes,
442{
443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444 f.write_str(self.as_str())
445 }
446}
447
448impl<T: Base32SnowExt> fmt::Debug for Base32SnowFormatterRef<'_, T>
449where
450 T::Ty: BeBytes,
451{
452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453 f.debug_tuple("Base32SnowFormatterRef")
454 .field(&self.as_str())
455 .finish()
456 }
457}
458
459#[cfg(feature = "alloc")]
460impl<T: Base32SnowExt> From<&Base32SnowFormatterRef<'_, T>> for alloc::string::String
461where
462 T::Ty: BeBytes,
463{
464 fn from(formatter: &Base32SnowFormatterRef<'_, T>) -> Self {
465 formatter.as_string()
466 }
467}
468#[cfg(feature = "alloc")]
469impl<T: Base32SnowExt> From<Base32SnowFormatterRef<'_, T>> for alloc::string::String
470where
471 T::Ty: BeBytes,
472{
473 fn from(formatter: Base32SnowFormatterRef<'_, T>) -> Self {
474 formatter.as_string()
475 }
476}
477
478impl<T: Base32SnowExt> AsRef<str> for Base32SnowFormatterRef<'_, T>
479where
480 T::Ty: BeBytes,
481{
482 fn as_ref(&self) -> &str {
483 self.as_str()
484 }
485}
486
487impl<T: Base32SnowExt> AsRef<[u8]> for Base32SnowFormatterRef<'_, T>
488where
489 T::Ty: BeBytes,
490{
491 fn as_ref(&self) -> &[u8] {
492 self.as_bytes()
493 }
494}
495
496impl<T: Base32SnowExt> core::ops::Deref for Base32SnowFormatterRef<'_, T>
497where
498 T::Ty: BeBytes,
499{
500 type Target = str;
501
502 fn deref(&self) -> &str {
503 self.as_str()
504 }
505}
506
507impl<T: Base32SnowExt> core::borrow::Borrow<str> for Base32SnowFormatterRef<'_, T>
508where
509 T::Ty: BeBytes,
510{
511 fn borrow(&self) -> &str {
512 self.as_str()
513 }
514}
515
516impl<T: Base32SnowExt> PartialEq<str> for Base32SnowFormatterRef<'_, T>
517where
518 T::Ty: BeBytes,
519{
520 fn eq(&self, other: &str) -> bool {
521 self.as_str() == other
522 }
523}
524impl<T: Base32SnowExt> PartialEq<&str> for Base32SnowFormatterRef<'_, T>
525where
526 T::Ty: BeBytes,
527{
528 fn eq(&self, other: &&str) -> bool {
529 self == *other
530 }
531}
532impl<T: Base32SnowExt> PartialEq<Base32SnowFormatterRef<'_, T>> for &str
533where
534 T::Ty: BeBytes,
535{
536 fn eq(&self, other: &Base32SnowFormatterRef<'_, T>) -> bool {
537 other == *self
538 }
539}
540
541#[cfg(feature = "alloc")]
542impl<T: Base32SnowExt> PartialEq<alloc::string::String> for Base32SnowFormatterRef<'_, T>
543where
544 T::Ty: BeBytes,
545{
546 fn eq(&self, other: &alloc::string::String) -> bool {
547 self.as_str() == other.as_str()
548 }
549}
550#[cfg(feature = "alloc")]
551impl<T: Base32SnowExt> PartialEq<Base32SnowFormatterRef<'_, T>> for alloc::string::String
552where
553 T::Ty: BeBytes,
554{
555 fn eq(&self, other: &Base32SnowFormatterRef<'_, T>) -> bool {
556 self.as_str() == other.as_str()
557 }
558}
559
560#[cfg(all(test, feature = "alloc", feature = "snowflake"))]
561mod alloc_test {
562 use alloc::string::ToString;
563
564 use crate::{
565 base32::Base32SnowExt,
566 id::{SnowflakeDiscordId, SnowflakeInstagramId, SnowflakeMastodonId, SnowflakeTwitterId},
567 };
568
569 #[test]
570 fn twitter_display() {
571 let id = SnowflakeTwitterId::decode("01ARZ3NDEKTSV").unwrap();
572 assert_eq!(alloc::format!("{id}"), "01ARZ3NDEKTSV");
573 assert_eq!(id.to_string(), "01ARZ3NDEKTSV");
574 }
575 #[test]
576 fn instagram_display() {
577 let id = SnowflakeInstagramId::decode("01ARZ3NDEKTSV").unwrap();
578 assert_eq!(alloc::format!("{id}"), "01ARZ3NDEKTSV");
579 assert_eq!(id.to_string(), "01ARZ3NDEKTSV");
580 }
581 #[test]
582 fn mastodon_display() {
583 let id = SnowflakeMastodonId::decode("01ARZ3NDEKTSV").unwrap();
584 assert_eq!(alloc::format!("{id}"), "01ARZ3NDEKTSV");
585 assert_eq!(id.to_string(), "01ARZ3NDEKTSV");
586 }
587 #[test]
588 fn discord_display() {
589 let id = SnowflakeDiscordId::decode("01ARZ3NDEKTSV").unwrap();
590 assert_eq!(alloc::format!("{id}"), "01ARZ3NDEKTSV");
591 assert_eq!(id.to_string(), "01ARZ3NDEKTSV");
592 }
593}
594
595#[cfg(all(test, feature = "snowflake"))]
596mod test {
597 use crate::{
598 base32::{Base32SnowExt, Error},
599 id::{SnowflakeDiscordId, SnowflakeInstagramId, SnowflakeMastodonId, SnowflakeTwitterId},
600 };
601
602 #[test]
603 fn snow_try_from() {
604 let id = SnowflakeTwitterId::try_from("01ARZ3NDEKTSV").unwrap();
606 let encoded = id.encode();
607 assert_eq!(encoded, "01ARZ3NDEKTSV");
608 }
609
610 #[test]
611 fn snow_from_str() {
612 use core::str::FromStr;
614 let id = SnowflakeTwitterId::from_str("01ARZ3NDEKTSV").unwrap();
615 let encoded = id.encode();
616 assert_eq!(encoded, "01ARZ3NDEKTSV");
617 }
618
619 #[test]
620 fn twitter_max() {
621 let id = SnowflakeTwitterId::from_components(
622 SnowflakeTwitterId::max_timestamp(),
623 SnowflakeTwitterId::max_machine_id(),
624 SnowflakeTwitterId::max_sequence(),
625 );
626 assert_eq!(id.timestamp(), SnowflakeTwitterId::max_timestamp());
627 assert_eq!(id.machine_id(), SnowflakeTwitterId::max_machine_id());
628 assert_eq!(id.sequence(), SnowflakeTwitterId::max_sequence());
629
630 let encoded = id.encode();
631 assert_eq!(encoded, "7ZZZZZZZZZZZZ");
632 let decoded = SnowflakeTwitterId::decode(encoded).unwrap();
633
634 assert_eq!(decoded.timestamp(), SnowflakeTwitterId::max_timestamp());
635 assert_eq!(decoded.machine_id(), SnowflakeTwitterId::max_machine_id());
636 assert_eq!(decoded.sequence(), SnowflakeTwitterId::max_sequence());
637 assert_eq!(id, decoded);
638 }
639
640 #[test]
641 fn twitter_zero() {
642 let id = SnowflakeTwitterId::from_components(0, 0, 0);
643 assert_eq!(id.timestamp(), 0);
644 assert_eq!(id.machine_id(), 0);
645 assert_eq!(id.sequence(), 0);
646
647 let encoded = id.encode();
648 assert_eq!(encoded, "0000000000000");
649 let decoded = SnowflakeTwitterId::decode(encoded).unwrap();
650
651 assert_eq!(decoded.timestamp(), 0);
652 assert_eq!(decoded.machine_id(), 0);
653 assert_eq!(decoded.sequence(), 0);
654 assert_eq!(id, decoded);
655 }
656
657 #[test]
658 fn discord_max() {
659 let id = SnowflakeDiscordId::from_components(
660 SnowflakeDiscordId::max_timestamp(),
661 SnowflakeDiscordId::max_machine_id(),
662 SnowflakeDiscordId::max_sequence(),
663 );
664 assert_eq!(id.timestamp(), SnowflakeDiscordId::max_timestamp());
665 assert_eq!(id.machine_id(), SnowflakeDiscordId::max_machine_id());
666 assert_eq!(id.sequence(), SnowflakeDiscordId::max_sequence());
667
668 let encoded = id.encode();
669 assert_eq!(encoded, "FZZZZZZZZZZZZ");
670 let decoded = SnowflakeDiscordId::decode(encoded).unwrap();
671
672 assert_eq!(decoded.timestamp(), SnowflakeDiscordId::max_timestamp());
673 assert_eq!(decoded.machine_id(), SnowflakeDiscordId::max_machine_id());
674 assert_eq!(decoded.sequence(), SnowflakeDiscordId::max_sequence());
675 assert_eq!(id, decoded);
676 }
677
678 #[test]
679 fn discord_zero() {
680 let id = SnowflakeDiscordId::from_components(0, 0, 0);
681 assert_eq!(id.timestamp(), 0);
682 assert_eq!(id.machine_id(), 0);
683 assert_eq!(id.sequence(), 0);
684
685 let encoded = id.encode();
686 assert_eq!(encoded, "0000000000000");
687 let decoded = SnowflakeDiscordId::decode(encoded).unwrap();
688
689 assert_eq!(decoded.timestamp(), 0);
690 assert_eq!(decoded.machine_id(), 0);
691 assert_eq!(decoded.sequence(), 0);
692 assert_eq!(id, decoded);
693 }
694
695 #[test]
696 fn instagram_max() {
697 let id = SnowflakeInstagramId::from_components(
698 SnowflakeInstagramId::max_timestamp(),
699 SnowflakeInstagramId::max_machine_id(),
700 SnowflakeInstagramId::max_sequence(),
701 );
702 assert_eq!(id.timestamp(), SnowflakeInstagramId::max_timestamp());
703 assert_eq!(id.machine_id(), SnowflakeInstagramId::max_machine_id());
704 assert_eq!(id.sequence(), SnowflakeInstagramId::max_sequence());
705
706 let encoded = id.encode();
707 assert_eq!(encoded, "FZZZZZZZZZZZZ");
708 let decoded = SnowflakeInstagramId::decode(encoded).unwrap();
709
710 assert_eq!(decoded.timestamp(), SnowflakeInstagramId::max_timestamp());
711 assert_eq!(decoded.machine_id(), SnowflakeInstagramId::max_machine_id());
712 assert_eq!(decoded.sequence(), SnowflakeInstagramId::max_sequence());
713 assert_eq!(id, decoded);
714 }
715
716 #[test]
717 fn instagram_zero() {
718 let id = SnowflakeInstagramId::from_components(0, 0, 0);
719 assert_eq!(id.timestamp(), 0);
720 assert_eq!(id.machine_id(), 0);
721 assert_eq!(id.sequence(), 0);
722
723 let encoded = id.encode();
724 assert_eq!(encoded, "0000000000000");
725 let decoded = SnowflakeInstagramId::decode(encoded).unwrap();
726
727 assert_eq!(decoded.timestamp(), 0);
728 assert_eq!(decoded.machine_id(), 0);
729 assert_eq!(decoded.sequence(), 0);
730 assert_eq!(id, decoded);
731 }
732
733 #[test]
734 fn mastodon_max() {
735 let id = SnowflakeMastodonId::from_components(
736 SnowflakeMastodonId::max_timestamp(),
737 SnowflakeMastodonId::max_machine_id(),
738 SnowflakeMastodonId::max_sequence(),
739 );
740 assert_eq!(id.timestamp(), SnowflakeMastodonId::max_timestamp());
741 assert_eq!(id.machine_id(), SnowflakeMastodonId::max_machine_id());
742 assert_eq!(id.sequence(), SnowflakeMastodonId::max_sequence());
743
744 let encoded = id.encode();
745 assert_eq!(encoded, "FZZZZZZZZZZZZ");
746 let decoded = SnowflakeMastodonId::decode(encoded).unwrap();
747
748 assert_eq!(decoded.timestamp(), SnowflakeMastodonId::max_timestamp());
749 assert_eq!(decoded.machine_id(), SnowflakeMastodonId::max_machine_id());
750 assert_eq!(decoded.sequence(), SnowflakeMastodonId::max_sequence());
751 assert_eq!(id, decoded);
752 }
753
754 #[test]
755 fn mastodon_zero() {
756 let id = SnowflakeMastodonId::from_components(0, 0, 0);
757 assert_eq!(id.timestamp(), 0);
758 assert_eq!(id.machine_id(), 0);
759 assert_eq!(id.sequence(), 0);
760
761 let encoded = id.encode();
762 assert_eq!(encoded, "0000000000000");
763 let decoded = SnowflakeMastodonId::decode(encoded).unwrap();
764
765 assert_eq!(decoded.timestamp(), 0);
766 assert_eq!(decoded.machine_id(), 0);
767 assert_eq!(decoded.sequence(), 0);
768 assert_eq!(id, decoded);
769 }
770
771 #[test]
772 fn decode_invalid_character_fails() {
773 let invalid = "000000@000000";
775 let res = SnowflakeTwitterId::decode(invalid);
776 assert_eq!(
777 res.unwrap_err(),
778 Error::DecodeInvalidAscii {
779 byte: b'@',
780 index: 6,
781 }
782 );
783 }
784
785 #[test]
786 fn decode_invalid_length_fails() {
787 let too_short = "012345678901";
789 let res = SnowflakeTwitterId::decode(too_short);
790 assert_eq!(res.unwrap_err(), Error::DecodeInvalidLen { len: 12 });
791
792 let too_long = "01234567890123";
794 let res = SnowflakeTwitterId::decode(too_long);
795
796 assert_eq!(res.unwrap_err(), Error::DecodeInvalidLen { len: 14 });
797 }
798}