#[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 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)
}
}