Skip to main content

stefans_utils/
literals.rs

1#[macro_export]
2macro_rules! literal {
3    ($(#[$attr:meta])* $name:ident($type:tt) = $value:literal) => {
4        $crate::literal!($(#[$attr])* $name($type => $type) = $value);
5    };
6
7    ($(#[$attr:meta])* $name:ident($serde_from:tt => $type:tt) = $value:literal) => {
8        $crate::literal!(@ $(#[$attr])* $name($serde_from => $type) = $value);
9
10        impl $name {
11            pub const VALUE: $type = $value;
12        }
13
14        impl<T: PartialEq<$type>> PartialEq<T> for $name {
15            fn eq(&self, other: &T) -> bool {
16                other.eq(&Self::VALUE)
17            }
18        }
19    };
20
21    ($(#[$attr:meta])* $name:ident(&$type:tt) = $value:literal) => {
22        $crate::literal!($(#[$attr])* $name($type => $type) = $value);
23    };
24
25    ($(#[$attr:meta])* $name:ident($serde_from:tt => &$type:tt) = $value:literal) => {
26        $crate::literal!(@ $(#[$attr])* $name($serde_from => $type) = $value);
27
28        impl $name {
29            pub const VALUE: &$type = $value;
30        }
31
32        impl<T: for<'a> PartialEq<&'a $type>> PartialEq<T> for $name {
33            fn eq(&self, other: &T) -> bool {
34                other.eq(&Self::VALUE)
35            }
36        }
37    };
38
39    (@ $(#[$attr:meta])*  $name:ident($serde_from:tt => $type:tt) = $value:literal) => {
40        #[derive(Clone, Copy)]
41        $(#[$attr])*
42        pub struct $name;
43
44        impl AsRef<$type> for $name {
45            fn as_ref(&self) -> &$type {
46                &Self::VALUE
47            }
48        }
49
50        impl ::std::fmt::Debug for $name {
51            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52                f.debug_tuple($name::NAME).field(&$name::VALUE).finish()
53            }
54        }
55
56        impl $name {
57            const NAME: &'static str = stringify!($name);
58        }
59
60        impl Default for $name {
61            fn default() -> Self {
62                Self
63            }
64        }
65
66        impl ::serde::Serialize for $name {
67            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68            where
69                S: ::serde::Serializer,
70            {
71                serializer.serialize_newtype_struct($name::NAME, &$name::VALUE)
72            }
73        }
74
75        impl<'de> ::serde::Deserialize<'de> for $name {
76            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77            where
78                D: ::serde::Deserializer<'de>,
79            {
80                <$serde_from as ::serde::Deserialize<'de>>::deserialize(deserializer).and_then(
81                    |value| {
82                        if value == $name::VALUE {
83                            Ok($name)
84                        } else {
85                            Err(::serde::de::Error::custom(concat!(
86                                "Value must be exactly ",
87                                stringify!($value)
88                            )))
89                        }
90                    },
91                )
92            }
93        }
94
95        impl ::schemars::JsonSchema for $name {
96            fn schema_name() -> ::std::borrow::Cow<'static, str> {
97                $name::NAME.into()
98            }
99
100            fn json_schema(_: &mut ::schemars::SchemaGenerator) -> ::schemars::Schema {
101                ::schemars::json_schema!({
102                    "const": $name::VALUE
103                })
104            }
105        }
106
107        impl Eq for $name {}
108    };
109}
110
111#[macro_export]
112macro_rules! literal_str {
113    ($(#[$attr:meta])* $name:ident = $value:literal) => {
114        $crate::literal!($(#[$attr])* $name(String => &str) = $value);
115    };
116}
117
118#[macro_export]
119macro_rules! literal_bool {
120    ($(#[$attr:meta])* $name:ident = true) => {
121        $crate::literal!($(#[$attr])* $name(bool) = true);
122        impl ::std::ops::Not for $name {
123            type Output = bool;
124            fn not(self) -> bool {
125                false
126            }
127        }
128    };
129
130    ($(#[$attr:meta])* $name:ident = false) => {
131        $crate::literal!($(#[$attr])* $name(bool) = false);
132        impl ::std::ops::Not for $name {
133            type Output = bool;
134            fn not(self) -> bool {
135                true
136            }
137        }
138    };
139}
140
141macro_rules! literal_scalars {
142    ($d:tt $($type:ident)*) => {
143        $(
144            ::paste::paste! {
145                #[macro_export]
146                macro_rules! [<literal_ $type>] {
147                    ($d (#[$d attr:meta])* $d name:ident = $d value:literal) => {
148                        $crate::literal!($d (#[$d attr])* $d name ($type) = $d value);
149                    }
150                }
151            }
152        )*
153    }
154}
155
156literal_scalars!($ u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize f32 f64);
157
158// export actual types because what the hell
159literal_bool!(True = true);
160literal_bool!(False = false);
161
162#[cfg(test)]
163mod tests {
164
165    #[test]
166    fn literal_bool_should_work() {
167        use super::{False, True};
168
169        assert_eq!(True, True);
170        assert_ne!(True, False);
171        assert_ne!(False, True);
172        assert_eq!(False, False);
173
174        assert_eq!(True, true);
175        assert_ne!(True, false);
176        assert_ne!(False, true);
177        assert_eq!(False, false);
178
179        assert!(True);
180        assert!(!False);
181    }
182
183    #[test]
184    fn literal_str_should_work() {
185        literal_str!(
186            #[doc = "metametameta"]
187            MyStr = "my-value"
188        );
189
190        assert_eq!(
191            serde_json::from_str::<MyStr>("\"my-value\"").unwrap(),
192            "my-value"
193        );
194
195        assert_eq!(MyStr, MyStr);
196    }
197
198    #[test]
199    fn literal_u8_should_work() {
200        literal_u8!(Two = 2);
201        literal_u8!(
202            #[doc = "lololol"]
203            AlsoTwo = 2
204        );
205
206        assert_eq!(Two, 2);
207        assert_eq!(Two, Two);
208        assert_eq!(Two, AlsoTwo);
209        assert_eq!(AlsoTwo, Two);
210        assert_ne!(Two, 3);
211    }
212    use schemars::{json_schema, schema_for};
213    use serde_json::json;
214
215    crate::literal_str!(Xbox = "xbox");
216
217    #[test]
218    fn literal_string_should_deserialize_ok_from_correct_value() {
219        let ok = serde_json::from_value::<Xbox>(json!("xbox")).unwrap();
220
221        assert_eq!(ok, "xbox");
222    }
223
224    #[test]
225    fn literal_string_should_deserialize_err_from_correct_type_but_wrong_value() {
226        let err = serde_json::from_value::<Xbox>(json!("ps4")).unwrap_err();
227
228        assert_eq!(err.to_string(), "Value must be exactly \"xbox\"");
229    }
230
231    #[test]
232    fn literal_string_should_produce_correct_json_schema() {
233        let schema = schema_for!(Xbox);
234
235        assert_eq!(
236            schema,
237            json_schema!({
238                "$schema": "https://json-schema.org/draft/2020-12/schema",
239                "title": "Xbox",
240                "const": "xbox"
241            })
242        );
243    }
244
245    literal_u32!(OneTwoThree = 123);
246
247    #[test]
248    fn literal_u32_should_deserialize_ok_from_correct_value() {
249        let ok = serde_json::from_value::<OneTwoThree>(json!(123)).unwrap();
250
251        assert_eq!(ok, 123);
252    }
253
254    #[test]
255    fn literal_u32_should_deserialize_err_from_correct_type_but_wrong_value() {
256        let err = serde_json::from_value::<OneTwoThree>(json!(321)).unwrap_err();
257
258        assert_eq!(err.to_string(), "Value must be exactly 123");
259    }
260
261    #[test]
262    fn literal_u32_should_produce_correct_json_schema() {
263        let schema = schema_for!(OneTwoThree);
264
265        assert_eq!(
266            schema,
267            json_schema!({
268                "$schema": "https://json-schema.org/draft/2020-12/schema",
269                "title": "OneTwoThree",
270                "const": 123
271            })
272        );
273    }
274}