Skip to content

Commit e18e057

Browse files
committed
Move const-compatible API onto decode::DecodeBuilder directly
1 parent e65bfa7 commit e18e057

File tree

4 files changed

+154
-168
lines changed

4 files changed

+154
-168
lines changed

src/decode.rs

+150-5
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ impl<const N: usize> DecodeTarget for [u8; N] {
196196
impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
197197
/// Setup decoder for the given string using the given alphabet.
198198
/// Preferably use [`bs58::decode`](crate::decode()) instead of this directly.
199-
pub fn new(input: I, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
199+
pub const fn new(input: I, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
200200
DecodeBuilder {
201201
input,
202202
alpha,
@@ -205,7 +205,7 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
205205
}
206206

207207
/// Setup decoder for the given string using default prepared alphabet.
208-
pub(crate) fn from_input(input: I) -> DecodeBuilder<'static, I> {
208+
pub(crate) const fn from_input(input: I) -> DecodeBuilder<'static, I> {
209209
DecodeBuilder {
210210
input,
211211
alpha: Alphabet::DEFAULT,
@@ -225,8 +225,9 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
225225
/// .into_vec()?);
226226
/// # Ok::<(), bs58::decode::Error>(())
227227
/// ```
228-
pub fn with_alphabet(self, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
229-
DecodeBuilder { alpha, ..self }
228+
pub const fn with_alphabet(mut self, alpha: &'a Alphabet) -> DecodeBuilder<'a, I> {
229+
self.alpha = alpha;
230+
self
230231
}
231232

232233
/// Expect and check checksum using the [Base58Check][] algorithm when
@@ -276,7 +277,6 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
276277
let check = Check::CB58(expected_ver);
277278
DecodeBuilder { check, ..self }
278279
}
279-
280280
/// Decode into a new vector of bytes.
281281
///
282282
/// See the documentation for [`bs58::decode`](crate::decode()) for an
@@ -348,6 +348,66 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> {
348348
}
349349
}
350350

351+
/// For `const` compatibility we are restricted to using a concrete input and output type, as
352+
/// `const` trait implementations and `&mut` are unstable. These methods will eventually be
353+
/// deprecated once the primary interfaces can be converted into `const fn` directly.
354+
impl<'a, 'b> DecodeBuilder<'a, &'b [u8]> {
355+
/// Decode into a new array.
356+
///
357+
/// Returns the decoded array as bytes.
358+
///
359+
/// See the documentation for [`bs58::decode`](crate::decode())
360+
/// for an explanation of the errors that may occur.
361+
///
362+
/// # Examples
363+
///
364+
/// ```rust
365+
/// const _: () = {
366+
/// let Ok(output) = bs58::decode(b"EUYUqQf".as_slice()).into_array_const::<5>() else {
367+
/// panic!()
368+
/// };
369+
/// assert!(matches!(&output, b"world"));
370+
/// };
371+
/// ```
372+
pub const fn into_array_const<const N: usize>(self) -> Result<[u8; N]> {
373+
assert!(
374+
matches!(self.check, Check::Disabled),
375+
"checksums in const aren't supported (why are you using this API at runtime)",
376+
);
377+
decode_into_const(self.input, self.alpha)
378+
}
379+
380+
/// [`Self::into_array_const`] but the result will be unwrapped, turning any error into a panic
381+
/// message via [`Error::unwrap_const`], as a simple `into_array_const().unwrap()` isn't
382+
/// possible yet.
383+
///
384+
/// # Examples
385+
///
386+
/// ```rust
387+
/// const _: () = {
388+
/// let output: [u8; 5] = bs58::decode(b"EUYUqQf".as_slice()).into_array_const_unwrap();
389+
/// assert!(matches!(&output, b"world"));
390+
/// };
391+
/// ```
392+
///
393+
/// ```rust
394+
/// const _: () = {
395+
/// assert!(matches!(
396+
/// bs58::decode(b"he11owor1d".as_slice())
397+
/// .with_alphabet(bs58::Alphabet::RIPPLE)
398+
/// .into_array_const_unwrap(),
399+
/// [0x60, 0x65, 0xe7, 0x9b, 0xba, 0x2f, 0x78],
400+
/// ));
401+
/// };
402+
/// ```
403+
pub const fn into_array_const_unwrap<const N: usize>(self) -> [u8; N] {
404+
match self.into_array_const() {
405+
Ok(result) => result,
406+
Err(err) => err.unwrap_const(),
407+
}
408+
}
409+
}
410+
351411
fn decode_into(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Result<usize> {
352412
let mut index = 0;
353413
let zero = alpha.encode[0];
@@ -480,6 +540,69 @@ fn decode_cb58_into(
480540
}
481541
}
482542

543+
const fn decode_into_const<const N: usize>(input: &[u8], alpha: &Alphabet) -> Result<[u8; N]> {
544+
let mut output = [0u8; N];
545+
let mut index = 0;
546+
let zero = alpha.encode[0];
547+
548+
let mut i = 0;
549+
while i < input.len() {
550+
let c = input[i];
551+
if c > 127 {
552+
return Err(Error::NonAsciiCharacter { index: i });
553+
}
554+
555+
let mut val = alpha.decode[c as usize] as usize;
556+
if val == 0xFF {
557+
return Err(Error::InvalidCharacter {
558+
character: c as char,
559+
index: i,
560+
});
561+
}
562+
563+
let mut j = 0;
564+
while j < index {
565+
let byte = output[j];
566+
val += (byte as usize) * 58;
567+
output[j] = (val & 0xFF) as u8;
568+
val >>= 8;
569+
j += 1;
570+
}
571+
572+
while val > 0 {
573+
if index >= output.len() {
574+
return Err(Error::BufferTooSmall);
575+
}
576+
output[index] = (val & 0xFF) as u8;
577+
index += 1;
578+
val >>= 8
579+
}
580+
i += 1;
581+
}
582+
583+
let mut i = 0;
584+
while i < input.len() && input[i] == zero {
585+
if index >= output.len() {
586+
return Err(Error::BufferTooSmall);
587+
}
588+
output[index] = 0;
589+
index += 1;
590+
i += 1;
591+
}
592+
593+
// reverse
594+
let mut i = 0;
595+
let n = index / 2;
596+
while i < n {
597+
let x = output[i];
598+
output[i] = output[index - 1 - i];
599+
output[index - 1 - i] = x;
600+
i += 1;
601+
}
602+
603+
Ok(output)
604+
}
605+
483606
#[cfg(feature = "std")]
484607
impl std::error::Error for Error {}
485608

@@ -520,3 +643,25 @@ impl fmt::Display for Error {
520643
}
521644
}
522645
}
646+
647+
impl Error {
648+
/// Panic with an error message based on this error. This cannot include any of the dynamic
649+
/// content because formatting in `const` is not yet possible.
650+
pub const fn unwrap_const(self) -> ! {
651+
match self {
652+
Error::BufferTooSmall => {
653+
panic!("buffer provided to decode base58 encoded string into was too small")
654+
}
655+
Error::InvalidCharacter { .. } => panic!("provided string contained invalid character"),
656+
Error::NonAsciiCharacter { .. } => {
657+
panic!("provided string contained non-ascii character")
658+
}
659+
#[cfg(any(feature = "check", feature = "cb58"))]
660+
Error::InvalidChecksum { .. } => panic!("invalid checksum"),
661+
#[cfg(any(feature = "check", feature = "cb58"))]
662+
Error::InvalidVersion { .. } => panic!("invalid version"),
663+
#[cfg(any(feature = "check", feature = "cb58"))]
664+
Error::NoChecksum => panic!("provided string is too small to contain a checksum"),
665+
}
666+
}
667+
}

src/decode_const.rs

-117
This file was deleted.

src/lib.rs

+1-43
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ pub mod alphabet;
8888
pub use alphabet::Alphabet;
8989

9090
pub mod decode;
91-
pub mod decode_const;
9291
pub mod encode;
9392

9493
#[cfg(any(feature = "check", feature = "cb58"))]
@@ -168,51 +167,10 @@ enum Check {
168167
/// bs58::decode::Error::BufferTooSmall,
169168
/// bs58::decode("he11owor1d").onto(&mut output).unwrap_err());
170169
/// ```
171-
pub fn decode<I: AsRef<[u8]>>(input: I) -> decode::DecodeBuilder<'static, I> {
170+
pub const fn decode<I: AsRef<[u8]>>(input: I) -> decode::DecodeBuilder<'static, I> {
172171
decode::DecodeBuilder::from_input(input)
173172
}
174173

175-
/// Setup decoder for the given string using the [default alphabet][Alphabet::DEFAULT].
176-
///
177-
/// Usable in `const` contexts, so the size of the output array must be specified.
178-
///
179-
/// # Examples
180-
///
181-
/// ## Basic example
182-
///
183-
/// ```rust
184-
/// assert_eq!(
185-
/// vec![0x04, 0x30, 0x5e, 0x2b, 0x24, 0x73, 0xf0, 0x58],
186-
/// bs58::decode_const(b"he11owor1d").into_array::<8>());
187-
/// ```
188-
///
189-
/// ## Changing the alphabet
190-
///
191-
/// ```rust
192-
/// assert_eq!(
193-
/// vec![0x60, 0x65, 0xe7, 0x9b, 0xba, 0x2f, 0x78],
194-
/// bs58::decode_const(b"he11owor1d")
195-
/// .with_alphabet(bs58::Alphabet::RIPPLE)
196-
/// .into_array::<7>());
197-
/// ```
198-
///
199-
/// ## Errors
200-
///
201-
/// ### Invalid Character
202-
///
203-
/// ```should_panic
204-
/// bs58::decode_const(b"hello world").into_array::<10>();
205-
/// ```
206-
///
207-
/// ### Non-ASCII Character
208-
///
209-
/// ```should_panic
210-
/// bs58::decode_const("he11o🇳🇿".as_bytes()).into_array::<10>();
211-
/// ```
212-
pub const fn decode_const(input: &[u8]) -> decode_const::DecodeBuilder<'_, '_> {
213-
decode_const::DecodeBuilder::from_input(input)
214-
}
215-
216174
/// Setup encoder for the given bytes using the [default alphabet][Alphabet::DEFAULT].
217175
///
218176
/// # Examples

0 commit comments

Comments
 (0)