1#![cfg_attr(
42    not(all(feature = "std", feature = "macros")),
43    doc = "Because the std or macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
44)]
45#![cfg_attr(all(feature = "std", feature = "macros"), doc = "\n```\n")]
46use core::marker::PhantomData;
87
88use alloc::{
89    borrow::{Cow, ToOwned},
90    boxed::Box,
91    format,
92    string::{String, ToString},
93    vec::Vec,
94};
95
96use crate::{error::Error, AsOptionalXmlText, AsXmlText, FromXmlText};
97
98#[cfg(feature = "base64")]
99use base64::engine::general_purpose::STANDARD as StandardBase64Engine;
100
101#[cfg_attr(
116    not(feature = "macros"),
117    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
118)]
119#[cfg_attr(feature = "macros", doc = "\n```\n")]
120#[macro_export]
142macro_rules! convert_via_fromstr_and_display {
143    ($($(#[cfg $cfg:tt])?$t:ty),+ $(,)?) => {
144        $(
145            $(
146                #[cfg $cfg]
147            )?
148            impl $crate::FromXmlText for $t {
149                #[doc = concat!("Parse [`", stringify!($t), "`] from XML text via [`FromStr`][`core::str::FromStr`].")]
150                fn from_xml_text(s: String) -> Result<Self, $crate::error::Error> {
151                    s.parse().map_err($crate::error::Error::text_parse_error)
152                }
153            }
154
155            $(
156                #[cfg $cfg]
157            )?
158            impl $crate::AsXmlText for $t {
159                #[doc = concat!("Convert [`", stringify!($t), "`] to XML text via [`Display`][`core::fmt::Display`].\n\nThis implementation never fails.")]
160                fn as_xml_text(&self) -> Result<$crate::exports::alloc::borrow::Cow<'_, str>, $crate::error::Error> {
161                    Ok($crate::exports::alloc::borrow::Cow::Owned(self.to_string()))
162                }
163            }
164        )+
165    }
166}
167
168impl FromXmlText for bool {
170    fn from_xml_text(s: String) -> Result<Self, Error> {
176        match s.as_str() {
177            "1" => "true",
178            "0" => "false",
179            other => other,
180        }
181        .parse()
182        .map_err(Error::text_parse_error)
183    }
184}
185
186impl AsXmlText for bool {
188    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
193        match self {
194            true => Ok(Cow::Borrowed("true")),
195            false => Ok(Cow::Borrowed("false")),
196        }
197    }
198}
199
200convert_via_fromstr_and_display! {
201    u8,
202    u16,
203    u32,
204    u64,
205    u128,
206    usize,
207    i8,
208    i16,
209    i32,
210    i64,
211    i128,
212    isize,
213    f32,
214    f64,
215    char,
216    core::net::IpAddr,
217    core::net::Ipv4Addr,
218    core::net::Ipv6Addr,
219    core::net::SocketAddr,
220    core::net::SocketAddrV4,
221    core::net::SocketAddrV6,
222    core::num::NonZeroU8,
223    core::num::NonZeroU16,
224    core::num::NonZeroU32,
225    core::num::NonZeroU64,
226    core::num::NonZeroU128,
227    core::num::NonZeroUsize,
228    core::num::NonZeroI8,
229    core::num::NonZeroI16,
230    core::num::NonZeroI32,
231    core::num::NonZeroI64,
232    core::num::NonZeroI128,
233    core::num::NonZeroIsize,
234
235    #[cfg(feature = "uuid")]
236    uuid::Uuid,
237
238    #[cfg(feature = "jid")]
239    jid::Jid,
240    #[cfg(feature = "jid")]
241    jid::FullJid,
242    #[cfg(feature = "jid")]
243    jid::BareJid,
244    #[cfg(feature = "jid")]
245    jid::NodePart,
246    #[cfg(feature = "jid")]
247    jid::DomainPart,
248    #[cfg(feature = "jid")]
249    jid::ResourcePart,
250
251    #[cfg(feature = "serde_json")]
252    serde_json::Value,
253}
254
255impl FromXmlText for String {
256    fn from_xml_text(data: String) -> Result<Self, Error> {
258        Ok(data)
259    }
260}
261
262impl<T: FromXmlText, B: ToOwned<Owned = T>> FromXmlText for Cow<'_, B> {
263    fn from_xml_text(data: String) -> Result<Self, Error> {
265        Ok(Cow::Owned(T::from_xml_text(data)?))
266    }
267}
268
269impl<T: FromXmlText> FromXmlText for Option<T> {
270    fn from_xml_text(data: String) -> Result<Self, Error> {
272        Ok(Some(T::from_xml_text(data)?))
273    }
274}
275
276impl<T: FromXmlText> FromXmlText for Box<T> {
277    fn from_xml_text(data: String) -> Result<Self, Error> {
279        Ok(Box::new(T::from_xml_text(data)?))
280    }
281}
282
283impl AsXmlText for String {
284    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
286        Ok(Cow::Borrowed(self))
287    }
288}
289
290impl AsXmlText for str {
291    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
293        Ok(Cow::Borrowed(self))
294    }
295}
296
297impl AsXmlText for &str {
298    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
300        Ok(Cow::Borrowed(self))
301    }
302}
303
304impl<T: AsXmlText> AsXmlText for Box<T> {
305    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
307        T::as_xml_text(self)
308    }
309}
310
311impl<B: AsXmlText + ToOwned> AsXmlText for Cow<'_, B> {
312    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
314        B::as_xml_text(self)
315    }
316}
317
318impl<T: AsXmlText> AsXmlText for &T {
319    fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
321        T::as_xml_text(*self)
322    }
323}
324
325impl<T: AsXmlText> AsOptionalXmlText for T {
326    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, Error> {
327        <Self as AsXmlText>::as_optional_xml_text(self)
328    }
329}
330
331impl<T: AsXmlText> AsOptionalXmlText for Option<T> {
332    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, Error> {
333        self.as_ref()
334            .map(T::as_optional_xml_text)
335            .transpose()
336            .map(Option::flatten)
337    }
338}
339
340#[diagnostic::on_unimplemented(
355    message = "`{Self}` cannot be used as XML text codec for values of type `{T}`."
356)]
357pub trait TextCodec<T> {
358    fn decode(&self, s: String) -> Result<T, Error>;
360
361    fn encode<'x>(&self, value: &'x T) -> Result<Option<Cow<'x, str>>, Error>;
365
366    fn filtered<F: TextFilter>(self, filter: F) -> Filtered<F, Self, T>
377    where
378        Self: Sized,
381    {
382        Filtered {
383            filter,
384            codec: self,
385            bound: PhantomData,
386        }
387    }
388}
389
390pub struct Filtered<F, C, T> {
395    filter: F,
396    codec: C,
397    bound: PhantomData<T>,
398}
399
400impl<T, F: TextFilter, C: TextCodec<T>> TextCodec<T> for Filtered<F, C, T> {
401    fn decode(&self, s: String) -> Result<T, Error> {
402        let s = self.filter.preprocess(s);
403        self.codec.decode(s)
404    }
405
406    fn encode<'x>(&self, value: &'x T) -> Result<Option<Cow<'x, str>>, Error> {
407        self.codec.encode(value)
408    }
409}
410
411pub struct Plain;
413
414impl TextCodec<String> for Plain {
415    fn decode(&self, s: String) -> Result<String, Error> {
416        Ok(s)
417    }
418
419    fn encode<'x>(&self, value: &'x String) -> Result<Option<Cow<'x, str>>, Error> {
420        Ok(Some(Cow::Borrowed(value.as_str())))
421    }
422}
423
424pub struct EmptyAsNone;
430
431impl<T> TextCodec<Option<T>> for EmptyAsNone
432where
433    T: FromXmlText + AsXmlText,
434{
435    fn decode(&self, s: String) -> Result<Option<T>, Error> {
436        if s.is_empty() {
437            Ok(None)
438        } else {
439            Some(T::from_xml_text(s)).transpose()
440        }
441    }
442
443    fn encode<'x>(&self, value: &'x Option<T>) -> Result<Option<Cow<'x, str>>, Error> {
444        Ok(value
445            .as_ref()
446            .map(AsXmlText::as_xml_text)
447            .transpose()?
448            .and_then(|v| (!v.is_empty()).then_some(v)))
449    }
450}
451
452pub struct EmptyAsError;
454
455impl TextCodec<String> for EmptyAsError {
456    fn decode(&self, s: String) -> Result<String, Error> {
457        if s.is_empty() {
458            Err(Error::Other("Empty text node."))
459        } else {
460            Ok(s)
461        }
462    }
463
464    fn encode<'x>(&self, value: &'x String) -> Result<Option<Cow<'x, str>>, Error> {
465        if value.is_empty() {
466            Err(Error::Other("Empty text node."))
467        } else {
468            Ok(Some(Cow::Borrowed(value.as_str())))
469        }
470    }
471}
472
473pub trait TextFilter {
477    fn preprocess(&self, s: String) -> String;
479}
480
481pub struct NoFilter;
483
484impl TextFilter for NoFilter {
485    fn preprocess(&self, s: String) -> String {
486        s
487    }
488}
489
490pub struct StripWhitespace;
492
493impl TextFilter for StripWhitespace {
494    fn preprocess(&self, s: String) -> String {
495        let s: String = s
496            .chars()
497            .filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
498            .collect();
499        s
500    }
501}
502
503#[cfg(feature = "base64")]
510pub struct Base64;
511
512#[cfg(feature = "base64")]
513impl TextCodec<Vec<u8>> for Base64 {
514    fn decode(&self, s: String) -> Result<Vec<u8>, Error> {
515        base64::engine::Engine::decode(&StandardBase64Engine, s.as_bytes())
516            .map_err(Error::text_parse_error)
517    }
518
519    fn encode<'x>(&self, value: &'x Vec<u8>) -> Result<Option<Cow<'x, str>>, Error> {
520        Ok(Some(Cow::Owned(base64::engine::Engine::encode(
521            &StandardBase64Engine,
522            value,
523        ))))
524    }
525}
526
527#[cfg(feature = "base64")]
528impl<'x> TextCodec<Cow<'x, [u8]>> for Base64 {
529    fn decode(&self, s: String) -> Result<Cow<'x, [u8]>, Error> {
530        base64::engine::Engine::decode(&StandardBase64Engine, s.as_bytes())
531            .map_err(Error::text_parse_error)
532            .map(Cow::Owned)
533    }
534
535    fn encode<'a>(&self, value: &'a Cow<'x, [u8]>) -> Result<Option<Cow<'a, str>>, Error> {
536        Ok(Some(Cow::Owned(base64::engine::Engine::encode(
537            &StandardBase64Engine,
538            value,
539        ))))
540    }
541}
542
543#[cfg(feature = "base64")]
544impl<T> TextCodec<Option<T>> for Base64
545where
546    Base64: TextCodec<T>,
547{
548    fn decode(&self, s: String) -> Result<Option<T>, Error> {
549        if s.is_empty() {
550            return Ok(None);
551        }
552        Ok(Some(self.decode(s)?))
553    }
554
555    fn encode<'x>(&self, decoded: &'x Option<T>) -> Result<Option<Cow<'x, str>>, Error> {
556        decoded
557            .as_ref()
558            .map(|x| self.encode(x))
559            .transpose()
560            .map(Option::flatten)
561    }
562}
563
564#[cfg(feature = "base64")]
565impl<T: base64::engine::Engine> TextCodec<Vec<u8>> for T {
566    fn decode(&self, s: String) -> Result<Vec<u8>, Error> {
567        base64::engine::Engine::decode(self, s.as_bytes()).map_err(Error::text_parse_error)
568    }
569
570    fn encode<'x>(&self, value: &'x Vec<u8>) -> Result<Option<Cow<'x, str>>, Error> {
571        Ok(Some(Cow::Owned(base64::engine::Engine::encode(
572            self, value,
573        ))))
574    }
575}
576
577#[cfg(feature = "base64")]
578impl<T: base64::engine::Engine, U> TextCodec<Option<U>> for T
579where
580    T: TextCodec<U>,
581{
582    fn decode(&self, s: String) -> Result<Option<U>, Error> {
583        if s.is_empty() {
584            return Ok(None);
585        }
586        Ok(Some(TextCodec::decode(self, s)?))
587    }
588
589    fn encode<'x>(&self, decoded: &'x Option<U>) -> Result<Option<Cow<'x, str>>, Error> {
590        decoded
591            .as_ref()
592            .map(|x| TextCodec::encode(self, x))
593            .transpose()
594            .map(Option::flatten)
595    }
596}
597
598pub struct FixedHex<const N: usize>;
602
603impl<const N: usize> TextCodec<[u8; N]> for FixedHex<N> {
604    fn decode(&self, s: String) -> Result<[u8; N], Error> {
605        if s.len() != 2 * N {
606            return Err(Error::Other("Invalid length"));
607        }
608
609        let mut bytes = [0u8; N];
610        for i in 0..N {
611            bytes[i] =
612                u8::from_str_radix(&s[2 * i..2 * i + 2], 16).map_err(Error::text_parse_error)?;
613        }
614
615        Ok(bytes)
616    }
617
618    fn encode<'x>(&self, value: &'x [u8; N]) -> Result<Option<Cow<'x, str>>, Error> {
619        let mut bytes = String::with_capacity(N * 2);
620        for byte in value {
621            bytes.extend(format!("{:02x}", byte).chars());
622        }
623        Ok(Some(Cow::Owned(bytes)))
624    }
625}
626
627impl<T, const N: usize> TextCodec<Option<T>> for FixedHex<N>
628where
629    FixedHex<N>: TextCodec<T>,
630{
631    fn decode(&self, s: String) -> Result<Option<T>, Error> {
632        if s.is_empty() {
633            return Ok(None);
634        }
635        Ok(Some(self.decode(s)?))
636    }
637
638    fn encode<'x>(&self, decoded: &'x Option<T>) -> Result<Option<Cow<'x, str>>, Error> {
639        decoded
640            .as_ref()
641            .map(|x| self.encode(x))
642            .transpose()
643            .map(Option::flatten)
644    }
645}
646
647pub struct ColonSeparatedHex;
649
650impl TextCodec<Vec<u8>> for ColonSeparatedHex {
651    fn decode(&self, s: String) -> Result<Vec<u8>, Error> {
652        assert_eq!((s.len() + 1) % 3, 0);
653        let mut bytes = Vec::with_capacity((s.len() + 1) / 3);
654        for i in 0..(1 + s.len()) / 3 {
655            let byte =
656                u8::from_str_radix(&s[3 * i..3 * i + 2], 16).map_err(Error::text_parse_error)?;
657            if 3 * i + 2 < s.len() {
658                assert_eq!(&s[3 * i + 2..3 * i + 3], ":");
659            }
660            bytes.push(byte);
661        }
662        Ok(bytes)
663    }
664
665    fn encode<'x>(&self, decoded: &'x Vec<u8>) -> Result<Option<Cow<'x, str>>, Error> {
666        let mut bytes = Vec::with_capacity(decoded.len());
668        for byte in decoded {
669            bytes.push(format!("{:02X}", byte));
670        }
671        Ok(Some(Cow::Owned(bytes.join(":"))))
672    }
673}