use base64ct::{Base64, Encoding};
use std::{
error::Error,
fmt::{self, Display, Formatter, Write},
str,
};
pub trait CanonicalStr {
fn canonical_str(&self) -> &'static str;
}
pub fn encode_base64<T: AsRef<[u8]>>(input: T) -> String {
Base64::encode_string(input.as_ref())
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Base64Error;
impl Display for Base64Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "failed to decode Base64 data")
}
}
impl Error for Base64Error {}
pub fn decode_base64(input: &str) -> Result<Vec<u8>, Base64Error> {
Base64::decode_vec(input).map_err(|_| Base64Error)
}
pub struct Base64Debug<'a>(pub &'a [u8]);
impl fmt::Debug for Base64Debug<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
struct Base64DebugHelper<'a>(&'a str);
impl fmt::Debug for Base64DebugHelper<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
let s = encode_base64(self.0);
if s.is_empty() {
write!(f, "Empty")
} else {
f.debug_tuple("Base64")
.field(&Base64DebugHelper(&s))
.finish()
}
}
}
pub struct BytesDebug<'a>(pub &'a [u8]);
impl fmt::Debug for BytesDebug<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut bytes = self.0;
f.write_char('"')?;
while !bytes.is_empty() {
if let Some(chunk) = next_utf8_chunk(bytes) {
for c in chunk.escape_debug() {
f.write_char(c)?;
}
bytes = &bytes[chunk.len()..];
} else {
write!(f, "\\x{:02x}", bytes[0])?;
bytes = &bytes[1..];
}
}
f.write_char('"')?;
Ok(())
}
}
pub fn next_utf8_chunk(input: &[u8]) -> Option<&str> {
match str::from_utf8(input) {
Ok(s) => {
if !s.is_empty() {
return Some(s);
}
}
Err(e) => {
let i = e.valid_up_to();
if i > 0 {
return Some(str::from_utf8(&input[..i]).unwrap());
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base64_debug_ok() {
assert_eq!(format!("{:?}", Base64Debug(&[])), "Empty");
assert_eq!(format!("{:?}", Base64Debug(&[1, 2, 3])), "Base64(AQID)");
}
#[test]
fn bytes_debug_string() {
let examples = [
("", r#""""#),
("hoi du", r#""hoi du""#),
("x\ny", r#""x\ny""#),
("\t\r", r#""\t\r""#),
("你好", r#""你好""#),
];
for (input, expected) in examples {
assert_eq!(format!("{:?}", input), expected);
assert_eq!(format!("{:?}", BytesDebug(input.as_bytes())), expected);
}
assert_eq!(format!("{:?}", "\"'"), r#""\"'""#);
assert_eq!(format!("{:?}", BytesDebug(b"\"'")), r#""\"\'""#);
}
#[test]
fn bytes_debug_bytes() {
assert_eq!(
format!("{:?}", BytesDebug(b"a\x00\xfe\xc3\xbc")),
r#""a\0\xfeü""#
);
}
}