relay_protocol/
macros.rs

1/// Returns a reference to the typed [`Annotated`] value at a given path.
2///
3/// The return type depends on the path expression used. By default, this macro will resolve to an
4/// `Option<&Annotated<T>>`, where the option is `Some` if the path exists. Note that if the
5/// annotated value at the specificed path is empty, this returns `Some` with an empty annotated.
6///
7/// When used with an exclamation mark after the path, this macro unwraps to an `&Annotated<T>`.
8///
9/// [`Annotated`]: crate::Annotated
10///
11/// # Syntax
12///
13/// A path starts with the name of a variable holding an [`Annotated`]. Access to children of this
14/// type depend on the value type:
15///  - To access a struct field, use a period followed by the field's name, for instance (`.field`).
16///  - To access an array element, use the element's numeric index in brackets, for instance `[0]`.
17///  - To access an object's element, use the element's quoted string key in brackets, for instance
18///    `["key_name"]`.
19///
20/// Paths can be chained, so a valid path expression is `data.values["key"].field`.
21///
22/// To unwrap the annotated field at the destination, append an exclamation mark at the end of the
23/// path, for instance: `data.field!`.
24///
25/// # Panics
26///
27/// Panics when unwrap (`!`) is used and there is an empty field along the path. Since `get_path!`
28/// always returns the final `Annotated`, the final field can be empty without panicking.
29///
30/// # Example
31///
32/// ```
33/// use relay_protocol::{get_path, Annotated, Object};
34///
35/// struct Inner {
36///     value: Annotated<u64>,
37/// }
38///
39/// struct Outer {
40///     inners: Annotated<Object<Inner>>,
41/// }
42///
43/// let outer = Annotated::new(Outer {
44///     inners: Annotated::new(Object::from([(
45///         "key".to_string(),
46///         Annotated::new(Inner {
47///             value: Annotated::new(1),
48///         }),
49///     )])),
50/// });
51///
52/// assert_eq!(get_path!(outer.inners["key"].value), Some(&Annotated::new(1)));
53/// assert_eq!(get_path!(outer.inners["key"].value!), &Annotated::new(1));
54/// ```
55#[macro_export]
56macro_rules! get_path {
57    (@access $root:ident,) => {};
58    (@access $root:ident, !) => {
59        let $root = $root.unwrap();
60    };
61    (@access $root:ident, . $field:ident $( $tail:tt )*) => {
62        let $root = $root.and_then(|a| a.value()).map(|v| &v.$field);
63        get_path!(@access $root, $($tail)*);
64    };
65    (@access $root:ident, [ $index:literal ] $( $tail:tt )*) => {
66        let $root = $root.and_then(|a| a.value()).and_then(|v| v.get($index));
67        get_path!(@access $root, $($tail)*);
68    };
69    ($root:ident $( $tail:tt )*) => {{
70        let $root = Some(&$root);
71        $crate::get_path!(@access $root, $($tail)*);
72        $root
73    }};
74}
75
76/// Returns a reference to the typed value at a given path in an [`Annotated`].
77///
78/// The return type depends on the path expression used. By default, this macro will resolve to an
79/// `Option<&T>`, where the option is `Some` if the path exists and the value is present.
80///
81/// When used with an exclamation mark after the path, this macro unwraps to an `&T`.
82///
83/// [`Annotated`]: crate::Annotated
84///
85/// # Syntax
86///
87/// A path starts with the name of a variable holding an [`Annotated`]. Access to children of this
88/// type depend on the value type:
89///  - To access a struct field, use a period followed by the field's name, for instance (`.field`).
90///  - To access an array element, use the element's numeric index in brackets, for instance `[0]`.
91///  - To access an object's element, use the element's quoted string key in brackets, for instance
92///    `["key_name"]`.
93///
94/// Paths can be chained, so a valid path expression is `data.values["key"].field`.
95///
96/// To unwrap the value at the destination, append an exclamation mark at the end of the path, for
97/// instance: `data.field!`.
98///
99/// # Panics
100///
101/// Panics when unwrap (`!`) is used and there is an empty field along the path.
102///
103/// # Example
104///
105/// ```
106/// use relay_protocol::{get_value, Annotated, Object};
107///
108/// struct Inner {
109///     value: Annotated<u64>,
110/// }
111///
112/// struct Outer {
113///     inners: Annotated<Object<Inner>>,
114/// }
115///
116/// let outer = Annotated::new(Outer {
117///     inners: Annotated::new(Object::from([(
118///         "key".to_string(),
119///         Annotated::new(Inner {
120///             value: Annotated::new(1),
121///         }),
122///     )])),
123/// });
124///
125/// assert_eq!(get_value!(outer.inners["key"].value), Some(&1));
126/// assert_eq!(get_value!(outer.inners["key"].value!), &1);
127/// ```
128#[macro_export]
129macro_rules! get_value {
130    (@access $root:ident,) => {};
131    (@access $root:ident, !) => {
132        let $root = $root.unwrap();
133    };
134    (@access $root:ident, . $field:ident $( $tail:tt )*) => {
135        let $root = $root.and_then(|v| v.$field.value());
136        get_value!(@access $root, $($tail)*);
137    };
138    (@access $root:ident, [ $index:literal ] $( $tail:tt )*) => {
139        let $root = $root.and_then(|v| v.get($index)).and_then(|a| a.value());
140        get_value!(@access $root, $($tail)*);
141    };
142    ($root:ident $( $tail:tt )*) => {{
143        let $root = $root.value();
144        $crate::get_value!(@access $root, $($tail)*);
145        $root
146    }};
147}
148
149/// Derives the [`FromValue`], [`IntoValue`], and [`Empty`] traits using the string representation.
150///
151/// Requires that this type implements `FromStr` and `Display`. Implements the following traits:
152///
153///  - [`FromValue`]: Deserializes a string, then uses [`FromStr`](std::str::FromStr) to convert
154///    into the type.
155///  - [`IntoValue`]: Serializes into a string using the [`Display`](std::fmt::Display) trait.
156///  - [`Empty`]: This type is never empty.
157///
158/// [`FromValue`]: crate::FromValue
159/// [`IntoValue`]: crate::IntoValue
160/// [`Empty`]: crate::Empty
161#[macro_export]
162macro_rules! derive_string_meta_structure {
163    ($type:ident, $expectation:expr) => {
164        impl $crate::FromValue for $type {
165            fn from_value(value: Annotated<Value>) -> Annotated<Self> {
166                match value {
167                    Annotated(Some(Value::String(value)), mut meta) => match value.parse() {
168                        Ok(value) => Annotated(Some(value), meta),
169                        Err(err) => {
170                            meta.add_error($crate::Error::invalid(err));
171                            meta.set_original_value(Some(value));
172                            Annotated(None, meta)
173                        }
174                    },
175                    Annotated(None, meta) => Annotated(None, meta),
176                    Annotated(Some(value), mut meta) => {
177                        meta.add_error($crate::Error::expected($expectation));
178                        meta.set_original_value(Some(value));
179                        Annotated(None, meta)
180                    }
181                }
182            }
183        }
184
185        impl $crate::IntoValue for $type {
186            fn into_value(self) -> Value {
187                Value::String(self.to_string())
188            }
189
190            fn serialize_payload<S>(
191                &self,
192                s: S,
193                _behavior: $crate::SkipSerialization,
194            ) -> Result<S::Ok, S::Error>
195            where
196                Self: Sized,
197                S: serde::ser::Serializer,
198            {
199                s.collect_str(self)
200            }
201        }
202
203        impl $crate::Empty for $type {
204            fn is_empty(&self) -> bool {
205                false
206            }
207        }
208    };
209}
210
211pub use derive_string_meta_structure;
212
213/// Asserts the snapshot of an annotated structure using `insta`.
214#[cfg(feature = "test")]
215#[macro_export]
216macro_rules! assert_annotated_snapshot {
217    ($value:expr, @$snapshot:literal) => {
218        ::insta::assert_snapshot!(
219            $value.to_json_pretty().unwrap(),
220            stringify!($value),
221            @$snapshot
222        )
223    };
224    ($value:expr, $debug_expr:expr, @$snapshot:literal) => {
225        ::insta::assert_snapshot!(
226            $value.to_json_pretty().unwrap(),
227            $debug_expr,
228            @$snapshot
229        )
230    };
231    ($name:expr, $value:expr) => {
232        ::insta::assert_snapshot!(
233            $name,
234            $value.to_json_pretty().unwrap(),
235            stringify!($value)
236        )
237    };
238    ($name:expr, $value:expr, $debug_expr:expr) => {
239        ::insta::assert_snapshot!(
240            $name,
241            $value.to_json_pretty().unwrap(),
242            $debug_expr
243        )
244    };
245    ($value:expr) => {
246        ::insta::assert_snapshot!(
247            None::<String>,
248            $value.to_json_pretty().unwrap(),
249            stringify!($value)
250        )
251    };
252}
253
254#[cfg(test)]
255mod tests {
256    use similar_asserts::assert_eq;
257
258    use crate::{Annotated, Array, Object};
259
260    #[derive(Clone, Debug, PartialEq)]
261    struct Inner {
262        value: Annotated<u64>,
263    }
264
265    #[derive(Clone, Debug, PartialEq)]
266    struct Outer {
267        inner: Annotated<Inner>,
268    }
269
270    #[test]
271    fn get_path() {
272        let value = Annotated::new(1);
273        let inner = Annotated::new(Inner {
274            value: value.clone(),
275        });
276        let outer = Annotated::new(Outer {
277            inner: inner.clone(),
278        });
279
280        // Optional
281        assert_eq!(get_path!(outer), Some(&outer));
282        assert_eq!(get_path!(outer.inner), Some(&inner));
283        assert_eq!(get_path!(outer.inner.value), Some(&value));
284
285        // Unwrap
286        assert_eq!(get_path!(outer!), &outer);
287        assert_eq!(get_path!(outer.inner!), &inner);
288        assert_eq!(get_path!(outer.inner.value!), &value);
289    }
290
291    #[test]
292    fn get_path_empty() {
293        let empty = Annotated::<Outer>::empty();
294        let outer_empty = Annotated::new(Outer {
295            inner: Annotated::empty(),
296        });
297        let outer = Annotated::new(Outer {
298            inner: Annotated::new(Inner {
299                value: Annotated::empty(),
300            }),
301        });
302
303        // Empty leaf
304        assert_eq!(get_path!(empty), Some(&Annotated::empty()));
305        assert_eq!(get_path!(outer_empty.inner), Some(&Annotated::empty()));
306        assert_eq!(get_path!(outer.inner.value), Some(&Annotated::empty()));
307
308        // Empty in the path
309        assert_eq!(get_path!(empty.inner), None);
310        assert_eq!(get_path!(empty.inner.value), None);
311        assert_eq!(get_path!(outer_empty.inner.value), None);
312
313        // Empty unwraps
314        assert_eq!(get_path!(empty!), &Annotated::empty());
315        assert_eq!(get_path!(outer_empty.inner!), &Annotated::empty());
316        assert_eq!(get_path!(outer.inner.value!), &Annotated::empty());
317    }
318
319    #[test]
320    fn get_path_array() {
321        let array = Annotated::new(Array::from([Annotated::new(0), Annotated::new(1)]));
322
323        // Indexes
324        assert_eq!(get_path!(array[0]), Some(&Annotated::new(0)));
325        assert_eq!(get_path!(array[1]), Some(&Annotated::new(1)));
326        // Out of bounds
327        assert_eq!(get_path!(array[2]), None);
328        // Unwrap
329        assert_eq!(get_path!(array[0]!), &Annotated::new(0));
330    }
331
332    #[test]
333    fn get_path_object() {
334        let object = Annotated::new(Object::from([("key".to_string(), Annotated::new(1))]));
335
336        // Exists
337        assert_eq!(get_path!(object["key"]), Some(&Annotated::new(1)));
338        // Unwrap
339        assert_eq!(get_path!(object["key"]!), &Annotated::new(1));
340        // Does not exist
341        assert_eq!(get_path!(object["other"]), None);
342    }
343
344    #[test]
345    fn get_path_combined() {
346        struct Inner {
347            value: Annotated<u64>,
348        }
349
350        struct Outer {
351            inners: Annotated<Object<Inner>>,
352        }
353
354        let outer = Annotated::new(Outer {
355            inners: Annotated::new(Object::from([(
356                "key".to_string(),
357                Annotated::new(Inner {
358                    value: Annotated::new(1),
359                }),
360            )])),
361        });
362
363        assert_eq!(
364            get_path!(outer.inners["key"].value),
365            Some(&Annotated::new(1))
366        );
367        assert_eq!(get_path!(outer.inners["key"].value!), &Annotated::new(1));
368    }
369
370    #[test]
371    fn get_value() {
372        let inner = Inner {
373            value: Annotated::new(1),
374        };
375        let outer = Outer {
376            inner: Annotated::new(inner.clone()),
377        };
378        let annotated = Annotated::new(outer.clone());
379
380        // Optional
381        assert_eq!(get_value!(annotated), Some(&outer));
382        assert_eq!(get_value!(annotated.inner), Some(&inner));
383        assert_eq!(get_value!(annotated.inner.value), Some(&1));
384
385        // Unwrap
386        assert_eq!(get_value!(annotated!), &outer);
387        assert_eq!(get_value!(annotated.inner!), &inner);
388        assert_eq!(get_value!(annotated.inner.value!), &1);
389    }
390
391    #[test]
392    fn get_value_empty() {
393        let empty = Annotated::<Outer>::empty();
394        let outer_empty = Annotated::new(Outer {
395            inner: Annotated::empty(),
396        });
397        let outer = Annotated::new(Outer {
398            inner: Annotated::new(Inner {
399                value: Annotated::empty(),
400            }),
401        });
402
403        // Empty leaf
404        assert_eq!(get_value!(empty), None);
405        assert_eq!(get_value!(outer_empty.inner), None);
406        assert_eq!(get_value!(outer.inner.value), None);
407
408        // Empty in the path
409        assert_eq!(get_value!(empty.inner), None);
410        assert_eq!(get_value!(empty.inner.value), None);
411        assert_eq!(get_value!(outer_empty.inner.value), None);
412    }
413
414    #[test]
415    fn get_value_array() {
416        let array = Annotated::new(Array::from([Annotated::new(0), Annotated::new(1)]));
417
418        // Existing indexes
419        assert_eq!(get_value!(array[0]), Some(&0));
420        assert_eq!(get_value!(array[1]), Some(&1));
421        // Out of bounds
422        assert_eq!(get_value!(array[2]), None);
423        // Unwrap
424        assert_eq!(get_value!(array[0]!), &0);
425    }
426
427    #[test]
428    fn get_value_object() {
429        let object = Annotated::new(Object::from([("key".to_string(), Annotated::new(1))]));
430
431        // Exists
432        assert_eq!(get_value!(object["key"]), Some(&1));
433        // Unwrap
434        assert_eq!(get_value!(object["key"]!), &1);
435        // Does not exist
436        assert_eq!(get_value!(object["other"]), None);
437    }
438
439    #[test]
440    fn get_value_combined() {
441        struct Inner {
442            value: Annotated<u64>,
443        }
444
445        struct Outer {
446            inners: Annotated<Object<Inner>>,
447        }
448
449        let outer = Annotated::new(Outer {
450            inners: Annotated::new(Object::from([(
451                "key".to_string(),
452                Annotated::new(Inner {
453                    value: Annotated::new(1),
454                }),
455            )])),
456        });
457
458        assert_eq!(get_value!(outer.inners["key"].value), Some(&1));
459        assert_eq!(get_value!(outer.inners["key"].value!), &1);
460    }
461}