#[cfg(feature = "base64")]
use core::marker::PhantomData;
use std::borrow::Cow;
use crate::{error::Error, AsXmlText, FromXmlText};
#[cfg(feature = "base64")]
use base64::engine::{general_purpose::STANDARD as StandardBase64Engine, Engine as _};
#[cfg(feature = "jid")]
use jid;
#[cfg(feature = "uuid")]
use uuid;
macro_rules! convert_via_fromstr_and_display {
($($(#[cfg(feature = $feature:literal)])?$t:ty,)+) => {
$(
$(
#[cfg(feature = $feature)]
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
)?
impl FromXmlText for $t {
fn from_xml_text(s: String) -> Result<Self, Error> {
s.parse().map_err(Error::text_parse_error)
}
}
$(
#[cfg(feature = $feature)]
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
)?
impl AsXmlText for $t {
fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
Ok(Cow::Owned(self.to_string()))
}
}
)+
}
}
impl FromXmlText for bool {
fn from_xml_text(s: String) -> Result<Self, Error> {
match s.as_str() {
"1" => "true",
"0" => "false",
other => other,
}
.parse()
.map_err(Error::text_parse_error)
}
}
impl AsXmlText for bool {
fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
match self {
true => Ok(Cow::Borrowed("true")),
false => Ok(Cow::Borrowed("false")),
}
}
}
convert_via_fromstr_and_display! {
u8,
u16,
u32,
u64,
u128,
usize,
i8,
i16,
i32,
i64,
i128,
isize,
f32,
f64,
std::net::IpAddr,
std::net::Ipv4Addr,
std::net::Ipv6Addr,
std::net::SocketAddr,
std::net::SocketAddrV4,
std::net::SocketAddrV6,
std::num::NonZeroU8,
std::num::NonZeroU16,
std::num::NonZeroU32,
std::num::NonZeroU64,
std::num::NonZeroU128,
std::num::NonZeroUsize,
std::num::NonZeroI8,
std::num::NonZeroI16,
std::num::NonZeroI32,
std::num::NonZeroI64,
std::num::NonZeroI128,
std::num::NonZeroIsize,
#[cfg(feature = "uuid")]
uuid::Uuid,
#[cfg(feature = "jid")]
jid::Jid,
#[cfg(feature = "jid")]
jid::FullJid,
#[cfg(feature = "jid")]
jid::BareJid,
}
pub trait TextCodec<T> {
fn decode(s: String) -> Result<T, Error>;
fn encode(value: &T) -> Result<Option<Cow<'_, str>>, Error>;
}
pub struct Plain;
impl TextCodec<String> for Plain {
fn decode(s: String) -> Result<String, Error> {
Ok(s)
}
fn encode(value: &String) -> Result<Option<Cow<'_, str>>, Error> {
Ok(Some(Cow::Borrowed(value.as_str())))
}
}
pub struct EmptyAsNone;
impl TextCodec<Option<String>> for EmptyAsNone {
fn decode(s: String) -> Result<Option<String>, Error> {
if s.is_empty() {
Ok(None)
} else {
Ok(Some(s))
}
}
fn encode(value: &Option<String>) -> Result<Option<Cow<'_, str>>, Error> {
Ok(match value.as_ref() {
Some(v) if !v.is_empty() => Some(Cow::Borrowed(v.as_str())),
Some(_) | None => None,
})
}
}
pub struct EmptyAsError;
impl TextCodec<String> for EmptyAsError {
fn decode(s: String) -> Result<String, Error> {
if s.is_empty() {
Err(Error::Other("Empty text node."))
} else {
Ok(s)
}
}
fn encode(value: &String) -> Result<Option<Cow<'_, str>>, Error> {
if value.is_empty() {
Err(Error::Other("Empty text node."))
} else {
Ok(Some(Cow::Borrowed(value.as_str())))
}
}
}
pub trait TextFilter {
fn preprocess(s: String) -> String;
}
pub struct NoFilter;
impl TextFilter for NoFilter {
fn preprocess(s: String) -> String {
s
}
}
pub struct StripWhitespace;
impl TextFilter for StripWhitespace {
fn preprocess(s: String) -> String {
let s: String = s
.chars()
.filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
.collect();
s
}
}
#[cfg(feature = "base64")]
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
pub struct Base64<Filter: TextFilter = NoFilter>(PhantomData<Filter>);
#[cfg(feature = "base64")]
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
impl<Filter: TextFilter> TextCodec<Vec<u8>> for Base64<Filter> {
fn decode(s: String) -> Result<Vec<u8>, Error> {
let value = Filter::preprocess(s);
StandardBase64Engine
.decode(value.as_bytes())
.map_err(Error::text_parse_error)
}
fn encode(value: &Vec<u8>) -> Result<Option<Cow<'_, str>>, Error> {
Ok(Some(Cow::Owned(StandardBase64Engine.encode(&value))))
}
}
#[cfg(feature = "base64")]
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
impl<Filter: TextFilter> TextCodec<Option<Vec<u8>>> for Base64<Filter> {
fn decode(s: String) -> Result<Option<Vec<u8>>, Error> {
if s.is_empty() {
return Ok(None);
}
Ok(Some(Self::decode(s)?))
}
fn encode(decoded: &Option<Vec<u8>>) -> Result<Option<Cow<'_, str>>, Error> {
decoded
.as_ref()
.map(Self::encode)
.transpose()
.map(Option::flatten)
}
}
pub struct FixedHex<const N: usize>;
impl<const N: usize> TextCodec<[u8; N]> for FixedHex<N> {
fn decode(s: String) -> Result<[u8; N], Error> {
if s.len() != 2 * N {
return Err(Error::Other("Invalid length"));
}
let mut bytes = [0u8; N];
for i in 0..N {
bytes[i] =
u8::from_str_radix(&s[2 * i..2 * i + 2], 16).map_err(Error::text_parse_error)?;
}
Ok(bytes)
}
fn encode(value: &[u8; N]) -> Result<Option<Cow<'_, str>>, Error> {
let mut bytes = String::with_capacity(N * 2);
for byte in value {
bytes.extend(format!("{:02x}", byte).chars());
}
Ok(Some(Cow::Owned(bytes)))
}
}
impl<T, const N: usize> TextCodec<Option<T>> for FixedHex<N>
where
FixedHex<N>: TextCodec<T>,
{
fn decode(s: String) -> Result<Option<T>, Error> {
if s.is_empty() {
return Ok(None);
}
Ok(Some(Self::decode(s)?))
}
fn encode(decoded: &Option<T>) -> Result<Option<Cow<'_, str>>, Error> {
decoded
.as_ref()
.map(Self::encode)
.transpose()
.map(Option::flatten)
}
}
pub struct ColonSeparatedHex;
impl TextCodec<Vec<u8>> for ColonSeparatedHex {
fn decode(s: String) -> Result<Vec<u8>, Error> {
assert_eq!((s.len() + 1) % 3, 0);
let mut bytes = Vec::with_capacity((s.len() + 1) / 3);
for i in 0..(1 + s.len()) / 3 {
let byte =
u8::from_str_radix(&s[3 * i..3 * i + 2], 16).map_err(Error::text_parse_error)?;
if 3 * i + 2 < s.len() {
assert_eq!(&s[3 * i + 2..3 * i + 3], ":");
}
bytes.push(byte);
}
Ok(bytes)
}
fn encode(decoded: &Vec<u8>) -> Result<Option<Cow<'_, str>>, Error> {
let mut bytes = Vec::with_capacity(decoded.len());
for byte in decoded {
bytes.push(format!("{:02X}", byte));
}
Ok(Some(Cow::Owned(bytes.join(":"))))
}
}