settings_inspect_utils/
managed_inspect_map.rs

1// Copyright 2022 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
5use fuchsia_inspect::Node;
6use fuchsia_inspect_derive::{AttachError, Inspect, WithInspect};
7use std::collections::HashMap;
8
9/// A map that wraps an inspect node and attaches all inserted values to the node.
10///
11/// This class can either be explicitly given an inspect node through [ManagedInspectMap::with_node]
12/// or can create its own inspect node when included in a struct that derives Inspect or when
13/// [ManagedInspectMap::with_inspect] is called.
14#[derive(Default)]
15pub struct ManagedInspectMap<V> {
16    map: HashMap<String, V>,
17    node: Node,
18}
19
20impl<V> ManagedInspectMap<V>
21where
22    for<'a> &'a mut V: Inspect,
23{
24    /// Creates a new [ManagedInspectMap] that attaches inserted values to the given node.
25    pub fn with_node(node: Node) -> Self {
26        Self { map: HashMap::new(), node }
27    }
28
29    /// Returns a reference to the underlying map. Clients should not insert values into the
30    /// map through this reference.
31    pub fn map(&self) -> &HashMap<String, V> {
32        &self.map
33    }
34
35    /// Returns a mutable reference to the underlying map. Clients should not insert values into the
36    /// map through this reference.
37    pub fn map_mut(&mut self) -> &mut HashMap<String, V> {
38        &mut self.map
39    }
40
41    /// Inserts the given value into the map and attach it to the inspect tree. Returns the previous
42    /// value with the given key, if any.
43    pub fn insert(&mut self, key: String, value: V) -> Option<V> {
44        // `with_inspect` will only return an error on types with interior mutability.
45        let value_with_inspect =
46            value.with_inspect(&self.node, &key).expect("Failed to attach new map entry");
47        self.map.insert(key, value_with_inspect)
48    }
49
50    /// Inserts the given value into the map and attaches it to the inspect tree with a different
51    /// name. Returns the previous value with the given map key, if any.
52    ///
53    /// This is useful for cases where the unique key for the map is not useful for actually
54    /// recording to inspect.
55    pub fn insert_with_property_name(
56        &mut self,
57        map_key: String,
58        property_name: String,
59        value: V,
60    ) -> Option<V> {
61        // `with_inspect` will only return an error on types with interior mutability.
62        let value_with_inspect =
63            value.with_inspect(&self.node, property_name).expect("Failed to attach new map entry");
64        self.map.insert(map_key, value_with_inspect)
65    }
66
67    /// Returns a mutable reference to the value at the given key, inserting a value if not present.
68    pub fn get_or_insert_with(&mut self, key: String, value: impl FnOnce() -> V) -> &mut V {
69        let node = &self.node;
70        self.map.entry(key.clone()).or_insert_with(|| {
71            // `with_inspect` will only return an error on types with interior mutability.
72            value().with_inspect(node, &key).expect("Failed to attach new map entry")
73        })
74    }
75
76    /// Returns a mutable reference to the entry at `key`.
77    pub fn get_mut(&mut self, key: &str) -> Option<&mut V> {
78        self.map.get_mut(key)
79    }
80
81    /// Returns an immutable reference to the entry at `key`.
82    pub fn get(&self, key: &str) -> Option<&V> {
83        self.map.get(key)
84    }
85
86    /// Returns a reference to the inspect node associated with this map.
87    pub fn inspect_node(&self) -> &Node {
88        &self.node
89    }
90}
91
92impl<V> Inspect for &mut ManagedInspectMap<V>
93where
94    for<'a> &'a mut V: Inspect,
95{
96    fn iattach(self, parent: &Node, name: impl AsRef<str>) -> Result<(), AttachError> {
97        self.node = parent.create_child(name.as_ref());
98        Ok(())
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use crate::managed_inspect_map::ManagedInspectMap;
105    use diagnostics_assertions::assert_data_tree;
106    use fuchsia_inspect::{Inspector, Node};
107    use fuchsia_inspect_derive::{IValue, Inspect, WithInspect};
108
109    #[derive(Default, Inspect)]
110    struct TestInspectWrapper {
111        inspect_node: Node,
112        pub test_map: ManagedInspectMap<IValue<String>>,
113    }
114
115    // Tests that inserting items into the map automatically records them in inspect.
116    #[fuchsia::test]
117    fn test_map_insert() {
118        let inspector = Inspector::default();
119
120        let mut map = ManagedInspectMap::<IValue<String>>::with_node(
121            inspector.root().create_child("managed_node"),
122        );
123
124        let _ = map.insert("key1".to_string(), "value1".to_string().into());
125        let _ = map.insert("key2".to_string(), "value2".to_string().into());
126
127        assert_data_tree!(inspector, root: {
128            managed_node: {
129                "key1": "value1",
130                "key2": "value2"
131            }
132        });
133    }
134
135    // Tests that removing items from the map automatically removes them from inspect.
136    #[fuchsia::test]
137    fn test_map_remove() {
138        let inspector = Inspector::default();
139
140        let mut map = ManagedInspectMap::<IValue<String>>::with_node(
141            inspector.root().create_child("managed_node"),
142        );
143
144        let _ = map.insert("key1".to_string(), "value1".to_string().into());
145        let _ = map.insert("key2".to_string(), "value2".to_string().into());
146
147        let _ = map.map_mut().remove(&"key1".to_string());
148
149        assert_data_tree!(inspector, root: {
150            managed_node: {
151                "key2": "value2"
152            }
153        });
154    }
155
156    // Tests that inserting items with a different property name shows the intended property name in
157    // inspect.
158    #[fuchsia::test]
159    fn test_map_insert_with_property_name() {
160        let inspector = Inspector::default();
161
162        let mut map = ManagedInspectMap::<IValue<String>>::with_node(
163            inspector.root().create_child("managed_node"),
164        );
165
166        let _ = map.insert_with_property_name(
167            "key1".to_string(),
168            "property_name_1".to_string(),
169            "value1".to_string().into(),
170        );
171
172        // This will overwrite the previous insert due to using the same map key.
173        let _ = map.insert_with_property_name(
174            "key1".to_string(),
175            "property_name_2".to_string(),
176            "value2".to_string().into(),
177        );
178
179        assert_data_tree!(inspector, root: {
180            managed_node: {
181                "property_name_2": "value2"
182            }
183        });
184    }
185
186    // Tests that the map automatically attaches itself to the inspect hierarchy when used as a
187    // field in a struct that derives Inspect.
188    #[fuchsia::test]
189    fn test_map_derive_inspect() {
190        let inspector = Inspector::default();
191
192        let mut wrapper = TestInspectWrapper::default()
193            .with_inspect(inspector.root(), "wrapper_node")
194            .expect("Failed to attach wrapper_node");
195
196        let _ = wrapper.test_map.insert("key1".to_string(), "value1".to_string().into());
197
198        // The map's node is named test_map since that's the field name in TestInspectWrapper.
199        assert_data_tree!(inspector, root: {
200            wrapper_node: {
201                test_map: {
202                    "key1": "value1",
203                }
204            }
205        });
206    }
207}