xso: Allow any T: FromXmlText + AsXmlText in EmptyAsNone

Emmanuel Gil Peyrot created

This text codec was previously implemented only for Option<String>, this
extends it to all types implementing those two traits, such as numbers
or JIDs.

Change summary

xso/src/text.rs | 29 +++++++++++++++++++----------
1 file changed, 19 insertions(+), 10 deletions(-)

Detailed changes

xso/src/text.rs 🔗

@@ -123,7 +123,7 @@ convert_via_fromstr_and_display! {
 
 /// Represent a way to encode/decode text data into a Rust type.
 ///
-/// This trait can be used in scenarios where implementing [`FromXmlText`]
+/// This trait can be used in scenarios where implementing [`FromXmlText`]
 /// and/or [`AsXmlText`] on a type is not feasible or sensible, such as the
 /// following:
 ///
@@ -202,23 +202,32 @@ impl TextCodec<String> for Plain {
     }
 }
 
-/// Text codec which returns None instead of the empty string.
+/// Text codec which returns `None` if the input to decode is the empty string, instead of
+/// attempting to decode it.
+///
+/// Particularly useful when parsing `Option<T>` on `#[xml(text)]`, which does not support
+/// `Option<_>` otherwise.
 pub struct EmptyAsNone;
 
-impl TextCodec<Option<String>> for EmptyAsNone {
-    fn decode(&self, s: String) -> Result<Option<String>, Error> {
+impl<T> TextCodec<Option<T>> for EmptyAsNone
+where
+    T: FromXmlText + AsXmlText,
+{
+    fn decode(&self, s: String) -> Result<Option<T>, Error> {
         if s.is_empty() {
             Ok(None)
         } else {
-            Ok(Some(s))
+            Some(T::from_xml_text(s)).transpose()
         }
     }
 
-    fn encode<'x>(&self, value: &'x Option<String>) -> Result<Option<Cow<'x, str>>, Error> {
-        Ok(match value.as_ref() {
-            Some(v) if !v.is_empty() => Some(Cow::Borrowed(v.as_str())),
-            Some(_) | None => None,
-        })
+    fn encode<'x>(&self, value: &'x Option<T>) -> Result<Option<Cow<'x, str>>, Error> {
+        Ok(value
+            .as_ref()
+            .map(AsXmlText::as_xml_text)
+            .transpose()?
+            .map(|v| (!v.is_empty()).then_some(v))
+            .flatten())
     }
 }