1use super::interface::Base32Ext;
2use crate::{Base32Error, BeBytes, Error, Id, Result, SnowflakeId};
3use core::fmt;
4use core::marker::PhantomData;
5
6pub trait Base32SnowExt: SnowflakeId
12where
13 Self::Ty: BeBytes,
14{
15 #[must_use]
25 fn buf() -> <<Self as Id>::Ty as BeBytes>::Base32Array {
26 <Self as Base32Ext>::inner_buf()
27 }
28 fn encode(&self) -> Base32SnowFormatter<Self> {
51 Base32SnowFormatter::new(self)
52 }
53 fn encode_to_buf<'buf>(
85 &self,
86 buf: &'buf mut <<Self as Id>::Ty as BeBytes>::Base32Array,
87 ) -> Base32SnowFormatterRef<'buf, Self> {
88 Base32SnowFormatterRef::new(self, buf)
89 }
90 fn decode(s: impl AsRef<str>) -> Result<Self, Error<Self>> {
179 let decoded = Self::inner_decode(s)?;
180 if !decoded.is_valid() {
181 return Err(Error::Base32Error(Base32Error::DecodeOverflow {
182 id: decoded,
183 }));
184 }
185 Ok(decoded)
186 }
187}
188
189impl<ID> Base32SnowExt for ID
190where
191 ID: SnowflakeId,
192 ID::Ty: BeBytes,
193{
194}
195
196#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
198pub struct Base32SnowFormatter<T>
199where
200 T: Base32SnowExt,
201 T::Ty: BeBytes,
202{
203 _id: PhantomData<T>,
204 buf: <T::Ty as BeBytes>::Base32Array,
205}
206
207impl<T: Base32SnowExt> Base32SnowFormatter<T>
208where
209 T::Ty: BeBytes,
210{
211 pub fn new(id: &T) -> Self {
212 let mut buf = T::buf();
213 id.inner_encode_to_buf(&mut buf);
214 Self {
215 _id: PhantomData,
216 buf,
217 }
218 }
219
220 #[must_use]
222 pub fn as_str(&self) -> &str {
223 unsafe { core::str::from_utf8_unchecked(self.buf.as_ref()) }
225 }
226
227 #[cfg(feature = "alloc")]
229 #[cfg_attr(not(feature = "alloc"), doc(hidden))]
230 #[allow(clippy::inherent_to_string_shadow_display)]
231 #[must_use]
232 pub fn to_string(&self) -> alloc::string::String {
233 unsafe { alloc::string::String::from_utf8_unchecked(self.buf.as_ref().to_vec()) }
235 }
236
237 pub const fn into_inner(self) -> <T::Ty as BeBytes>::Base32Array {
239 self.buf
240 }
241}
242
243impl<T: Base32SnowExt> fmt::Display for Base32SnowFormatter<T>
244where
245 T::Ty: BeBytes,
246{
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 f.write_str(self.as_str())
249 }
250}
251
252impl<T: Base32SnowExt> AsRef<str> for Base32SnowFormatter<T>
253where
254 T::Ty: BeBytes,
255{
256 fn as_ref(&self) -> &str {
257 self.as_str()
258 }
259}
260
261impl<T: Base32SnowExt> PartialEq<&str> for Base32SnowFormatter<T>
262where
263 T::Ty: BeBytes,
264{
265 fn eq(&self, other: &&str) -> bool {
266 self.as_str() == *other
267 }
268}
269
270#[cfg(feature = "std")]
271#[cfg_attr(not(feature = "std"), doc(hidden))]
272impl<T: Base32SnowExt> PartialEq<alloc::string::String> for Base32SnowFormatter<T>
273where
274 T::Ty: BeBytes,
275{
276 fn eq(&self, other: &alloc::string::String) -> bool {
277 self.as_str() == other.as_str()
278 }
279}
280
281#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
283pub struct Base32SnowFormatterRef<'a, T>
284where
285 T: Base32SnowExt,
286 T::Ty: BeBytes,
287{
288 _id: PhantomData<T>,
289 buf: &'a <T::Ty as BeBytes>::Base32Array,
290}
291
292impl<'a, T: Base32SnowExt> Base32SnowFormatterRef<'a, T>
293where
294 T::Ty: BeBytes,
295{
296 pub fn new(id: &T, buf: &'a mut <T::Ty as BeBytes>::Base32Array) -> Self {
297 id.inner_encode_to_buf(buf);
298 Self {
299 _id: PhantomData,
300 buf,
301 }
302 }
303
304 #[must_use]
306 pub fn as_str(&self) -> &str {
307 unsafe { core::str::from_utf8_unchecked(self.buf.as_ref()) }
309 }
310
311 #[cfg(feature = "alloc")]
313 #[cfg_attr(not(feature = "alloc"), doc(hidden))]
314 #[allow(clippy::inherent_to_string_shadow_display)]
315 #[must_use]
316 pub fn to_string(&self) -> alloc::string::String {
317 unsafe { alloc::string::String::from_utf8_unchecked(self.buf.as_ref().to_vec()) }
319 }
320}
321
322impl<T: Base32SnowExt> fmt::Display for Base32SnowFormatterRef<'_, T>
323where
324 T::Ty: BeBytes,
325{
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 f.write_str(self.as_str())
328 }
329}
330
331impl<T: Base32SnowExt> AsRef<str> for Base32SnowFormatterRef<'_, T>
332where
333 T::Ty: BeBytes,
334{
335 fn as_ref(&self) -> &str {
336 self.as_str()
337 }
338}
339
340impl<T: Base32SnowExt> PartialEq<str> for Base32SnowFormatterRef<'_, T>
341where
342 T::Ty: BeBytes,
343{
344 fn eq(&self, other: &str) -> bool {
345 self.as_str() == other
346 }
347}
348impl<T: Base32SnowExt> PartialEq<&str> for Base32SnowFormatterRef<'_, T>
349where
350 T::Ty: BeBytes,
351{
352 fn eq(&self, other: &&str) -> bool {
353 self.as_str() == *other
354 }
355}
356
357#[cfg(feature = "alloc")]
358#[cfg_attr(not(feature = "alloc"), doc(hidden))]
359impl<T: Base32SnowExt> PartialEq<alloc::string::String> for Base32SnowFormatterRef<'_, T>
360where
361 T::Ty: BeBytes,
362{
363 fn eq(&self, other: &alloc::string::String) -> bool {
364 self.as_str() == other.as_str()
365 }
366}
367
368#[cfg(all(test, feature = "snowflake"))]
369mod test {
370 use crate::{
371 Base32Error, Base32SnowExt, Error, SnowflakeDiscordId, SnowflakeId, SnowflakeInstagramId,
372 SnowflakeMastodonId, SnowflakeTwitterId,
373 };
374
375 #[test]
376 fn twitter_max() {
377 let id = SnowflakeTwitterId::from_components(
378 SnowflakeTwitterId::max_timestamp(),
379 SnowflakeTwitterId::max_machine_id(),
380 SnowflakeTwitterId::max_sequence(),
381 );
382 assert_eq!(id.timestamp(), SnowflakeTwitterId::max_timestamp());
383 assert_eq!(id.machine_id(), SnowflakeTwitterId::max_machine_id());
384 assert_eq!(id.sequence(), SnowflakeTwitterId::max_sequence());
385
386 let encoded = id.encode();
387 assert_eq!(encoded, "7ZZZZZZZZZZZZ");
388 let decoded = SnowflakeTwitterId::decode(encoded).unwrap();
389
390 assert_eq!(decoded.timestamp(), SnowflakeTwitterId::max_timestamp());
391 assert_eq!(decoded.machine_id(), SnowflakeTwitterId::max_machine_id());
392 assert_eq!(decoded.sequence(), SnowflakeTwitterId::max_sequence());
393 assert_eq!(id, decoded);
394 }
395
396 #[test]
397 fn twitter_zero() {
398 let id = SnowflakeTwitterId::from_components(0, 0, 0);
399 assert_eq!(id.timestamp(), 0);
400 assert_eq!(id.machine_id(), 0);
401 assert_eq!(id.sequence(), 0);
402
403 let encoded = id.encode();
404 assert_eq!(encoded, "0000000000000");
405 let decoded = SnowflakeTwitterId::decode(encoded).unwrap();
406
407 assert_eq!(decoded.timestamp(), 0);
408 assert_eq!(decoded.machine_id(), 0);
409 assert_eq!(decoded.sequence(), 0);
410 assert_eq!(id, decoded);
411 }
412
413 #[test]
414 fn discord_max() {
415 let id = SnowflakeDiscordId::from_components(
416 SnowflakeDiscordId::max_timestamp(),
417 SnowflakeDiscordId::max_machine_id(),
418 SnowflakeDiscordId::max_sequence(),
419 );
420 assert_eq!(id.timestamp(), SnowflakeDiscordId::max_timestamp());
421 assert_eq!(id.machine_id(), SnowflakeDiscordId::max_machine_id());
422 assert_eq!(id.sequence(), SnowflakeDiscordId::max_sequence());
423
424 let encoded = id.encode();
425 assert_eq!(encoded, "FZZZZZZZZZZZZ");
426 let decoded = SnowflakeDiscordId::decode(encoded).unwrap();
427
428 assert_eq!(decoded.timestamp(), SnowflakeDiscordId::max_timestamp());
429 assert_eq!(decoded.machine_id(), SnowflakeDiscordId::max_machine_id());
430 assert_eq!(decoded.sequence(), SnowflakeDiscordId::max_sequence());
431 assert_eq!(id, decoded);
432 }
433
434 #[test]
435 fn discord_zero() {
436 let id = SnowflakeDiscordId::from_components(0, 0, 0);
437 assert_eq!(id.timestamp(), 0);
438 assert_eq!(id.machine_id(), 0);
439 assert_eq!(id.sequence(), 0);
440
441 let encoded = id.encode();
442 assert_eq!(encoded, "0000000000000");
443 let decoded = SnowflakeDiscordId::decode(encoded).unwrap();
444
445 assert_eq!(decoded.timestamp(), 0);
446 assert_eq!(decoded.machine_id(), 0);
447 assert_eq!(decoded.sequence(), 0);
448 assert_eq!(id, decoded);
449 }
450
451 #[test]
452 fn instagram_max() {
453 let id = SnowflakeInstagramId::from_components(
454 SnowflakeInstagramId::max_timestamp(),
455 SnowflakeInstagramId::max_machine_id(),
456 SnowflakeInstagramId::max_sequence(),
457 );
458 assert_eq!(id.timestamp(), SnowflakeInstagramId::max_timestamp());
459 assert_eq!(id.machine_id(), SnowflakeInstagramId::max_machine_id());
460 assert_eq!(id.sequence(), SnowflakeInstagramId::max_sequence());
461
462 let encoded = id.encode();
463 assert_eq!(encoded, "FZZZZZZZZZZZZ");
464 let decoded = SnowflakeInstagramId::decode(encoded).unwrap();
465
466 assert_eq!(decoded.timestamp(), SnowflakeInstagramId::max_timestamp());
467 assert_eq!(decoded.machine_id(), SnowflakeInstagramId::max_machine_id());
468 assert_eq!(decoded.sequence(), SnowflakeInstagramId::max_sequence());
469 assert_eq!(id, decoded);
470 }
471
472 #[test]
473 fn instagram_zero() {
474 let id = SnowflakeInstagramId::from_components(0, 0, 0);
475 assert_eq!(id.timestamp(), 0);
476 assert_eq!(id.machine_id(), 0);
477 assert_eq!(id.sequence(), 0);
478
479 let encoded = id.encode();
480 assert_eq!(encoded, "0000000000000");
481 let decoded = SnowflakeInstagramId::decode(encoded).unwrap();
482
483 assert_eq!(decoded.timestamp(), 0);
484 assert_eq!(decoded.machine_id(), 0);
485 assert_eq!(decoded.sequence(), 0);
486 assert_eq!(id, decoded);
487 }
488
489 #[test]
490 fn mastodon_max() {
491 let id = SnowflakeMastodonId::from_components(
492 SnowflakeMastodonId::max_timestamp(),
493 SnowflakeMastodonId::max_machine_id(),
494 SnowflakeMastodonId::max_sequence(),
495 );
496 assert_eq!(id.timestamp(), SnowflakeMastodonId::max_timestamp());
497 assert_eq!(id.machine_id(), SnowflakeMastodonId::max_machine_id());
498 assert_eq!(id.sequence(), SnowflakeMastodonId::max_sequence());
499
500 let encoded = id.encode();
501 assert_eq!(encoded, "FZZZZZZZZZZZZ");
502 let decoded = SnowflakeMastodonId::decode(encoded).unwrap();
503
504 assert_eq!(decoded.timestamp(), SnowflakeMastodonId::max_timestamp());
505 assert_eq!(decoded.machine_id(), SnowflakeMastodonId::max_machine_id());
506 assert_eq!(decoded.sequence(), SnowflakeMastodonId::max_sequence());
507 assert_eq!(id, decoded);
508 }
509
510 #[test]
511 fn mastodon_zero() {
512 let id = SnowflakeMastodonId::from_components(0, 0, 0);
513 assert_eq!(id.timestamp(), 0);
514 assert_eq!(id.machine_id(), 0);
515 assert_eq!(id.sequence(), 0);
516
517 let encoded = id.encode();
518 assert_eq!(encoded, "0000000000000");
519 let decoded = SnowflakeMastodonId::decode(encoded).unwrap();
520
521 assert_eq!(decoded.timestamp(), 0);
522 assert_eq!(decoded.machine_id(), 0);
523 assert_eq!(decoded.sequence(), 0);
524 assert_eq!(id, decoded);
525 }
526
527 #[test]
528 fn decode_invalid_character_fails() {
529 let invalid = "012345678901@";
531 let result = SnowflakeTwitterId::decode(invalid);
532 assert!(matches!(
533 result,
534 Err(Error::Base32Error(Base32Error::DecodeInvalidAscii {
535 byte: 64
536 }))
537 ));
538 }
539
540 #[test]
541 fn decode_invalid_length_fails() {
542 let too_short = "012345678901";
544 let result = SnowflakeTwitterId::decode(too_short);
545 assert!(matches!(
546 result,
547 Err(Error::Base32Error(Base32Error::DecodeInvalidLen {
548 len: 12
549 }))
550 ));
551
552 let too_long = "01234567890123";
554 let result = SnowflakeTwitterId::decode(too_long);
555 assert!(matches!(
556 result,
557 Err(Error::Base32Error(Base32Error::DecodeInvalidLen {
558 len: 14
559 }))
560 ));
561 }
562}