diff --git a/instant-xml/src/de.rs b/instant-xml/src/de.rs index 84a8f97..4164662 100644 --- a/instant-xml/src/de.rs +++ b/instant-xml/src/de.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, VecDeque}; use xmlparser::{ElementEnd, Token, Tokenizer}; -use crate::impls::decode; +use crate::impls::{decode, CowStrAccumulator}; use crate::{Error, Id}; pub struct Deserializer<'cx, 'xml> { @@ -344,19 +344,21 @@ impl<'xml> Iterator for Context<'xml> { } } -pub fn borrow_cow_str<'xml>( - into: &mut Option>, +pub fn borrow_cow_str<'a, 'xml: 'a>( + into: &mut CowStrAccumulator<'xml, 'a>, _: &'static str, deserializer: &mut Deserializer<'_, 'xml>, ) -> Result<(), Error> { - if into.is_some() { + if into.inner.is_some() { return Err(Error::DuplicateValue); } - if let Some(value) = deserializer.take_str()? { - *into = Some(decode(value)?); + let value = match deserializer.take_str()? { + Some(value) => value, + None => return Ok(()), }; + into.inner = Some(decode(value)?); deserializer.ignore()?; Ok(()) } diff --git a/instant-xml/src/impls.rs b/instant-xml/src/impls.rs index 79c42a7..4cf85da 100644 --- a/instant-xml/src/impls.rs +++ b/instant-xml/src/impls.rs @@ -319,11 +319,7 @@ impl<'xml> FromXml<'xml> for &'xml str { const KIND: Kind = Kind::Scalar; } -impl<'xml, 'a, T: ?Sized> FromXml<'xml> for Cow<'a, T> -where - T: ToOwned, - T::Owned: FromXml<'xml>, -{ +impl<'xml, 'a> FromXml<'xml> for Cow<'a, str> { #[inline] fn matches(id: Id<'_>, field: Option>) -> bool { match field { @@ -334,20 +330,65 @@ where fn deserialize( into: &mut Self::Accumulator, - field: &'static str, + _: &'static str, deserializer: &mut Deserializer<'_, 'xml>, ) -> Result<(), Error> { - if into.is_some() { + if into.inner.is_some() { return Err(Error::DuplicateValue); } - let mut value = >::Accumulator::default(); - T::Owned::deserialize(&mut value, field, deserializer)?; - *into = Some(Cow::Owned(value.try_done(field)?)); + let value = match deserializer.take_str()? { + Some(value) => value, + None => return Ok(()), + }; + + into.inner = Some(decode(value)?.into_owned().into()); Ok(()) } - type Accumulator = Option>; + type Accumulator = CowStrAccumulator<'xml, 'a>; + const KIND: Kind = Kind::Scalar; +} + +#[derive(Default)] +pub struct CowStrAccumulator<'xml, 'a> { + pub(crate) inner: Option>, + marker: PhantomData<&'xml str>, +} + +impl<'xml, 'a> Accumulate> for CowStrAccumulator<'xml, 'a> { + fn try_done(self, field: &'static str) -> Result, Error> { + match self.inner { + Some(inner) => Ok(inner), + None => Err(Error::MissingValue(field)), + } + } +} + +// The `FromXml` implementation for `Cow<'a, [T]>` always builds a `Cow::Owned`: +// it is not possible to deserialize into a `Cow::Borrowed` because there's no +// place to store the originating slice (length only known at run-time). +impl<'xml, 'a, T: FromXml<'xml>> FromXml<'xml> for Cow<'a, [T]> +where + [T]: ToOwned>, +{ + #[inline] + fn matches(id: Id<'_>, field: Option>) -> bool { + T::matches(id, field) + } + + fn deserialize( + into: &mut Self::Accumulator, + field: &'static str, + deserializer: &mut Deserializer<'_, 'xml>, + ) -> Result<(), Error> { + let mut value = T::Accumulator::default(); + T::deserialize(&mut value, field, deserializer)?; + into.push(value.try_done(field)?); + Ok(()) + } + + type Accumulator = Vec; const KIND: Kind = Kind::Scalar; } diff --git a/instant-xml/src/lib.rs b/instant-xml/src/lib.rs index 0747bf3..477de34 100644 --- a/instant-xml/src/lib.rs +++ b/instant-xml/src/lib.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{borrow::Cow, fmt}; use thiserror::Error; @@ -66,6 +66,15 @@ impl Accumulate> for Vec { } } +impl<'a, T> Accumulate> for Vec +where + [T]: ToOwned>, +{ + fn try_done(self, _: &'static str) -> Result, Error> { + Ok(Cow::Owned(self)) + } +} + impl Accumulate> for Option { fn try_done(self, _: &'static str) -> Result, Error> { Ok(self) diff --git a/instant-xml/tests/scalar.rs b/instant-xml/tests/scalar.rs index 3720d2f..6511adb 100644 --- a/instant-xml/tests/scalar.rs +++ b/instant-xml/tests/scalar.rs @@ -25,13 +25,14 @@ struct StructDeserializerScalars<'a, 'b> { nested: NestedLifetimes<'a>, cow: Cow<'a, str>, option: Option<&'a str>, + slice: Cow<'a, [u8]>, } #[test] fn scalars() { assert_eq!( from_str( - "true142stringlifetime alifetime bc1.20trueasd123" + "true142stringlifetime alifetime bc1.20trueasd123123" ), Ok(StructDeserializerScalars{ bool_type: true, @@ -48,13 +49,14 @@ fn scalars() { }, cow: Cow::from("123"), option: None, + slice: Cow::Borrowed(&[1, 2, 3]), }) ); // Option none assert_eq!( from_str( - "true142stringlifetime alifetime bc1.2trueasd123" + "true142stringlifetime alifetime bc1.2trueasd123123" ), Ok(StructDeserializerScalars{ bool_type: true, @@ -71,6 +73,7 @@ fn scalars() { }, cow: Cow::from("123"), option: Some("asd"), + slice: Cow::Borrowed(&[1, 2, 3]), }) ); }