use std::fmt::Display;
use std::str::FromStr;
use base64::Engine;
use crate::{BASE64_OUTPUT_SIZE, Hash, OUTPUT_SIZE, ParseError};
pub(crate) const BASE64: base64::engine::GeneralPurpose = base64::prelude::BASE64_URL_SAFE_NO_PAD;
#[derive(Debug, thiserror::Error)]
pub enum Base64ParseError {
#[error(transparent)]
Decode(#[from] base64::DecodeSliceError),
#[error(transparent)]
Parse(#[from] ParseError),
}
impl Hash {
pub(crate) fn write_base64<'a>(&self, output: &'a mut [u8]) -> &'a str {
let bytes = self.to_bytes();
let encoded = BASE64.encode_slice(&bytes, output).unwrap();
assert_eq!(encoded, BASE64_OUTPUT_SIZE);
unsafe { str::from_utf8_unchecked(&output[..BASE64_OUTPUT_SIZE]) }
}
}
impl FromStr for Hash {
type Err = Base64ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = [0u8; OUTPUT_SIZE];
BASE64.decode_slice(s, &mut bytes)?;
Ok(Hash::from_bytes(&bytes)?)
}
}
impl Display for Hash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut output = [0u8; BASE64_OUTPUT_SIZE];
f.write_str(self.write_base64(&mut output))
}
}
#[cfg(test)]
mod tests {
extern crate test;
use std::hint::black_box;
use test::Bencher;
use crate::tests::HASH;
use crate::*;
#[test]
fn round_trip() {
assert_eq!(HASH.to_string().parse::<Hash>().unwrap(), *HASH);
}
#[bench]
fn to_str(bencher: &mut Bencher) {
let hash = *HASH;
bencher.iter(|| {
let mut out = [0u8; BASE64_OUTPUT_SIZE];
let hash = black_box(hash).write_base64(black_box(&mut out));
black_box(hash);
});
}
#[bench]
fn to_alloc_string(bencher: &mut Bencher) {
let hash = *HASH;
bencher.iter(|| black_box(hash).to_string());
}
#[bench]
fn from_string(bencher: &mut Bencher) {
let string = HASH.to_string();
bencher.iter(|| black_box(&*string).parse::<Hash>().unwrap());
}
}