diff --git a/parsers/src/forwarding.rs b/parsers/src/forwarding.rs index 5bc1eda976f7cf923b7df86977f2f856d7633130..01635358ba024cc015de6465683b62305f02754d 100644 --- a/parsers/src/forwarding.rs +++ b/parsers/src/forwarding.rs @@ -96,4 +96,36 @@ mod tests { let serialized: Element = forwarded.into(); assert_eq!(serialized, reference); } + + #[test] + fn test_invalid_duplicate_delay() { + let elem: Element = "" + .parse() + .unwrap(); + let error = Forwarded::try_from(elem).unwrap_err(); + let message = match error { + FromElementError::Invalid(Error::Other(string)) => string, + _ => panic!(), + }; + assert_eq!( + message, + "Element forwarded must not have more than one delay child." + ); + } + + #[test] + fn test_invalid_duplicate_message() { + let elem: Element = "" + .parse() + .unwrap(); + let error = Forwarded::try_from(elem).unwrap_err(); + let message = match error { + FromElementError::Invalid(Error::Other(string)) => string, + _ => panic!(), + }; + assert_eq!( + message, + "Element forwarded must not have more than one message child." + ); + } } diff --git a/parsers/src/private.rs b/parsers/src/private.rs index 0c6b24bcffe9a04b9ee81ed772a9e4e3952cbb4a..5459bba828fb05cea1617f9be8a1df3b1f93d6a6 100644 --- a/parsers/src/private.rs +++ b/parsers/src/private.rs @@ -39,3 +39,26 @@ pub struct Query { impl IqSetPayload for Query {} impl IqGetPayload for Query {} impl IqResultPayload for Query {} + +#[cfg(test)] +mod tests { + use super::*; + use minidom::Element; + use xso::error::{Error, FromElementError}; + + #[test] + fn test_invalid_duplicate_child() { + let elem: Element = "" + .parse() + .unwrap(); + let error = Query::try_from(elem).unwrap_err(); + let message = match error { + FromElementError::Invalid(Error::Other(string)) => string, + _ => panic!(), + }; + assert_eq!( + message, + "Query element must not have more than one child in field 'storage'." + ); + } +} diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs index 48470ab327264cb975f4e0adb82276e31f994d52..da70cd87f6e5cf05b96f88ba0a534b0ad05d281a 100644 --- a/parsers/src/util/macro_tests.rs +++ b/parsers/src/util/macro_tests.rs @@ -522,6 +522,19 @@ fn parent_positive() { assert_eq!(v.child.foo, "hello world!"); } +#[test] +fn parent_negative_duplicate_child() { + #[allow(unused_imports)] + use std::{ + option::Option::{None, Some}, + result::Result::{Err, Ok}, + }; + match parse_str::("") { + Err(::xso::error::FromElementError::Invalid(::xso::error::Error::Other(e))) if e.contains("must not have more than one") => (), + other => panic!("unexpected result: {:?}", other), + } +} + #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "parent")] struct OptionalChild { diff --git a/xso-proc/src/error_message.rs b/xso-proc/src/error_message.rs index dbfc7fc17ce55d2a4696ca9c7e8efd25f37b61be..0d0c51bfa2234a944a45fb05c3790aad6497d2de 100644 --- a/xso-proc/src/error_message.rs +++ b/xso-proc/src/error_message.rs @@ -87,3 +87,15 @@ pub(super) fn on_missing_attribute(parent_name: &ParentRef, field: &Member) -> S pub(super) fn on_missing_child(parent_name: &ParentRef, field: &Member) -> String { format!("Missing child {} in {}.", FieldName(&field), parent_name) } + +/// Create a string error message for a duplicate child element. +/// +/// `parent_name` should point at the compound which is being parsed and +/// `field` should be the field to which the child belongs. +pub(super) fn on_duplicate_child(parent_name: &ParentRef, field: &Member) -> String { + format!( + "{} must not have more than one child in {}.", + parent_name, + FieldName(&field) + ) +} diff --git a/xso-proc/src/field.rs b/xso-proc/src/field.rs index 4ba404a1ed958a66197271e3691c7447b20d5ff4..e8cc5caa2d8730c2bd9c8bdcb3274b03f54b01c3 100644 --- a/xso-proc/src/field.rs +++ b/xso-proc/src/field.rs @@ -393,6 +393,8 @@ impl FieldDef { AmountConstraint::FixedSingle(_) => { let missing_msg = error_message::on_missing_child(container_name, &self.member); + let duplicate_msg = + error_message::on_duplicate_child(container_name, &self.member); let on_absent = match default_ { Flag::Absent => quote! { @@ -411,7 +413,16 @@ impl FieldDef { init: quote! { ::std::option::Option::None }, ty: option_ty(self.ty.clone()), }, - matcher, + matcher: quote! { + match #matcher { + ::core::result::Result::Ok(v) => if #field_access.is_some() { + ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(::xso::error::Error::Other(#duplicate_msg))) + } else { + ::core::result::Result::Ok(v) + }, + ::core::result::Result::Err(e) => ::core::result::Result::Err(e), + } + }, builder, collect: quote! { #field_access = ::std::option::Option::Some(#substate_result);