use std::borrow::Cow;
use std::fmt::{self, Write};
use std::marker::PhantomData;
use std::ops::Deref;
use base64::engine::{general_purpose::STANDARD as StandardBase64Engine, Engine as Base64Engine};
use crate::{error::Error, FromXmlText, IntoXmlText};
pub trait TextCodec<T> {
fn decode(s: &str) -> Result<T, Error>;
fn encode(value: T) -> Option<String>;
}
pub struct Plain;
impl TextCodec<String> for Plain {
fn decode(s: &str) -> Result<String, Error> {
Ok(s.to_string())
}
fn encode(value: String) -> Option<String> {
Some(value)
}
}
pub struct EmptyAsNone;
impl TextCodec<Option<String>> for EmptyAsNone {
fn decode(s: &str) -> Result<Option<String>, Error> {
if s.len() == 0 {
Ok(None)
} else {
Ok(Some(s.to_owned()))
}
}
fn encode(value: Option<String>) -> Option<String> {
match value {
Some(v) if v.len() > 0 => Some(v),
Some(_) | None => None,
}
}
}
pub struct Trimmed<Inner = Plain>(PhantomData<Inner>);
impl<Inner: TextCodec<String>> TextCodec<String> for Trimmed<Inner> {
fn decode(s: &str) -> Result<String, Error> {
Inner::decode(s.trim())
}
fn encode(decoded: String) -> Option<String> {
Inner::encode(decoded)
}
}
pub trait TextFilter {
fn preprocess(s: &str) -> Cow<'_, str>;
}
pub struct NoFilter;
impl TextFilter for NoFilter {
fn preprocess(s: &str) -> Cow<'_, str> {
Cow::Borrowed(s)
}
}
pub struct StripWhitespace;
impl TextFilter for StripWhitespace {
fn preprocess(s: &str) -> Cow<'_, str> {
let s: String = s
.chars()
.filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
.collect();
Cow::Owned(s)
}
}
pub struct Base64<Filter: TextFilter = NoFilter>(PhantomData<Filter>);
impl<Filter: TextFilter> TextCodec<Vec<u8>> for Base64<Filter> {
fn decode(s: &str) -> Result<Vec<u8>, Error> {
let value = Filter::preprocess(s);
Ok(StandardBase64Engine.decode(value.as_ref())?)
}
fn encode(value: Vec<u8>) -> Option<String> {
Some(StandardBase64Engine.encode(&value))
}
}
impl<Filter: TextFilter> TextCodec<Option<Vec<u8>>> for Base64<Filter> {
fn decode(s: &str) -> Result<Option<Vec<u8>>, Error> {
if s.len() == 0 {
return Ok(None);
}
Ok(Some(Self::decode(s)?))
}
fn encode(decoded: Option<Vec<u8>>) -> Option<String> {
decoded.and_then(Self::encode)
}
}
pub struct ColonSeparatedHex;
impl TextCodec<Vec<u8>> for ColonSeparatedHex {
fn decode(s: &str) -> Result<Vec<u8>, Error> {
let mut bytes = vec![];
for i in 0..(1 + s.len()) / 3 {
let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?;
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>) -> Option<String> {
let mut bytes = vec![];
for byte in decoded {
bytes.push(format!("{:02X}", byte));
}
Some(bytes.join(":"))
}
}
pub struct Hex;
impl<const N: usize> TextCodec<[u8; N]> for Hex {
fn decode(s: &str) -> Result<[u8; N], Error> {
if s.len() != 2 * N {
return Err(Error::ParseError("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)?;
}
Ok(bytes)
}
fn encode(decoded: [u8; N]) -> Option<String> {
let mut bytes = String::with_capacity(N * 2);
for byte in decoded {
write!(&mut bytes, "{:02x}", byte).unwrap();
}
Some(bytes)
}
}
impl<const N: usize> TextCodec<Option<[u8; N]>> for Hex {
fn decode(s: &str) -> Result<Option<[u8; N]>, Error> {
if s.len() == 0 {
return Ok(None);
}
Ok(Some(Self::decode(s)?))
}
fn encode(decoded: Option<[u8; N]>) -> Option<String> {
decoded.and_then(Self::encode)
}
}
impl TextCodec<Vec<u8>> for Hex {
fn decode(s: &str) -> Result<Vec<u8>, Error> {
if s.len() % 2 != 0 {
return Err(Error::ParseError("Invalid length"));
}
let n = s.len() / 2;
let mut bytes = Vec::with_capacity(n);
bytes.resize(n, 0u8);
for i in 0..n {
bytes[i] = u8::from_str_radix(&s[2 * i..2 * i + 2], 16)?;
}
Ok(bytes)
}
fn encode(decoded: Vec<u8>) -> Option<String> {
let mut bytes = String::with_capacity(decoded.len() * 2);
for byte in decoded {
write!(&mut bytes, "{:02x}", byte).unwrap();
}
Some(bytes)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NonEmptyString(String);
impl NonEmptyString {
pub fn new(s: String) -> Option<Self> {
if s.len() == 0 {
return None;
}
Some(Self(s))
}
pub fn into_inner(self) -> String {
self.0
}
}
impl From<NonEmptyString> for String {
fn from(other: NonEmptyString) -> Self {
other.into_inner()
}
}
impl PartialEq<str> for NonEmptyString {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for NonEmptyString {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl Deref for NonEmptyString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for NonEmptyString {
fn fmt<'f>(&self, f: &'f mut fmt::Formatter) -> fmt::Result {
<String as fmt::Display>::fmt(&self.0, f)
}
}
impl FromXmlText for NonEmptyString {
fn from_xml_text(s: &str) -> Result<Self, Error> {
if s.len() == 0 {
return Err(Error::ParseError("string must not be empty"));
}
Ok(Self(s.to_string()))
}
}
impl IntoXmlText for NonEmptyString {
fn into_xml_text(self) -> String {
self.0
}
}
impl TextCodec<String> for NonEmptyString {
fn decode(s: &str) -> Result<String, Error> {
if s.len() == 0 {
return Err(Error::ParseError("string must not be empty"));
}
Ok(s.to_owned())
}
fn encode(value: String) -> Option<String> {
Some(value)
}
}