1//! `atproto!` macro.
2/// Construct a atproto `Data<'_>` value from a literal.
3///
4/// ```
5/// # use jacquard_common::atproto;
6/// #
7/// let value = atproto!({
8/// "code": 200,
9/// "success": true,
10/// "payload": {
11/// "features": [
12/// "serde",
13/// "json"
14/// ]
15/// }
16/// });
17/// ```
18///
19/// Variables or expressions can be interpolated into the ATProto literal. Any type
20/// interpolated into an array element or object value must implement Serde's
21/// `Serialize` trait, while any type interpolated into a object key must
22/// implement `Into<String>`. If the `Serialize` implementation of the
23/// interpolated type decides to fail, or if the interpolated type contains a
24/// map with non-string keys, the `atproto!` macro will panic.
25///
26/// ```
27/// # use jacquard_common::atproto;
28/// #
29/// let code = 200;
30/// let features = vec!["serde", "json"];
31///
32/// let value = atproto!({
33/// "code": code,
34/// "success": code == 200,
35/// "payload": {
36/// features[0]: features[1]
37/// }
38/// });
39/// ```
40///
41/// Trailing commas are allowed inside both arrays and objects.
42///
43/// ```
44/// # use jacquard_common::atproto;
45/// #
46/// let value = atproto!([
47/// "notice",
48/// "the",
49/// "trailing",
50/// "comma -->",
51/// ]);
52/// ```
53#[macro_export(local_inner_macros)]
54macro_rules! atproto {
55 // Hide distracting implementation details from the generated rustdoc.
56 ($($atproto:tt)+) => {
57 atproto_internal!($($atproto)+)
58 };
59}
60
61#[macro_export(local_inner_macros)]
62#[doc(hidden)]
63macro_rules! atproto_internal {
64 //////////////////////////////////////////////////////////////////////////
65 // TT muncher for parsing the inside of an array [...]. Produces a vec![...]
66 // of the elements.
67 //
68 // Must be invoked as: atproto_internal!(@array [] $($tt)*)
69 //////////////////////////////////////////////////////////////////////////
70
71 // Done with trailing comma.
72 (@array [$($elems:expr,)*]) => {
73 atproto_internal_vec![$($elems,)*]
74 };
75
76 // Done without trailing comma.
77 (@array [$($elems:expr),*]) => {
78 atproto_internal_vec![$($elems),*]
79 };
80
81 // Next element is `null`.
82 (@array [$($elems:expr,)*] null $($rest:tt)*) => {
83 atproto_internal!(@array [$($elems,)* atproto_internal!(null)] $($rest)*)
84 };
85
86 // Next element is `true`.
87 (@array [$($elems:expr,)*] true $($rest:tt)*) => {
88 atproto_internal!(@array [$($elems,)* atproto_internal!(true)] $($rest)*)
89 };
90
91 // Next element is `false`.
92 (@array [$($elems:expr,)*] false $($rest:tt)*) => {
93 atproto_internal!(@array [$($elems,)* atproto_internal!(false)] $($rest)*)
94 };
95
96 // Next element is an array.
97 (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
98 atproto_internal!(@array [$($elems,)* atproto_internal!([$($array)*])] $($rest)*)
99 };
100
101 // Next element is a map.
102 (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
103 atproto_internal!(@array [$($elems,)* atproto_internal!({$($map)*})] $($rest)*)
104 };
105
106 // Next element is an expression followed by comma.
107 (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
108 atproto_internal!(@array [$($elems,)* atproto_internal!($next),] $($rest)*)
109 };
110
111 // Last element is an expression with no trailing comma.
112 (@array [$($elems:expr,)*] $last:expr) => {
113 atproto_internal!(@array [$($elems,)* atproto_internal!($last)])
114 };
115
116 // Comma after the most recent element.
117 (@array [$($elems:expr),*] , $($rest:tt)*) => {
118 atproto_internal!(@array [$($elems,)*] $($rest)*)
119 };
120
121 // Unexpected token after most recent element.
122 (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
123 atproto_unexpected!($unexpected)
124 };
125
126 //////////////////////////////////////////////////////////////////////////
127 // TT muncher for parsing the inside of an object {...}. Each entry is
128 // inserted into the given map variable.
129 //
130 // Must be invoked as: atproto_internal!(@object $map () ($($tt)*) ($($tt)*))
131 //
132 // We require two copies of the input tokens so that we can match on one
133 // copy and trigger errors on the other copy.
134 //////////////////////////////////////////////////////////////////////////
135
136 // Done.
137 (@object $object:ident () () ()) => {};
138
139 // Insert the current entry followed by trailing comma.
140 (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
141 let _ = $object.insert(($($key)+).into(), $value);
142 atproto_internal!(@object $object () ($($rest)*) ($($rest)*));
143 };
144
145 // Current entry followed by unexpected token.
146 (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
147 atproto_unexpected!($unexpected);
148 };
149
150 // Insert the last entry without trailing comma.
151 (@object $object:ident [$($key:tt)+] ($value:expr)) => {
152 let _ = $object.insert(($($key)+).into(), $value);
153 };
154
155 // Next value is `null`.
156 (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
157 atproto_internal!(@object $object [$($key)+] (atproto_internal!(null)) $($rest)*);
158 };
159
160 // Next value is `true`.
161 (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
162 atproto_internal!(@object $object [$($key)+] (atproto_internal!(true)) $($rest)*);
163 };
164
165 // Next value is `false`.
166 (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
167 atproto_internal!(@object $object [$($key)+] (atproto_internal!(false)) $($rest)*);
168 };
169
170 // Next value is an array.
171 (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
172 atproto_internal!(@object $object [$($key)+] (atproto_internal!([$($array)*])) $($rest)*);
173 };
174
175 // Next value is a map.
176 (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
177 atproto_internal!(@object $object [$($key)+] (atproto_internal!({$($map)*})) $($rest)*);
178 };
179
180 // Next value is an expression followed by comma.
181 (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
182 atproto_internal!(@object $object [$($key)+] (atproto_internal!($value)) , $($rest)*);
183 };
184
185 // Last value is an expression with no trailing comma.
186 (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
187 atproto_internal!(@object $object [$($key)+] (atproto_internal!($value)));
188 };
189
190 // Missing value for last entry. Trigger a reasonable error message.
191 (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
192 // "unexpected end of macro invocation"
193 atproto_internal!();
194 };
195
196 // Missing colon and value for last entry. Trigger a reasonable error
197 // message.
198 (@object $object:ident ($($key:tt)+) () $copy:tt) => {
199 // "unexpected end of macro invocation"
200 atproto_internal!();
201 };
202
203 // Misplaced colon. Trigger a reasonable error message.
204 (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
205 // Takes no arguments so "no rules expected the token `:`".
206 atproto_unexpected!($colon);
207 };
208
209 // Found a comma inside a key. Trigger a reasonable error message.
210 (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
211 // Takes no arguments so "no rules expected the token `,`".
212 atproto_unexpected!($comma);
213 };
214
215 // Key is fully parenthesized. This avoids clippy double_parens false
216 // positives because the parenthesization may be necessary here.
217 (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
218 atproto_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*));
219 };
220
221 // Munch a token into the current key.
222 (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
223 atproto_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
224 };
225
226 //////////////////////////////////////////////////////////////////////////
227 // The main implementation.
228 //
229 // Must be invoked as: atproto_internal!($($atproto)+)
230 //////////////////////////////////////////////////////////////////////////
231
232 (null) => {
233 $crate::types::value::Data::Null
234 };
235
236 (true) => {
237 $crate::types::value::Data::Boolean(true)
238 };
239
240 (false) => {
241 $crate::types::value::Data::Boolean(false)
242 };
243
244 ([]) => {
245 $crate::types::value::Data::Array($crate::types::value::Array(atproto_internal_vec![]))
246 };
247
248 ([ $($tt:tt)+ ]) => {
249 $crate::types::value::Data::Array($crate::types::value::Array(atproto_internal!(@array [] $($tt)+)))
250 };
251
252 ({}) => {
253 $crate::types::value::Data::Object($crate::types::value::Object(::std::collections::BTreeMap::new()))
254 };
255
256 ({ $($tt:tt)+ }) => {
257 $crate::types::value::Data::Object($crate::types::value::Object({
258 let mut object = ::std::collections::BTreeMap::new();
259 atproto_internal!(@object object () ($($tt)+) ($($tt)+));
260 object
261 }))
262 };
263
264 // Any Serialize type: numbers, strings, struct literals, variables etc.
265 // Must be below every other rule.
266 ($other:expr) => {
267 {
268 $crate::types::value::Data::from($other)
269 }
270 };
271}
272
273// The atproto_internal macro above cannot invoke vec directly because it uses
274// local_inner_macros. A vec invocation there would resolve to $crate::vec.
275// Instead invoke vec here outside of local_inner_macros.
276#[macro_export]
277#[doc(hidden)]
278macro_rules! atproto_internal_vec {
279 ($($content:tt)*) => {
280 ::std::vec![$($content)*]
281 };
282}
283
284#[macro_export]
285#[doc(hidden)]
286macro_rules! atproto_unexpected {
287 () => {};
288}