stefans_utils/
literals.rs1#[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
158literal_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}