diff --git a/xmlity-quick-xml/src/de.rs b/xmlity-quick-xml/src/de.rs index 7167cdc..7b30432 100644 --- a/xmlity-quick-xml/src/de.rs +++ b/xmlity-quick-xml/src/de.rs @@ -355,6 +355,7 @@ impl<'de> de::AttributeAccess<'de> for AttributeAccess<'_, 'de> { T::deserialize(TextDeserializer { value: self.value, deserializer: self.deserializer, + used_up: false, }) } } @@ -362,6 +363,7 @@ impl<'de> de::AttributeAccess<'de> for AttributeAccess<'_, 'de> { struct TextDeserializer<'a, 'v> { value: Cow<'v, [u8]>, deserializer: &'a Deserializer<'a>, + used_up: bool, } impl<'de> de::XmlText<'de> for TextDeserializer<'_, 'de> { @@ -394,6 +396,61 @@ impl<'de> de::XmlText<'de> for TextDeserializer<'_, 'de> { } } +impl<'de> de::SeqAccess<'de> for TextDeserializer<'_, 'de> { + type Error = Error; + + type SubAccess<'g> + = TextDeserializer<'g, 'de> + where + Self: 'g; + + fn next_element(&mut self) -> Result, Self::Error> + where + T: Deserialize<'de>, + { + if self.used_up { + return Ok(None); + } + + T::deserialize(TextDeserializer { + value: self.value.clone(), + deserializer: self.deserializer, + used_up: false, + }) + .map(|value| { + self.used_up = true; + Some(value) + }) + } + + fn next_element_seq(&mut self) -> Result, Self::Error> + where + T: Deserialize<'de>, + { + if self.used_up { + return Ok(None); + } + + T::deserialize_seq(TextDeserializer { + value: self.value.clone(), + deserializer: self.deserializer, + used_up: false, + }) + .map(|value| { + self.used_up = true; + Some(value) + }) + } + + fn sub_access(&mut self) -> Result, Self::Error> { + Ok(TextDeserializer { + value: self.value.clone(), + deserializer: self.deserializer, + used_up: self.used_up, + }) + } +} + impl<'de> de::Deserializer<'de> for TextDeserializer<'_, 'de> { type Error = Error; @@ -408,7 +465,7 @@ impl<'de> de::Deserializer<'de> for TextDeserializer<'_, 'de> { where V: Visitor<'de>, { - visitor.visit_text(self) + visitor.visit_seq(self) } } diff --git a/xmlity-quick-xml/src/ser.rs b/xmlity-quick-xml/src/ser.rs index 9943904..0128d93 100644 --- a/xmlity-quick-xml/src/ser.rs +++ b/xmlity-quick-xml/src/ser.rs @@ -348,19 +348,49 @@ pub struct AttributeSerializer<'t, W: Write> { /// The text serializer for the `quick-xml` crate. Used when serializing to an attribute value. pub struct TextSerializer { - value: Vec, + value: Option, } -impl ser::Serializer for &mut TextSerializer { +impl ser::SerializeSeq for &mut TextSerializer { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &V) -> Result { + if self.value.is_some() { + return Err(Error::unexpected_serialize(Unexpected::Text)); + } + + let mut text_ser = TextSerializer { value: None }; + value.serialize(&mut text_ser)?; + + if let Some(value) = text_ser.value { + self.value = Some(value); + } else { + return Err(Error::unexpected_serialize(Unexpected::None)); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a> ser::Serializer for &'a mut TextSerializer { type Ok = (); type Error = Error; type SerializeElement = NoopDeSerializer; - type SerializeSeq = NoopDeSerializer; + type SerializeSeq = &'a mut TextSerializer; fn serialize_text>(self, text: S) -> Result { - self.value.extend_from_slice(text.as_ref().as_bytes()); + if self.value.is_some() { + return Err(Error::unexpected_serialize(Unexpected::Text)); + } + + self.value = Some(text.as_ref().to_string()); Ok(()) } @@ -381,7 +411,7 @@ impl ser::Serializer for &mut TextSerializer { } fn serialize_seq(self) -> Result { - Err(Error::unexpected_serialize(Unexpected::Seq)) + Ok(self) } fn serialize_decl>( @@ -447,11 +477,17 @@ impl ser::SerializeAttributeAccess for AttributeSerializer<'_, W> { self.serializer.push_decl_attr(decl); } - let mut text_ser = TextSerializer { value: Vec::new() }; + let mut text_ser = TextSerializer { value: None }; value.serialize(&mut text_ser)?; - self.serializer.push_attr(qname, text_ser.value); + self.serializer.push_attr( + qname, + text_ser + .value + .expect("TextSerializer should have a value") + .into_bytes(), + ); Ok(()) } diff --git a/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs b/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs index 48abec9..b4f04be 100644 --- a/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs +++ b/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs @@ -2,21 +2,39 @@ use crate::define_test; use xmlity::{Deserialize, Serialize}; +fn f_serialize(f: &F, serializer: T) -> Result { + serializer.serialize_text(&f.0) +} + +fn f_deserialize<'de, T: xmlity::Deserializer<'de>>(deserializer: T) -> Result { + let s = String::deserialize(deserializer)?; + Ok(F(s)) +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[xvalue(serialize_with = "f_serialize", deserialize_with = "f_deserialize")] +struct F(pub String); + #[derive(Debug, PartialEq, Serialize, Deserialize)] #[xelement(name = "c")] -pub struct C { +struct C { #[xattribute(name = "b")] - pub c: String, + pub c: F, } define_test!( element_with_single_child, - [(C { c: "A".to_string() }, r#""#)] + [( + C { + c: F("A".to_string()) + }, + r#""# + )] ); #[derive(Debug, PartialEq, Serialize, Deserialize)] #[xelement(name = "d")] -pub struct D { +struct D { #[xattribute(name = "b")] pub b: String, pub c: C, @@ -27,7 +45,9 @@ define_test!( [( D { b: "A".to_string(), - c: C { c: "B".to_string() } + c: C { + c: F("B".to_string()) + } }, r#""# )] @@ -35,7 +55,7 @@ define_test!( #[derive(Debug, PartialEq, Serialize, Deserialize)] #[xelement(name = "e")] -pub struct E { +struct E { pub d: Vec, } @@ -46,14 +66,43 @@ define_test!( d: vec![ D { b: "A".to_string(), - c: C { c: "B".to_string() } + c: C { + c: F("B".to_string()) + } }, D { b: "C".to_string(), - c: C { c: "D".to_string() } + c: C { + c: F("D".to_string()) + } } ] }, r#""# )] ); + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +enum H { + F(F), +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[xelement(name = "g")] +struct G { + #[xattribute(name = "f", optional, default)] + pub f: Option, +} + +define_test!( + element_with_optional_attribute_of_enum, + [ + ( + G { + f: Some(H::F(F("A".to_string()))) + }, + r#""# + ), + (G { f: None }, r#""#) + ] +);