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 #[allow(clippy::bool_assert_comparison)]
167 fn literal_bool_should_work() {
168 use super::{False, True};
169
170 assert_eq!(True, True);
171 assert_ne!(True, False);
172 assert_ne!(False, True);
173 assert_eq!(False, False);
174
175 assert_eq!(True, true);
176 assert_ne!(True, false);
177 assert_ne!(False, true);
178 assert_eq!(False, false);
179
180 assert!(True);
181 assert!(!False);
182 }
183
184 #[test]
185 fn literal_str_should_work() {
186 literal_str!(
187 #[doc = "metametameta"]
188 MyStr = "my-value"
189 );
190
191 assert_eq!(
192 serde_json::from_str::<MyStr>("\"my-value\"").unwrap(),
193 "my-value"
194 );
195
196 assert_eq!(MyStr, MyStr);
197 }
198
199 #[test]
200 fn literal_u8_should_work() {
201 literal_u8!(Two = 2);
202 literal_u8!(
203 #[doc = "lololol"]
204 AlsoTwo = 2
205 );
206
207 assert_eq!(Two, 2);
208 assert_eq!(Two, Two);
209 assert_eq!(Two, AlsoTwo);
210 assert_eq!(AlsoTwo, Two);
211 assert_ne!(Two, 3);
212 }
213 use schemars::{json_schema, schema_for};
214 use serde_json::json;
215
216 crate::literal_str!(Xbox = "xbox");
217
218 #[test]
219 fn literal_string_should_deserialize_ok_from_correct_value() {
220 let ok = serde_json::from_value::<Xbox>(json!("xbox")).unwrap();
221
222 assert_eq!(ok, "xbox");
223 }
224
225 #[test]
226 fn literal_string_should_deserialize_err_from_correct_type_but_wrong_value() {
227 let err = serde_json::from_value::<Xbox>(json!("ps4")).unwrap_err();
228
229 assert_eq!(err.to_string(), "Value must be exactly \"xbox\"");
230 }
231
232 #[test]
233 fn literal_string_should_produce_correct_json_schema() {
234 let schema = schema_for!(Xbox);
235
236 assert_eq!(
237 schema,
238 json_schema!({
239 "$schema": "https://json-schema.org/draft/2020-12/schema",
240 "title": "Xbox",
241 "const": "xbox"
242 })
243 );
244 }
245
246 literal_u32!(OneTwoThree = 123);
247
248 #[test]
249 fn literal_u32_should_deserialize_ok_from_correct_value() {
250 let ok = serde_json::from_value::<OneTwoThree>(json!(123)).unwrap();
251
252 assert_eq!(ok, 123);
253 }
254
255 #[test]
256 fn literal_u32_should_deserialize_err_from_correct_type_but_wrong_value() {
257 let err = serde_json::from_value::<OneTwoThree>(json!(321)).unwrap_err();
258
259 assert_eq!(err.to_string(), "Value must be exactly 123");
260 }
261
262 #[test]
263 fn literal_u32_should_produce_correct_json_schema() {
264 let schema = schema_for!(OneTwoThree);
265
266 assert_eq!(
267 schema,
268 json_schema!({
269 "$schema": "https://json-schema.org/draft/2020-12/schema",
270 "title": "OneTwoThree",
271 "const": 123
272 })
273 );
274 }
275}