|
2 | 2 |
|
3 | 3 | use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
4 | 4 |
|
| 5 | +#[macro_export] |
| 6 | +#[doc(hidden)] |
| 7 | +macro_rules! deserialize_variant { |
| 8 | + // Produce struct enum variant |
| 9 | + ( $de:expr, $enum:tt, $variant:ident { |
| 10 | + $( |
| 11 | + $(#[$meta:meta])* |
| 12 | + $field:ident : $typ:ty |
| 13 | + ),* $(,)? |
| 14 | + } ) => ({ |
| 15 | + let var = { |
| 16 | + // Create anonymous type |
| 17 | + #[derive(serde::Deserialize)] |
| 18 | + struct $variant { |
| 19 | + $( |
| 20 | + $(#[$meta])* |
| 21 | + $field: $typ, |
| 22 | + )* |
| 23 | + } |
| 24 | + <$variant>::deserialize($de)? |
| 25 | + }; |
| 26 | + // Due to https://github.com/rust-lang/rust/issues/86935 we cannot use |
| 27 | + // <$enum> :: $variant |
| 28 | + use $enum :: *; |
| 29 | + $variant { |
| 30 | + $($field: var.$field,)* |
| 31 | + } |
| 32 | + }); |
| 33 | + |
| 34 | + // Produce newtype enum variant |
| 35 | + ( $de:expr, $enum:tt, $variant:ident($typ:ty) ) => ({ |
| 36 | + let var = <$typ>::deserialize($de)?; |
| 37 | + <$enum> :: $variant(var) |
| 38 | + }); |
| 39 | + |
| 40 | + // Produce unit enum variant |
| 41 | + ( $de:expr, $enum:tt, $variant:ident ) => ({ |
| 42 | + serde::de::IgnoredAny::deserialize($de)?; |
| 43 | + <$enum> :: $variant |
| 44 | + }); |
| 45 | +} |
| 46 | + |
| 47 | +/// A helper to implement [`Deserialize`] for [internally tagged] enums which |
| 48 | +/// does not use [`Deserializer::deserialize_any`] that produces wrong results |
| 49 | +/// with XML because of [serde#1183]. |
| 50 | +/// |
| 51 | +/// In contract to deriving [`Deserialize`] this macro assumes that a tag will be |
| 52 | +/// the first element or attribute in the XML. |
| 53 | +/// |
| 54 | +/// # Example |
| 55 | +/// |
| 56 | +/// ``` |
| 57 | +/// # use pretty_assertions::assert_eq; |
| 58 | +/// use quick_xml::de::from_str; |
| 59 | +/// use quick_xml::impl_deserialize_for_internally_tagged_enum; |
| 60 | +/// use serde::Deserialize; |
| 61 | +/// |
| 62 | +/// #[derive(Deserialize, Debug, PartialEq)] |
| 63 | +/// struct Root { |
| 64 | +/// one: InternallyTaggedEnum, |
| 65 | +/// two: InternallyTaggedEnum, |
| 66 | +/// three: InternallyTaggedEnum, |
| 67 | +/// } |
| 68 | +/// |
| 69 | +/// #[derive(Debug, PartialEq)] |
| 70 | +/// // #[serde(tag = "@tag")] |
| 71 | +/// enum InternallyTaggedEnum { |
| 72 | +/// Unit, |
| 73 | +/// Newtype(Newtype), |
| 74 | +/// Struct { |
| 75 | +/// // #[serde(rename = "@attribute")] |
| 76 | +/// attribute: u32, |
| 77 | +/// element: f32, |
| 78 | +/// }, |
| 79 | +/// } |
| 80 | +/// |
| 81 | +/// #[derive(Deserialize, Debug, PartialEq)] |
| 82 | +/// struct Newtype { |
| 83 | +/// #[serde(rename = "@attribute")] |
| 84 | +/// attribute: u64, |
| 85 | +/// } |
| 86 | +/// |
| 87 | +/// // The macro needs the type of the enum, the tag name, |
| 88 | +/// // and information about all the variants |
| 89 | +/// impl_deserialize_for_internally_tagged_enum!{ |
| 90 | +/// InternallyTaggedEnum, "@tag", |
| 91 | +/// ("Unit" => Unit), |
| 92 | +/// ("Newtype" => Newtype(Newtype)), |
| 93 | +/// ("Struct" => Struct { |
| 94 | +/// #[serde(rename = "@attribute")] |
| 95 | +/// attribute: u32, |
| 96 | +/// element: f32, |
| 97 | +/// }), |
| 98 | +/// } |
| 99 | +/// |
| 100 | +/// assert_eq!( |
| 101 | +/// from_str::<Root>(r#" |
| 102 | +/// <root> |
| 103 | +/// <one tag="Unit" /> |
| 104 | +/// <two tag="Newtype" attribute="42" /> |
| 105 | +/// <three tag="Struct" attribute="42"> |
| 106 | +/// <element>4.2</element> |
| 107 | +/// </three> |
| 108 | +/// </root> |
| 109 | +/// "#).unwrap(), |
| 110 | +/// Root { |
| 111 | +/// one: InternallyTaggedEnum::Unit, |
| 112 | +/// two: InternallyTaggedEnum::Newtype(Newtype { attribute: 42 }), |
| 113 | +/// three: InternallyTaggedEnum::Struct { |
| 114 | +/// attribute: 42, |
| 115 | +/// element: 4.2, |
| 116 | +/// }, |
| 117 | +/// }, |
| 118 | +/// ); |
| 119 | +/// ``` |
| 120 | +/// |
| 121 | +/// [internally tagged]: https://serde.rs/enum-representations.html#internally-tagged |
| 122 | +/// [serde#1183]: https://github.com/serde-rs/serde/issues/1183 |
| 123 | +#[macro_export(local_inner_macros)] |
| 124 | +macro_rules! impl_deserialize_for_internally_tagged_enum { |
| 125 | + ( |
| 126 | + $enum:ty, |
| 127 | + $tag:literal, |
| 128 | + $( |
| 129 | + ($variant_tag:literal => $($variant:tt)+ ) |
| 130 | + ),* $(,)? |
| 131 | + ) => { |
| 132 | + impl<'de> serde::de::Deserialize<'de> for $enum { |
| 133 | + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| 134 | + where |
| 135 | + D: serde::de::Deserializer<'de>, |
| 136 | + { |
| 137 | + use serde::de::{Error, MapAccess, Visitor}; |
| 138 | + |
| 139 | + // The Visitor struct is normally used for state, but none is needed |
| 140 | + struct TheVisitor; |
| 141 | + // The main logic of the deserializing happens in the Visitor trait |
| 142 | + impl<'de> Visitor<'de> for TheVisitor { |
| 143 | + // The type that is being deserialized |
| 144 | + type Value = $enum; |
| 145 | + |
| 146 | + // Try to give a better error message when this is used wrong |
| 147 | + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
| 148 | + f.write_str("expecting map with tag in ")?; |
| 149 | + f.write_str($tag) |
| 150 | + } |
| 151 | + |
| 152 | + // The xml data is provided as an opaque map, |
| 153 | + // that map is parsed into the type |
| 154 | + fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> |
| 155 | + where |
| 156 | + A: MapAccess<'de>, |
| 157 | + { |
| 158 | + // Here the assumption is made that only one attribute |
| 159 | + // exists and it's the discriminator (enum "tag"). |
| 160 | + let entry: Option<(String, String)> = map.next_entry()?; |
| 161 | + // If there are more attributes those would need |
| 162 | + // to be parsed as well. |
| 163 | + let tag = match entry { |
| 164 | + // Return an error if the no attributes are found, |
| 165 | + // and indicate that the @tag attribute is missing. |
| 166 | + None => Err(A::Error::missing_field($tag)), |
| 167 | + // Check if the attribute is the tag |
| 168 | + Some((attribute, value)) => { |
| 169 | + if attribute == $tag { |
| 170 | + // return the value of the tag |
| 171 | + Ok(value) |
| 172 | + } else { |
| 173 | + // The attribute is not @tag, return an error |
| 174 | + // indicating that there is an unexpected attribute |
| 175 | + Err(A::Error::unknown_field(&attribute, &[$tag])) |
| 176 | + } |
| 177 | + } |
| 178 | + }?; |
| 179 | + |
| 180 | + let de = serde::de::value::MapAccessDeserializer::new(map); |
| 181 | + match tag.as_ref() { |
| 182 | + $( |
| 183 | + $variant_tag => Ok(deserialize_variant!( de, $enum, $($variant)+ )), |
| 184 | + )* |
| 185 | + _ => Err(A::Error::unknown_field(&tag, &[$($variant_tag),+])), |
| 186 | + } |
| 187 | + } |
| 188 | + } |
| 189 | + // Tell the deserializer to deserialize the data as a map, |
| 190 | + // using the TheVisitor as the decoder |
| 191 | + deserializer.deserialize_map(TheVisitor) |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | +} |
| 196 | + |
5 | 197 | /// Provides helper functions to serialization and deserialization of types
|
6 | 198 | /// (usually enums) as a text content of an element and intended to use with
|
7 | 199 | /// [`#[serde(with = "...")]`][with], [`#[serde(deserialize_with = "...")]`][de-with]
|
|
0 commit comments