Skip to main content

mx20022_model/common/
choice.rs

1//! Wrapper type enabling `xs:choice` fields to work with `quick-xml` + serde.
2//!
3//! `quick-xml` requires that enum types used as struct field values are wrapped
4//! in a struct with a `#[serde(rename = "$value")]` field.  Without the wrapper,
5//! serialization of newtype enum variants fails with
6//! `"cannot serialize enum newtype variant"`, and deserialization fails with
7//! `"UnexpectedStart"`.
8//!
9//! ## XML mapping
10//!
11//! For an ISO 20022 struct like:
12//!
13//! ```xml
14//! <Fr>
15//!   <FIId>...</FIId>   <!-- xs:choice discriminator + content -->
16//! </Fr>
17//! ```
18//!
19//! The Rust model is:
20//!
21//! ```rust
22//! use mx20022_model::common::ChoiceWrapper;
23//!
24//! #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
25//! pub enum Party51Choice {
26//!     #[serde(rename = "FIId")]
27//!     FIId(String),           // simplified
28//!     #[serde(rename = "OrgId")]
29//!     OrgId(String),          // simplified
30//! }
31//!
32//! #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
33//! pub struct BusinessApplicationHeader {
34//!     #[serde(rename = "Fr")]
35//!     pub fr: ChoiceWrapper<Party51Choice>,
36//! }
37//! ```
38
39/// A transparent wrapper that places an `xs:choice` enum as the `$value`
40/// (element content) of its parent XML element.
41///
42/// `quick-xml` only supports serializing and deserializing enum newtype
43/// variants when they appear as element content via a `$value`-renamed field.
44/// This struct is that one-field wrapper.
45///
46/// # Access
47///
48/// Use the public `.inner` field to access the wrapped enum, or use
49/// `ChoiceWrapper::new(value)` / `From<T>` to construct one.
50///
51/// ```
52/// use mx20022_model::common::ChoiceWrapper;
53///
54/// #[derive(Debug, PartialEq)]
55/// enum MyChoice { A(String), B(u32) }
56///
57/// let w = ChoiceWrapper::new(MyChoice::A("hello".to_owned()));
58/// // Access inner value
59/// match &w.inner {
60///     MyChoice::A(s) => assert_eq!(s, "hello"),
61///     MyChoice::B(_) => unreachable!(),
62/// }
63/// ```
64#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
65pub struct ChoiceWrapper<T> {
66    /// The wrapped xs:choice enum value.
67    #[serde(rename = "$value")]
68    pub inner: T,
69}
70
71impl<T> ChoiceWrapper<T> {
72    /// Wrap a value.
73    pub fn new(inner: T) -> Self {
74        Self { inner }
75    }
76}
77
78impl<T> From<T> for ChoiceWrapper<T> {
79    fn from(inner: T) -> Self {
80        Self { inner }
81    }
82}
83
84impl<T> std::ops::Deref for ChoiceWrapper<T> {
85    type Target = T;
86
87    fn deref(&self) -> &Self::Target {
88        &self.inner
89    }
90}
91
92impl<T> std::ops::DerefMut for ChoiceWrapper<T> {
93    fn deref_mut(&mut self) -> &mut Self::Target {
94        &mut self.inner
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[derive(Debug, Clone, PartialEq)]
103    enum TestChoice {
104        TypeA(String),
105        TypeB(u32),
106    }
107
108    #[test]
109    fn deref_reaches_inner() {
110        let w = ChoiceWrapper::new(TestChoice::TypeA("x".to_owned()));
111        match &*w {
112            TestChoice::TypeA(s) => assert_eq!(s, "x"),
113            TestChoice::TypeB(_) => panic!("wrong variant"),
114        }
115    }
116
117    #[test]
118    fn from_impl() {
119        let w: ChoiceWrapper<TestChoice> = TestChoice::TypeB(7).into();
120        assert_eq!(w.inner, TestChoice::TypeB(7));
121    }
122
123    #[test]
124    fn deref_mut_updates_inner() {
125        let mut w = ChoiceWrapper::new(TestChoice::TypeA("before".to_owned()));
126        *w = TestChoice::TypeA("after".to_owned());
127        match &w.inner {
128            TestChoice::TypeA(s) => assert_eq!(s, "after"),
129            TestChoice::TypeB(_) => panic!("wrong variant"),
130        }
131    }
132}