openzeppelin_monitor/utils/macros/
deserialization.rs

1//! Utilities for case-insensitive enum serialization/deserialization
2//!
3//! This module provides macros and utilities to help with case-insensitive
4//! handling of enum variants in JSON and other serialization formats.
5
6/// Macro to implement case-insensitive deserialization for enums with simple string variants.
7///
8/// This macro generates a custom `Deserialize` implementation for an enum that makes
9/// variant string matching case-insensitive. It works with enums that use string
10/// representation in serialization (e.g., with `#[serde(tag = "type")]`).
11///
12/// The generated implementation will match variant names case-insensitively, so both
13/// `"variant1"` and `"VARIANT1"` will be deserialized as `MyEnum::Variant1`.
14#[macro_export]
15macro_rules! impl_case_insensitive_enum {
16    ($enum_name:ident, { $($variant_str:expr => $variant:ident),* $(,)? }) => {
17        impl<'de> ::serde::Deserialize<'de> for $enum_name {
18            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
19            where
20                D: ::serde::Deserializer<'de>,
21            {
22                use ::serde::de::{self, MapAccess, Visitor};
23                use std::fmt;
24
25                struct EnumVisitor;
26
27                impl<'de> Visitor<'de> for EnumVisitor {
28                    type Value = $enum_name;
29
30                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
31                        formatter.write_str(concat!("a struct with a `type` field for ", stringify!($enum_name)))
32                    }
33
34                    fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
35                    where
36                        M: MapAccess<'de>,
37                    {
38                        let mut type_: Option<String> = None;
39                        let mut value: Option<::serde_json::Value> = None;
40
41                        while let Some(key) = map.next_key::<String>()? {
42                            if key == "type" {
43                                type_ = Some(map.next_value()?);
44                            } else if key == "value" {
45                                value = Some(map.next_value()?);
46                            } else {
47                                let _: ::serde_json::Value = map.next_value()?;
48                            }
49                        }
50
51                        let type_ = type_.ok_or_else(|| de::Error::missing_field("type"))?;
52                        let value = value.ok_or_else(|| de::Error::missing_field("value"))?;
53                        let type_lowercase = type_.to_lowercase();
54
55                        match type_lowercase.as_str() {
56                            $(
57                                $variant_str => {
58                                    let content = ::serde_json::from_value::<String>(value)
59                                        .map_err(|e| de::Error::custom(format!(
60                                            concat!("invalid ", $variant_str, " value: {}"), e
61                                        )))?;
62                                    Ok($enum_name::$variant(content.into()))
63                                },
64                            )*
65                            _ => Err(de::Error::unknown_variant(
66                                &type_,
67                                &[$($variant_str),*],
68                            )),
69                        }
70                    }
71                }
72
73                deserializer.deserialize_map(EnumVisitor)
74            }
75        }
76    };
77}
78
79/// Macro to implement case-insensitive deserialize for struct enum variants
80///
81/// Similar to `impl_case_insensitive_enum` but for enums where variants contain structs
82/// rather than simple types.
83#[macro_export]
84macro_rules! impl_case_insensitive_enum_struct {
85    ($enum_name:ident, { $($variant_str:expr => $variant:ident),* $(,)? }) => {
86        impl<'de> ::serde::Deserialize<'de> for $enum_name {
87            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88            where
89                D: ::serde::Deserializer<'de>,
90            {
91                use ::serde::de::{self, MapAccess, Visitor};
92                use std::fmt;
93
94                #[derive(::serde::Deserialize)]
95                struct TypeField {
96                    #[serde(rename = "type")]
97                    type_: String,
98                    #[serde(flatten)]
99                    rest: ::serde_json::Value,
100                }
101
102                struct EnumVisitor;
103
104                impl<'de> Visitor<'de> for EnumVisitor {
105                    type Value = $enum_name;
106
107                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
108                        formatter.write_str(concat!("a struct with a `type` field for ", stringify!($enum_name)))
109                    }
110
111                    fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
112                    where
113                        M: MapAccess<'de>,
114                    {
115                        // First deserialize into an intermediate structure to extract the type field
116                        let TypeField { type_, rest } = ::serde::Deserialize::deserialize(
117                            ::serde::de::value::MapAccessDeserializer::new(map)
118                        )?;
119
120                        let type_lowercase = type_.to_lowercase();
121
122                        // Then match on the type field to determine the variant
123                        match type_lowercase.as_str() {
124                            $(
125                                $variant_str => {
126                                    // Create a new value with the corrected type field
127                                    let mut json_value = rest;
128                                    if let ::serde_json::Value::Object(ref mut map) = json_value {
129                                        map.insert("type".to_string(), ::serde_json::Value::String(stringify!($variant).to_string()));
130                                    }
131
132                                    // Deserialize into the enum
133                                    ::serde_json::from_value(json_value)
134                                        .map_err(de::Error::custom)
135                                }
136                            )*
137                            _ => Err(de::Error::unknown_variant(
138                                &type_,
139                                &[$($variant_str),*],
140                            )),
141                        }
142                    }
143                }
144
145                deserializer.deserialize_map(EnumVisitor)
146            }
147        }
148    };
149}
150
151#[cfg(test)]
152mod tests {
153	use serde::Serialize;
154
155	#[test]
156	fn test_impl_case_insensitive_enum() {
157		#[derive(Debug, Clone, Serialize, PartialEq)]
158		#[serde(tag = "type", content = "value")]
159		enum MyEnum {
160			Variant1(String),
161			Variant2(String),
162		}
163
164		impl_case_insensitive_enum!(MyEnum, {
165			"variant1" => Variant1,
166			"variant2" => Variant2,
167		});
168
169		let json = r#"{"type": "variant1", "value": "test"}"#;
170		let deserialized: MyEnum = serde_json::from_str(json).unwrap();
171		assert_eq!(deserialized, MyEnum::Variant1("test".to_string()));
172
173		let json = r#"{"type": "VARIANT1", "value": "test"}"#;
174		let deserialized: MyEnum = serde_json::from_str(json).unwrap();
175		assert_eq!(deserialized, MyEnum::Variant1("test".to_string()));
176
177		let json = r#"{"type": "Variant1", "value": "test"}"#;
178		let deserialized: MyEnum = serde_json::from_str(json).unwrap();
179		assert_eq!(deserialized, MyEnum::Variant1("test".to_string()));
180
181		let json = r#"{"type": "variant2", "value": "test"}"#;
182		let deserialized: MyEnum = serde_json::from_str(json).unwrap();
183		assert_eq!(deserialized, MyEnum::Variant2("test".to_string()));
184
185		let json = r#"{"type": "VARIANT2", "value": "test"}"#;
186		let deserialized: MyEnum = serde_json::from_str(json).unwrap();
187		assert_eq!(deserialized, MyEnum::Variant2("test".to_string()));
188
189		let json = r#"{"type": "Variant2", "value": "test"}"#;
190		let deserialized: MyEnum = serde_json::from_str(json).unwrap();
191		assert_eq!(deserialized, MyEnum::Variant2("test".to_string()));
192
193		let json = r#"{"type": "variant3", "value": "test"}"#;
194		let deserialized: Result<MyEnum, serde_json::Error> = serde_json::from_str(json);
195		assert!(deserialized.is_err());
196
197		let json = r#"{"type": "VARIANT3", "value": "test"}"#;
198		let deserialized: Result<MyEnum, serde_json::Error> = serde_json::from_str(json);
199		assert!(deserialized.is_err());
200
201		let json = r#"{"type": "Variant3", "value": "test"}"#;
202		let deserialized: Result<MyEnum, serde_json::Error> = serde_json::from_str(json);
203		assert!(deserialized.is_err());
204	}
205}