Skip to content

Commit

Permalink
Handle null values in XML as well
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
einarmo committed Feb 28, 2025
1 parent 851c904 commit 4923abd
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 5 deletions.
8 changes: 6 additions & 2 deletions async-opcua-macros/src/encoding/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,16 @@ pub fn generate_xml_encode_impl(strct: EncodingStruct) -> syn::Result<TokenStrea
if field.attr.optional {
body.extend(quote! {
if let Some(item) = &self.#ident {
stream.encode_child(#name, item, ctx)?;
if !item.is_null_xml() {
stream.encode_child(#name, item, ctx)?;
}
}
});
} else {
body.extend(quote! {
stream.encode_child(#name, &self.#ident, ctx)?;
if !self.#ident.is_null_xml() {
stream.encode_child(#name, &self.#ident, ctx)?;
}
});
}
}
Expand Down
4 changes: 4 additions & 0 deletions async-opcua-types/src/expanded_node_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ mod xml {
};
node_id.encode(writer, context)
}

fn is_null_xml(&self) -> bool {
self.is_null()
}
}

impl XmlDecodable for ExpandedNodeId {
Expand Down
4 changes: 4 additions & 0 deletions async-opcua-types/src/node_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions async-opcua-types/src/qualified_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions async-opcua-types/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ mod xml {

Ok(())
}

fn is_null_xml(&self) -> bool {
self.is_null()
}
}

impl XmlDecodable for UAString {
Expand Down
4 changes: 2 additions & 2 deletions async-opcua-types/src/tests/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand All @@ -369,7 +369,7 @@ fn from_xml_variant() {
<EUInformation>
<NamespaceUri>https://my.namespace.uri</NamespaceUri>
<UnitId>1</UnitId>
<DisplayName><Locale>en</Locale><Text>MyUnit</Text></DisplayName>
<DisplayName><Text>MyUnit</Text></DisplayName>
<Description><Locale>en</Locale><Text>MyDesc</Text></Description>
</EUInformation>
</Body>
Expand Down
26 changes: 25 additions & 1 deletion async-opcua-types/src/xml/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -63,6 +67,10 @@ macro_rules! xml_enc_float {
}
Ok(())
}

fn is_null_xml(&self) -> bool {
*self == 0.0
}
}

impl XmlDecodable for $t {
Expand Down Expand Up @@ -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<T> XmlType for Box<T>
Expand Down Expand Up @@ -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<T> XmlType for Vec<T>
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -292,4 +312,8 @@ where
}
Ok(())
}

fn is_null_xml(&self) -> bool {
self.is_none()
}
}
6 changes: 6 additions & 0 deletions async-opcua-types/src/xml/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions async-opcua-xml/src/encoding/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ impl<T: Write> XmlStreamWriter<T> {
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)))?;
Expand Down

0 comments on commit 4923abd

Please sign in to comment.