From cb304580ca799e372c0055f27300eb6c29f798ac Mon Sep 17 00:00:00 2001 From: Lukas Friman Date: Wed, 2 Jul 2025 13:40:21 +0200 Subject: [PATCH 1/4] Add inline attribute declaration tests. --- .../elements/inline_attribute_declarations.rs | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs b/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs index 48abec9..d70dd03 100644 --- a/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs +++ b/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs @@ -2,16 +2,34 @@ 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 { #[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)] @@ -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#""# )] @@ -46,14 +66,38 @@ 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)] +#[xelement(name = "g")] +pub struct G { + #[xattribute(name = "f", optional, default)] + pub f: Option>, +} + +define_test!( + element_with_optional_attribute, + [ + ( + G { + f: Some(Box::new(F("A".to_string()))) + }, + r#""# + ), + (G { f: None }, r#""#) + ] +); From b8bc4e40734fbe6d7061a4bb73673d0c0951a463 Mon Sep 17 00:00:00 2001 From: Lukas Friman Date: Wed, 2 Jul 2025 17:02:51 +0200 Subject: [PATCH 2/4] Identified issue with enum values of attributes for xmlity-quick-xml. --- .../tests/elements/inline_attribute_declarations.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs b/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs index d70dd03..03f3e89 100644 --- a/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs +++ b/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs @@ -82,19 +82,24 @@ define_test!( )] ); +#[derive(Debug, PartialEq, Serialize, Deserialize)] +enum H { + F(F), +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] #[xelement(name = "g")] -pub struct G { +struct G { #[xattribute(name = "f", optional, default)] - pub f: Option>, + pub f: Option, } define_test!( - element_with_optional_attribute, + element_with_optional_attribute_of_enum, [ ( G { - f: Some(Box::new(F("A".to_string()))) + f: Some(H::F(F("A".to_string()))) }, r#""# ), From 86d25c17270731ee161eb09f97db552b4c77aa5d Mon Sep 17 00:00:00 2001 From: Lukas Friman Date: Wed, 2 Jul 2025 17:26:15 +0200 Subject: [PATCH 3/4] Fixed. --- xmlity-quick-xml/src/de.rs | 59 ++++++++++++++++++- xmlity-quick-xml/src/ser.rs | 50 +++++++++++++--- .../elements/inline_attribute_declarations.rs | 6 +- 3 files changed, 104 insertions(+), 11 deletions(-) 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..6ee9751 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<'a> ser::SerializeSeq for &'a 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 03f3e89..b4f04be 100644 --- a/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs +++ b/xmlity-quick-xml/tests/elements/inline_attribute_declarations.rs @@ -17,7 +17,7 @@ struct F(pub String); #[derive(Debug, PartialEq, Serialize, Deserialize)] #[xelement(name = "c")] -pub struct C { +struct C { #[xattribute(name = "b")] pub c: F, } @@ -34,7 +34,7 @@ define_test!( #[derive(Debug, PartialEq, Serialize, Deserialize)] #[xelement(name = "d")] -pub struct D { +struct D { #[xattribute(name = "b")] pub b: String, pub c: C, @@ -55,7 +55,7 @@ define_test!( #[derive(Debug, PartialEq, Serialize, Deserialize)] #[xelement(name = "e")] -pub struct E { +struct E { pub d: Vec, } From f511e4d0ff43785dfe0871e32dc1685603ec00ac Mon Sep 17 00:00:00 2001 From: Lukas Friman Date: Wed, 2 Jul 2025 17:33:35 +0200 Subject: [PATCH 4/4] `cargo clippy` --- xmlity-quick-xml/src/ser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmlity-quick-xml/src/ser.rs b/xmlity-quick-xml/src/ser.rs index 6ee9751..0128d93 100644 --- a/xmlity-quick-xml/src/ser.rs +++ b/xmlity-quick-xml/src/ser.rs @@ -351,7 +351,7 @@ pub struct TextSerializer { value: Option, } -impl<'a> ser::SerializeSeq for &'a mut TextSerializer { +impl ser::SerializeSeq for &mut TextSerializer { type Ok = (); type Error = Error;