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.
45use fuchsia_inspect::Node;
6use fuchsia_inspect_derive::{AttachError, Inspect, WithInspect};
7use std::collections::HashMap;
89/// 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}
1920impl<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.
25pub fn with_node(node: Node) -> Self {
26Self { map: HashMap::new(), node }
27 }
2829/// Returns a reference to the underlying map. Clients should not insert values into the
30 /// map through this reference.
31pub fn map(&self) -> &HashMap<String, V> {
32&self.map
33 }
3435/// Returns a mutable reference to the underlying map. Clients should not insert values into the
36 /// map through this reference.
37pub fn map_mut(&mut self) -> &mut HashMap<String, V> {
38&mut self.map
39 }
4041/// 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.
43pub fn insert(&mut self, key: String, value: V) -> Option<V> {
44// `with_inspect` will only return an error on types with interior mutability.
45let value_with_inspect =
46 value.with_inspect(&self.node, &key).expect("Failed to attach new map entry");
47self.map.insert(key, value_with_inspect)
48 }
4950/// 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.
55pub 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.
62let value_with_inspect =
63 value.with_inspect(&self.node, property_name).expect("Failed to attach new map entry");
64self.map.insert(map_key, value_with_inspect)
65 }
6667/// Returns a mutable reference to the value at the given key, inserting a value if not present.
68pub fn get_or_insert_with(&mut self, key: String, value: impl FnOnce() -> V) -> &mut V {
69let node = &self.node;
70self.map.entry(key.clone()).or_insert_with(|| {
71// `with_inspect` will only return an error on types with interior mutability.
72value().with_inspect(node, &key).expect("Failed to attach new map entry")
73 })
74 }
7576/// Returns a mutable reference to the entry at `key`.
77pub fn get_mut(&mut self, key: &str) -> Option<&mut V> {
78self.map.get_mut(key)
79 }
8081/// Returns an immutable reference to the entry at `key`.
82pub fn get(&self, key: &str) -> Option<&V> {
83self.map.get(key)
84 }
8586/// Returns a reference to the inspect node associated with this map.
87pub fn inspect_node(&self) -> &Node {
88&self.node
89 }
90}
9192impl<V> Inspect for &mut ManagedInspectMap<V>
93where
94 for<'a> &'a mut V: Inspect,
95{
96fn iattach(self, parent: &Node, name: impl AsRef<str>) -> Result<(), AttachError> {
97self.node = parent.create_child(name.as_ref());
98Ok(())
99 }
100}
101102#[cfg(test)]
103mod tests {
104use crate::managed_inspect_map::ManagedInspectMap;
105use diagnostics_assertions::assert_data_tree;
106use fuchsia_inspect::{Inspector, Node};
107use fuchsia_inspect_derive::{IValue, Inspect, WithInspect};
108109#[derive(Default, Inspect)]
110struct TestInspectWrapper {
111 inspect_node: Node,
112pub test_map: ManagedInspectMap<IValue<String>>,
113 }
114115// Tests that inserting items into the map automatically records them in inspect.
116#[fuchsia::test]
117fn test_map_insert() {
118let inspector = Inspector::default();
119120let mut map = ManagedInspectMap::<IValue<String>>::with_node(
121 inspector.root().create_child("managed_node"),
122 );
123124let _ = map.insert("key1".to_string(), "value1".to_string().into());
125let _ = map.insert("key2".to_string(), "value2".to_string().into());
126127assert_data_tree!(inspector, root: {
128 managed_node: {
129"key1": "value1",
130"key2": "value2"
131}
132 });
133 }
134135// Tests that removing items from the map automatically removes them from inspect.
136#[fuchsia::test]
137fn test_map_remove() {
138let inspector = Inspector::default();
139140let mut map = ManagedInspectMap::<IValue<String>>::with_node(
141 inspector.root().create_child("managed_node"),
142 );
143144let _ = map.insert("key1".to_string(), "value1".to_string().into());
145let _ = map.insert("key2".to_string(), "value2".to_string().into());
146147let _ = map.map_mut().remove(&"key1".to_string());
148149assert_data_tree!(inspector, root: {
150 managed_node: {
151"key2": "value2"
152}
153 });
154 }
155156// Tests that inserting items with a different property name shows the intended property name in
157 // inspect.
158#[fuchsia::test]
159fn test_map_insert_with_property_name() {
160let inspector = Inspector::default();
161162let mut map = ManagedInspectMap::<IValue<String>>::with_node(
163 inspector.root().create_child("managed_node"),
164 );
165166let _ = map.insert_with_property_name(
167"key1".to_string(),
168"property_name_1".to_string(),
169"value1".to_string().into(),
170 );
171172// This will overwrite the previous insert due to using the same map key.
173let _ = map.insert_with_property_name(
174"key1".to_string(),
175"property_name_2".to_string(),
176"value2".to_string().into(),
177 );
178179assert_data_tree!(inspector, root: {
180 managed_node: {
181"property_name_2": "value2"
182}
183 });
184 }
185186// 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]
189fn test_map_derive_inspect() {
190let inspector = Inspector::default();
191192let mut wrapper = TestInspectWrapper::default()
193 .with_inspect(inspector.root(), "wrapper_node")
194 .expect("Failed to attach wrapper_node");
195196let _ = wrapper.test_map.insert("key1".to_string(), "value1".to_string().into());
197198// The map's node is named test_map since that's the field name in TestInspectWrapper.
199assert_data_tree!(inspector, root: {
200 wrapper_node: {
201 test_map: {
202"key1": "value1",
203 }
204 }
205 });
206 }
207}