stefans_utils/
literals.rs1#[macro_export]
2macro_rules! literal {
3 ($(#[$attr:meta])* $name:ident($type:ty) = $value:expr) => {
4 #[derive(::core::fmt::Debug, ::core::clone::Clone, ::serde::Serialize)]
5 $(#[$attr])*
6 pub struct $name($type);
7
8 impl<'de> ::serde::Deserialize<'de> for $name {
9 fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
10 where
11 D: ::serde::Deserializer<'de>,
12 {
13 let value = <$type as ::serde::Deserialize<'de>>::deserialize(deserializer)?;
14
15 Self::try_from(value).map_err(::serde::de::Error::custom)
16 }
17 }
18
19 impl<T: PartialEq<$type>> PartialEq<T> for $name {
20 fn eq(&self, other: &T) -> bool {
21 other.eq(&self.0)
22 }
23 }
24
25 impl ::schemars::JsonSchema for $name {
26 fn schema_name() -> ::std::borrow::Cow<'static, str> {
27 stringify!($name).into()
28 }
29
30 fn json_schema(_: &mut ::schemars::SchemaGenerator) -> ::schemars::Schema {
31 ::schemars::json_schema!({ "const": $value })
32 }
33 }
34
35 impl ::std::fmt::Display for $name {
36 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
37 f.write_fmt(format_args!("{}", &self.0))
38 }
39 }
40
41 impl AsRef<$type> for $name {
42 fn as_ref(&self) -> &$type {
43 &self.0
44 }
45 }
46
47 impl TryFrom<$type> for $name {
48 type Error = String;
49
50 fn try_from(value: $type) -> ::std::result::Result<Self, Self::Error> {
51 if value == $value {
52 Ok(Self(value))
53 } else {
54 Err(format!("Value must be exactly {:?}", $value))
55 }
56 }
57 }
58
59 impl From<$name> for $type {
60 fn from(value: $name) -> Self {
61 value.0
62 }
63 }
64 };
65}
66
67#[macro_export]
68macro_rules! literal_string {
69 ($name:ident = $value:expr) => {
70 $crate::literal!($name(String) = $value);
71
72 impl Default for $name {
73 fn default() -> Self {
74 Self($value.to_string())
75 }
76 }
77 };
78}
79
80macro_rules! literal_scalars {
81 ($d:tt $($type:ident)*) => {
82 $(
83 ::paste::paste! {
84 #[macro_export]
85 macro_rules! [<literal_ $type>] {
86 ($d name:ident = $d value:expr) => {
87 $crate::literal!($d name($type) = $d value);
88
89 impl Default for $d name {
90 fn default() -> Self {
91 Self($d value)
92 }
93 }
94 };
95 }
96 }
97 )*
98 };
99}
100
101literal_scalars!($ u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize f32 f64 bool);
102
103#[cfg(test)]
104mod tests {
105 use schemars::{json_schema, schema_for};
106 use serde_json::json;
107
108 crate::literal_string!(Xbox = "xbox");
109
110 #[test]
111 fn literal_string_should_deserialize_ok_from_correct_value() {
112 let ok = serde_json::from_value::<Xbox>(json!("xbox")).unwrap();
113
114 assert_eq!(ok, "xbox");
115 }
116
117 #[test]
118 fn literal_string_should_deserialize_err_from_correct_type_but_wrong_value() {
119 let err = serde_json::from_value::<Xbox>(json!("ps4")).unwrap_err();
120
121 assert_eq!(err.to_string(), "Value must be exactly \"xbox\"");
122 }
123
124 #[test]
125 fn literal_string_should_produce_correct_json_schema() {
126 let schema = schema_for!(Xbox);
127
128 assert_eq!(
129 schema,
130 json_schema!({
131 "$schema": "https://json-schema.org/draft/2020-12/schema",
132 "title": "Xbox",
133 "const": "xbox"
134 })
135 );
136 }
137
138 literal_u32!(OneTwoThree = 123);
139
140 #[test]
141 fn literal_u32_should_deserialize_ok_from_correct_value() {
142 let ok = serde_json::from_value::<OneTwoThree>(json!(123)).unwrap();
143
144 assert_eq!(ok, 123);
145 }
146
147 #[test]
148 fn literal_u32_should_deserialize_err_from_correct_type_but_wrong_value() {
149 let err = serde_json::from_value::<OneTwoThree>(json!(321)).unwrap_err();
150
151 assert_eq!(err.to_string(), "Value must be exactly 123");
152 }
153
154 #[test]
155 fn literal_u32_should_produce_correct_json_schema() {
156 let schema = schema_for!(OneTwoThree);
157
158 assert_eq!(
159 schema,
160 json_schema!({
161 "$schema": "https://json-schema.org/draft/2020-12/schema",
162 "title": "OneTwoThree",
163 "const": 123
164 })
165 );
166 }
167}