bluetape_rs_codec/base62.rs
1//! Strict byte-oriented Base62 encoding and decoding.
2
3use std::error::Error;
4use std::fmt;
5
6use crate::base_n::{BaseNDecodeError, decode_base_n, encode_base_n};
7
8const BASE62_ALPHABET: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
9
10/// Error returned when Base62 decoding rejects caller-owned input.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum Base62DecodeError {
14 /// The encoded text contains a byte outside the selected Base62 alphabet.
15 InvalidCharacter {
16 /// Zero-based byte position of the invalid byte.
17 index: usize,
18 /// Invalid byte value.
19 byte: u8,
20 },
21}
22
23impl fmt::Display for Base62DecodeError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::InvalidCharacter { index, byte } => {
27 write!(
28 f,
29 "base62 input contains invalid byte 0x{byte:02x} at position {index}"
30 )
31 }
32 }
33 }
34}
35
36impl Error for Base62DecodeError {}
37
38impl From<BaseNDecodeError> for Base62DecodeError {
39 fn from(error: BaseNDecodeError) -> Self {
40 match error {
41 BaseNDecodeError::InvalidCharacter { index, byte } => {
42 Self::InvalidCharacter { index, byte }
43 }
44 }
45 }
46}
47
48/// Encodes bytes with the bluetape Base62 alphabet.
49///
50/// The alphabet is `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`.
51/// This API is byte-oriented; UUID and integer rendering should build on top of
52/// it in a separate ID-focused crate. Leading zero bytes are preserved as
53/// leading `0` characters.
54///
55/// # Examples
56///
57/// ```
58/// use bluetape_rs_codec::encode_base62;
59///
60/// assert_eq!(encode_base62(b"Hello, World!"), "1wJfrzvdbtXUOlUjUf");
61/// ```
62#[must_use]
63pub fn encode_base62(bytes: impl AsRef<[u8]>) -> String {
64 encode_base_n(bytes.as_ref(), BASE62_ALPHABET)
65}
66
67/// Decodes byte-oriented Base62 text into bytes.
68///
69/// # Examples
70///
71/// ```
72/// use bluetape_rs_codec::decode_base62;
73///
74/// assert_eq!(decode_base62("1wJfrzvdbtXUOlUjUf")?, b"Hello, World!");
75/// # Ok::<(), bluetape_rs_codec::Base62DecodeError>(())
76/// ```
77///
78/// # Errors
79///
80/// Returns [`Base62DecodeError`] when input contains bytes outside the Base62
81/// alphabet.
82pub fn decode_base62(encoded: impl AsRef<str>) -> Result<Vec<u8>, Base62DecodeError> {
83 decode_base_n(encoded.as_ref(), BASE62_ALPHABET).map_err(Into::into)
84}