xso: implement TextCodec<_> on all T: base64::engine::Engine

The `xso::text::Base64` struct remains as a shorthand (because frankly,
I find the const names in the base64 crate very unwieldly), but you can
now use any of the base64 engines as codec.
This commit is contained in:
Jonas Schäfer 2025-01-25 12:22:05 +01:00
commit 69787569c9
2 changed files with 53 additions and 8 deletions

View file

@ -27,6 +27,8 @@ Version NEXT:
jid::DomainPart, and jid::ResourcePart (!485)
- Support for optional child elements, the presence of which are
translated into a boolean (`#[xml(flag)]`).
- Generic TextCodec implementation for all base64 engines provided by
the base64 crate (if the `base64` feature is enabled).
Version 0.1.2:
2024-07-26 Jonas Schäfer <jonas@zombofant.net>

View file

@ -18,7 +18,7 @@ use alloc::{
use crate::{error::Error, AsXmlText, FromXmlText};
#[cfg(feature = "base64")]
use base64::engine::{general_purpose::STANDARD as StandardBase64Engine, Engine as _};
use base64::engine::general_purpose::STANDARD as StandardBase64Engine;
macro_rules! convert_via_fromstr_and_display {
($($(#[cfg $cfg:tt])?$t:ty,)+) => {
@ -291,38 +291,47 @@ impl TextFilter for StripWhitespace {
}
}
/// Text codec transforming text to binary using standard base64.
/// Text codec transforming text to binary using standard `base64`.
///
/// The `Filter` type argument can be used to employ additional preprocessing
/// of incoming text data. Most interestingly, passing [`StripWhitespace`]
/// will make the implementation ignore any whitespace within the text.
///
/// `Base64` uses the [`base64::engine::general_purpose::STANDARD`] engine.
/// [`TextCodec`] is also automatically implemented for any value which
/// implements [`base64::engine::Engine`], allowing you to choose different
/// alphabets easily.
#[cfg(feature = "base64")]
pub struct Base64;
#[cfg(feature = "base64")]
impl TextCodec<Vec<u8>> for Base64 {
fn decode(&self, s: String) -> Result<Vec<u8>, Error> {
StandardBase64Engine
.decode(s.as_bytes())
base64::engine::Engine::decode(&StandardBase64Engine, s.as_bytes())
.map_err(Error::text_parse_error)
}
fn encode<'x>(&self, value: &'x Vec<u8>) -> Result<Option<Cow<'x, str>>, Error> {
Ok(Some(Cow::Owned(StandardBase64Engine.encode(&value))))
Ok(Some(Cow::Owned(base64::engine::Engine::encode(
&StandardBase64Engine,
&value,
))))
}
}
#[cfg(feature = "base64")]
impl<'x> TextCodec<Cow<'x, [u8]>> for Base64 {
fn decode(&self, s: String) -> Result<Cow<'x, [u8]>, Error> {
StandardBase64Engine
.decode(s.as_bytes())
base64::engine::Engine::decode(&StandardBase64Engine, s.as_bytes())
.map_err(Error::text_parse_error)
.map(Cow::Owned)
}
fn encode<'a>(&self, value: &'a Cow<'x, [u8]>) -> Result<Option<Cow<'a, str>>, Error> {
Ok(Some(Cow::Owned(StandardBase64Engine.encode(&value))))
Ok(Some(Cow::Owned(base64::engine::Engine::encode(
&StandardBase64Engine,
&value,
))))
}
}
@ -347,6 +356,40 @@ where
}
}
#[cfg(feature = "base64")]
impl<T: base64::engine::Engine> TextCodec<Vec<u8>> for T {
fn decode(&self, s: String) -> Result<Vec<u8>, Error> {
base64::engine::Engine::decode(self, s.as_bytes()).map_err(Error::text_parse_error)
}
fn encode<'x>(&self, value: &'x Vec<u8>) -> Result<Option<Cow<'x, str>>, Error> {
Ok(Some(Cow::Owned(base64::engine::Engine::encode(
self, &value,
))))
}
}
#[cfg(feature = "base64")]
impl<'a, T: base64::engine::Engine, U> TextCodec<Option<U>> for T
where
T: TextCodec<U>,
{
fn decode(&self, s: String) -> Result<Option<U>, Error> {
if s.is_empty() {
return Ok(None);
}
Ok(Some(TextCodec::decode(self, s)?))
}
fn encode<'x>(&self, decoded: &'x Option<U>) -> Result<Option<Cow<'x, str>>, Error> {
decoded
.as_ref()
.map(|x| TextCodec::encode(self, x))
.transpose()
.map(Option::flatten)
}
}
/// Text codec transforming text to binary using hexadecimal nibbles.
///
/// The length must be known at compile-time.