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}