diagnostics_hierarchy/
macros.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Macro utilities for building a `DiagnosticsHierarchy`.
6
7use crate::{ArrayContent, Property};
8
9/// This macro simplifies creating diagnostics hierarchies, to remove the need of writing multiple
10/// nested hierarchies and manually writing all properties.
11///
12/// Example:
13///
14/// ```
15/// let hierarchy = hierarchy!{
16///     root: {
17///         some_int_property: 2,
18///         some_nested_child: {
19///             some_string_property: "foo",
20///         }
21///     }
22/// };
23/// ```
24///
25/// Using names like this will create a `DiagnosticsHierarchy` where `Key` is of type `String`.
26///
27/// It's possible to use expressions that resolve to some other type. In the following example, we
28/// use `Field` variants as the key. The function `bar()` returns a `Field::Bar`.
29///
30/// ```
31/// enum Field {
32///     Foo,
33///     Bar,
34/// }
35///
36/// let hierarchy = hierarchy!{
37///     root: {
38///         Field::Foo => "bar",
39///         bar() => 2,
40///     }
41/// };
42/// ```
43///
44/// The only requirement (as in any `DiagnosticsHierarchy`'s `Key`) it must implement `AsRef<str>`.
45///
46/// It's also possible to use existing variables as keys. For example:
47///
48/// ```
49/// let my_var = "my_key".to_string();
50/// let hierarchy = hierarchy!{
51///     root: {
52///         var my_var: "foo",
53///     }
54/// };
55/// ```
56#[macro_export]
57macro_rules! hierarchy {
58    (@build $hierarchy:expr,) => {};
59
60    // Handle adding a nodes to the hierarchy.
61    (@build $hierarchy:expr, var $key:ident: { $($sub:tt)* }) => {{
62        #[allow(unused_mut)]
63        let mut child = $crate::DiagnosticsHierarchy::new($key, vec![], vec![]);
64        $crate::hierarchy!(@build child, $($sub)*);
65        $hierarchy.add_child(child);
66    }};
67    (@build $hierarchy:expr, var $key:ident: { $($sub:tt)* }, $($rest:tt)*) => {{
68        $crate::hierarchy!(@build $hierarchy, var $key: { $($sub)* });
69        $crate::hierarchy!(@build $hierarchy, $($rest)*);
70    }};
71
72    // Handle adding properties to the hierarchy.
73    (@build $hierarchy:expr, var $key:ident: $value:expr) => {{
74        use $crate::macros::IntoPropertyWithKey;
75        let property = $value.into_property_with_key($key);
76        $hierarchy.add_property(property);
77    }};
78    (@build $hierarchy:expr, var $key:ident: $value:expr, $($rest:tt)*) => {{
79        $crate::hierarchy!(@build $hierarchy, var $key: $value);
80        $crate::hierarchy!(@build $hierarchy, $($rest)*);
81    }};
82
83    // Allow a literal as key. It'll be treated as a String key.
84    (@build $hierarchy:expr, $key:ident: $($rest:tt)+) => {{
85        let key = stringify!($key).to_string();
86        $crate::hierarchy!(@build $hierarchy, var key: $($rest)+);
87    }};
88    // Allow a string literal as key.
89    (@build $hierarchy:expr, $key:tt: $($rest:tt)+) => {{
90        let key: &'static str = $key;
91        let key = $key.to_string();
92        $crate::hierarchy!(@build $hierarchy, var key: $($rest)+);
93    }};
94    // Allow a expression as key, only for properties.
95    (@build $hierarchy:expr, $key:expr => $value:expr) => {{
96        let key = $key;
97        $crate::hierarchy!(@build $hierarchy, var key: $value);
98    }};
99    (@build $hierarchy:expr, $key:expr => $value:expr, $($rest:tt)*) => {{
100        let key = $key;
101        $crate::hierarchy!(@build $hierarchy, var key: $value, $($rest)*);
102    }};
103
104    // Entry points
105    (var $key:ident: { $($rest:tt)* }) => {{
106        #[allow(unused_mut)]
107        let mut hierarchy = $crate::DiagnosticsHierarchy::new($key, vec![], vec![]);
108        $crate::hierarchy!(@build hierarchy, $($rest)*);
109        hierarchy
110    }};
111    ($key:ident: $($rest:tt)+) => {{
112        let key = stringify!($key).to_string();
113        $crate::hierarchy!(var key: $($rest)+)
114    }};
115    ($key:tt: $($rest:tt)+) => {{
116        let key : &'static str = $key;
117        let key = key.to_string();
118        $crate::tree_assertion!(var key: $($rest)+)
119    }};
120}
121
122/// Trait implemented by all types that can be converted to a `Property`.
123pub trait IntoPropertyWithKey<Key = String> {
124    fn into_property_with_key(self, key: Key) -> Property<Key>;
125}
126
127macro_rules! impl_into_property_with_key {
128    // Implementation for all `$type`s.
129    ($property_name:ident, [$($type:ty),*]) => {
130        $(
131        impl<Key> IntoPropertyWithKey<Key> for $type {
132            fn into_property_with_key(self, key: Key) -> Property<Key> {
133                Property::$property_name(key, self.into())
134            }
135        }
136        )*
137    };
138
139    // Implementation for all `Vec<$type>`s calling Into for each of them.
140    ($property_name:ident, map:[$($type:ty),*]) => {
141        $(
142        impl<Key> IntoPropertyWithKey<Key> for $type {
143            fn into_property_with_key(self, key: Key) -> Property<Key> {
144                let value = self.into_iter().map(|value| value.into()).collect::<Vec<_>>();
145                Property::$property_name(key, ArrayContent::Values(value))
146            }
147        }
148        )*
149    };
150}
151
152// TODO(https://fxbug.dev/42155846): support missing data types -> bytes, histograms
153impl_into_property_with_key!(String, [String, &str]);
154impl_into_property_with_key!(Int, [i64, i32, i16, i8]);
155impl_into_property_with_key!(Uint, [u64, u32, u16, u8]);
156impl_into_property_with_key!(Double, [f64, f32]);
157impl_into_property_with_key!(Bool, [bool]);
158impl_into_property_with_key!(DoubleArray, map:[Vec<f64>, Vec<f32>]);
159impl_into_property_with_key!(IntArray, map:[Vec<i64>, Vec<i32>, Vec<i16>, Vec<i8>]);
160impl_into_property_with_key!(UintArray, map:[Vec<u64>, Vec<u32>, Vec<u16>]);
161impl_into_property_with_key!(StringList, [Vec<String>]);
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::{ArrayFormat, DiagnosticsHierarchy};
167
168    #[fuchsia::test]
169    fn test_empty_hierarchy() {
170        let expected = DiagnosticsHierarchy::new_root();
171        let h: DiagnosticsHierarchy = hierarchy! { root: {} };
172        assert_eq!(expected, h);
173    }
174
175    #[fuchsia::test]
176    fn test_all_types() {
177        let string_list = vec!["foo".to_string(), "bar".to_string()];
178
179        let mut expected = DiagnosticsHierarchy::new_root();
180        expected.add_property(Property::String("string".to_string(), "some string".to_string()));
181        expected.add_property(Property::String("strref".to_string(), "some str ref".to_string()));
182        expected.add_property(Property::Int("int8".to_string(), 1i64));
183        expected.add_property(Property::Int("int16".to_string(), 2i64));
184        expected.add_property(Property::Int("int32".to_string(), 3i64));
185        expected.add_property(Property::Int("int64".to_string(), 4i64));
186        expected.add_property(Property::Uint("uint16".to_string(), 5u64));
187        expected.add_property(Property::Uint("uint32".to_string(), 6u64));
188        expected.add_property(Property::Uint("uint64".to_string(), 7u64));
189        expected.add_property(Property::Double("float32".to_string(), 8.25f64));
190        expected.add_property(Property::Double("float64".to_string(), 9f64));
191        expected.add_property(Property::Bool("boolean".to_string(), true));
192        expected.add_property(Property::DoubleArray(
193            "array_float32".to_string(),
194            ArrayContent::new(vec![1f64, 2.5], ArrayFormat::Default).unwrap(),
195        ));
196        expected.add_property(Property::DoubleArray(
197            "array_float64".to_string(),
198            ArrayContent::new(vec![3f64, 4.25], ArrayFormat::Default).unwrap(),
199        ));
200        expected.add_property(Property::IntArray(
201            "array_int8".to_string(),
202            ArrayContent::new(vec![1i64, 2, 3, 4], ArrayFormat::Default).unwrap(),
203        ));
204        expected.add_property(Property::IntArray(
205            "array_int16".to_string(),
206            ArrayContent::new(vec![5i64, 6, 7, 8], ArrayFormat::Default).unwrap(),
207        ));
208        expected.add_property(Property::IntArray(
209            "array_int32".to_string(),
210            ArrayContent::new(vec![2i64, 4], ArrayFormat::Default).unwrap(),
211        ));
212        expected.add_property(Property::IntArray(
213            "array_int64".to_string(),
214            ArrayContent::new(vec![6i64, 8], ArrayFormat::Default).unwrap(),
215        ));
216        expected.add_property(Property::UintArray(
217            "array_uint16".to_string(),
218            ArrayContent::new(vec![0u64, 9], ArrayFormat::Default).unwrap(),
219        ));
220        expected.add_property(Property::UintArray(
221            "array_uint32".to_string(),
222            ArrayContent::new(vec![1u64, 3, 5], ArrayFormat::Default).unwrap(),
223        ));
224        expected.add_property(Property::UintArray(
225            "array_uint64".to_string(),
226            ArrayContent::new(vec![7u64, 9], ArrayFormat::Default).unwrap(),
227        ));
228        expected.add_property(Property::StringList("string_list".to_string(), string_list.clone()));
229
230        let result = hierarchy! {
231            root: {
232                string: "some string".to_string(),
233                strref: "some str ref",
234                int8:  1i8,
235                int16: 2i16,
236                int32: 3i32,
237                int64: 4i64,
238                uint16: 5u16,
239                uint32: 6u32,
240                uint64: 7u64,
241                float32: 8.25f32,
242                float64: 9f64,
243                boolean: true,
244                array_float32: vec![1f32, 2.5],
245                array_float64: vec![3f64, 4.25],
246                array_int8: vec![1i8,2,3,4],
247                array_int16: vec![5i16,6,7,8],
248                array_int32: vec![2i32,4],
249                array_int64: vec![6i64,8],
250                array_uint16: vec![0u16,9],
251                array_uint32: vec![1u32,3, 5],
252                array_uint64: vec![7u64, 9],
253                string_list: string_list.clone(),
254            }
255        };
256
257        assert_eq!(expected, result);
258    }
259
260    #[fuchsia::test]
261    fn test_nested_hierarchy() {
262        let mut expected = DiagnosticsHierarchy::new_root();
263        expected.add_property_at_path(
264            &["root", "sub1", "sub11", "sub111"],
265            Property::Int("value".to_string(), 1i64),
266        );
267        expected.add_property_at_path(
268            &["root", "sub1", "sub11", "sub111"],
269            Property::String("other_value".to_string(), "foo".to_string()),
270        );
271        expected.add_property_at_path(
272            &["root", "sub2", "sub21"],
273            Property::Uint("value".to_string(), 2u64),
274        );
275        let _ = expected.get_or_add_node(&["root", "sub2", "sub22"]);
276        let result = hierarchy! {
277            root: {
278                sub1: {
279                    sub11: {
280                        sub111: {
281                            value: 1i64,
282                            other_value: "foo",
283                        }
284                    }
285                },
286                sub2: {
287                    sub21: {
288                        value: 2u64,
289                    },
290                    sub22: {},
291                }
292            }
293        };
294        assert_eq!(expected, result);
295    }
296
297    #[fuchsia::test]
298    fn test_var_key_syntax() {
299        let mut expected = DiagnosticsHierarchy::new("foo", vec![], vec![]);
300        expected
301            .add_property_at_path(&["foo"], Property::String("bar".to_string(), "baz".to_string()));
302        let some_key = "foo".to_string();
303        let another_key = "bar".to_string();
304        let result = hierarchy! {
305            var some_key: {
306                var another_key: "baz",
307            }
308        };
309        assert_eq!(expected, result);
310    }
311
312    #[derive(Debug, PartialEq)]
313    enum Field {
314        Foo,
315        Bar,
316    }
317
318    impl AsRef<str> for Field {
319        fn as_ref(&self) -> &str {
320            match self {
321                Field::Foo => "foo",
322                Field::Bar => "bar",
323            }
324        }
325    }
326
327    #[fuchsia::test]
328    fn test_custom_key_type_for_properties() {
329        let result = hierarchy! {
330            root: {
331                Field::Bar => 2u64,
332                baz: {
333                    Field::Foo => "baz",
334                }
335            }
336        };
337        assert_eq!(
338            result,
339            DiagnosticsHierarchy::new(
340                "root",
341                vec![Property::Uint(Field::Bar, 2u64)],
342                vec![DiagnosticsHierarchy::new(
343                    "baz",
344                    vec![Property::String(Field::Foo, "baz".to_string())],
345                    vec![]
346                )],
347            )
348        );
349    }
350}