From 4923abde31a6ea731d4d985369cea408718140f3 Mon Sep 17 00:00:00 2001 From: Einar Omang Date: Thu, 27 Feb 2025 12:19:40 +0100 Subject: [PATCH] Handle null values in XML as well This is the same as what we do for JSON, we make it possible to leave out values that are null, which is important for certain special values like UAString, where an empty value is different from a null value. I considered making this a separate trait, but it would be annoying to have to derive a trait that does nothing most of the time, and without specialization there's no better way. We also can't put it on BinaryEncodable since that trait isn't implemented for `Option`, and can't really be at the moment. It's now effectively duplicated between JsonEncodable and XmlEncodable, which is fine. --- async-opcua-macros/src/encoding/xml.rs | 8 +++++-- async-opcua-types/src/expanded_node_id.rs | 4 ++++ async-opcua-types/src/node_id.rs | 4 ++++ async-opcua-types/src/qualified_name.rs | 4 ++++ async-opcua-types/src/string.rs | 4 ++++ async-opcua-types/src/tests/xml.rs | 4 ++-- async-opcua-types/src/xml/builtins.rs | 26 ++++++++++++++++++++++- async-opcua-types/src/xml/encoding.rs | 6 ++++++ async-opcua-xml/src/encoding/writer.rs | 7 ++++++ 9 files changed, 62 insertions(+), 5 deletions(-) diff --git a/async-opcua-macros/src/encoding/xml.rs b/async-opcua-macros/src/encoding/xml.rs index 2a52be04..9b680feb 100644 --- a/async-opcua-macros/src/encoding/xml.rs +++ b/async-opcua-macros/src/encoding/xml.rs @@ -102,12 +102,16 @@ pub fn generate_xml_encode_impl(strct: EncodingStruct) -> syn::Result bool { + self.is_null() + } } impl XmlDecodable for ExpandedNodeId { diff --git a/async-opcua-types/src/node_id.rs b/async-opcua-types/src/node_id.rs index eda0a659..d9fd32f3 100644 --- a/async-opcua-types/src/node_id.rs +++ b/async-opcua-types/src/node_id.rs @@ -357,6 +357,10 @@ mod xml { let val = ctx.resolve_alias_inverse(&self_str); writer.encode_child("Identifier", val, ctx) } + + fn is_null_xml(&self) -> bool { + self.is_null() + } } impl XmlDecodable for NodeId { diff --git a/async-opcua-types/src/qualified_name.rs b/async-opcua-types/src/qualified_name.rs index c88048a3..6273758a 100644 --- a/async-opcua-types/src/qualified_name.rs +++ b/async-opcua-types/src/qualified_name.rs @@ -67,6 +67,10 @@ mod xml { writer.encode_child("Name", &self.name, context)?; Ok(()) } + + fn is_null_xml(&self) -> bool { + self.is_null() + } } impl XmlDecodable for QualifiedName { diff --git a/async-opcua-types/src/string.rs b/async-opcua-types/src/string.rs index 3ae97784..82014917 100644 --- a/async-opcua-types/src/string.rs +++ b/async-opcua-types/src/string.rs @@ -159,6 +159,10 @@ mod xml { Ok(()) } + + fn is_null_xml(&self) -> bool { + self.is_null() + } } impl XmlDecodable for UAString { diff --git a/async-opcua-types/src/tests/xml.rs b/async-opcua-types/src/tests/xml.rs index 134ee529..b554a5f1 100644 --- a/async-opcua-types/src/tests/xml.rs +++ b/async-opcua-types/src/tests/xml.rs @@ -359,7 +359,7 @@ fn from_xml_variant() { &Variant::from(EUInformation { namespace_uri: "https://my.namespace.uri".into(), unit_id: 1, - display_name: LocalizedText::new("en", "MyUnit"), + display_name: LocalizedText::from("MyUnit"), description: LocalizedText::new("en", "MyDesc"), }), r#" @@ -369,7 +369,7 @@ fn from_xml_variant() { https://my.namespace.uri 1 - enMyUnit + MyUnit enMyDesc diff --git a/async-opcua-types/src/xml/builtins.rs b/async-opcua-types/src/xml/builtins.rs index 3cb93c76..7bd4cab9 100644 --- a/async-opcua-types/src/xml/builtins.rs +++ b/async-opcua-types/src/xml/builtins.rs @@ -21,6 +21,10 @@ macro_rules! xml_enc_number { writer.write_text(&self.to_string())?; Ok(()) } + + fn is_null_xml(&self) -> bool { + *self == 0 + } } impl XmlDecodable for $t { @@ -63,6 +67,10 @@ macro_rules! xml_enc_float { } Ok(()) } + + fn is_null_xml(&self) -> bool { + *self == 0.0 + } } impl XmlDecodable for $t { @@ -165,6 +173,10 @@ impl XmlEncodable for bool { writer.write_text(if *self { "true" } else { "false" })?; Ok(()) } + + fn is_null_xml(&self) -> bool { + !self + } } impl XmlType for Box @@ -200,6 +212,10 @@ where ) -> Result<(), Error> { self.as_ref().encode(writer, context) } + + fn is_null_xml(&self) -> bool { + self.as_ref().is_null_xml() + } } impl XmlType for Vec @@ -248,7 +264,11 @@ where context: &Context<'_>, ) -> super::EncodingResult<()> { for item in self { - writer.encode_child(item.tag(), item, context)?; + if item.is_null_xml() { + writer.write_empty(item.tag())?; + } else { + writer.encode_child(item.tag(), item, context)?; + } } Ok(()) } @@ -292,4 +312,8 @@ where } Ok(()) } + + fn is_null_xml(&self) -> bool { + self.is_none() + } } diff --git a/async-opcua-types/src/xml/encoding.rs b/async-opcua-types/src/xml/encoding.rs index 11417147..189fe0b3 100644 --- a/async-opcua-types/src/xml/encoding.rs +++ b/async-opcua-types/src/xml/encoding.rs @@ -55,6 +55,12 @@ pub trait XmlEncodable: XmlType { writer: &mut XmlStreamWriter<&mut dyn Write>, context: &Context<'_>, ) -> EncodingResult<()>; + + /// This method should return `true` if the value is default + /// and should not be serialized. + fn is_null_xml(&self) -> bool { + false + } } /// Extensions for XmlStreamWriter. diff --git a/async-opcua-xml/src/encoding/writer.rs b/async-opcua-xml/src/encoding/writer.rs index 30a11807..ebca6a9d 100644 --- a/async-opcua-xml/src/encoding/writer.rs +++ b/async-opcua-xml/src/encoding/writer.rs @@ -49,6 +49,13 @@ impl XmlStreamWriter { Ok(()) } + /// Write an empty tag to the stream. + pub fn write_empty(&mut self, tag: &str) -> Result<(), XmlWriteError> { + self.writer + .write_event(Event::Empty(BytesStart::new(tag)))?; + Ok(()) + } + /// Write node contents to the stream. pub fn write_text(&mut self, text: &str) -> Result<(), XmlWriteError> { self.writer.write_event(Event::Text(BytesText::new(text)))?;