routing/bedrock/
structured_dict.rs1use crate::DictExt;
6use cm_types::{BorrowedName, IterablePath, Name};
7use runtime_capabilities::{Capability, Data, Dictionary};
8use std::fmt;
9use std::marker::PhantomData;
10use std::sync::LazyLock;
11
12trait StructuredDict: Into<Dictionary> + Default + Clone + fmt::Debug {
19 fn from_dict(dict: Dictionary) -> Self;
26}
27
28#[derive(Clone, Debug, Default)]
37#[allow(private_bounds)]
38pub struct StructuredDictMap<T: StructuredDict> {
39 inner: Dictionary,
40 phantom: PhantomData<T>,
41}
42
43impl<T: StructuredDict> StructuredDict for StructuredDictMap<T> {
44 fn from_dict(dict: Dictionary) -> Self {
45 Self { inner: dict, phantom: Default::default() }
46 }
47}
48
49#[allow(private_bounds)]
50impl<T: StructuredDict> StructuredDictMap<T> {
51 pub fn insert(&self, key: Name, value: T) -> Option<Capability> {
52 let dict: Dictionary = value.into();
53 self.inner.insert(key, dict.into())
54 }
55
56 pub fn get(&self, key: &BorrowedName) -> Option<T> {
57 self.inner.get(key).map(|cap| {
58 let Capability::Dictionary(dict) = cap else {
59 unreachable!("structured map entry must be a dict: {cap:?}");
60 };
61 T::from_dict(dict)
62 })
63 }
64
65 pub fn remove(&self, key: &Name) -> Option<T> {
66 self.inner.remove(&*key).map(|cap| {
67 let Capability::Dictionary(dict) = cap else {
68 unreachable!("structured map entry must be a dict: {cap:?}");
69 };
70 T::from_dict(dict)
71 })
72 }
73
74 pub fn append(&self, other: &Self) -> Result<(), ()> {
75 self.inner.append(&other.inner)
76 }
77
78 pub fn enumerate(&self) -> impl Iterator<Item = (Name, T)> {
79 self.inner.enumerate().map(|(key, capability_res)| match capability_res {
80 Capability::Dictionary(dict) => (key, T::from_dict(dict)),
81 cap => unreachable!("structured map entry must be a dict: {cap:?}"),
82 })
83 }
84}
85
86impl<T: StructuredDict> From<StructuredDictMap<T>> for Dictionary {
87 fn from(m: StructuredDictMap<T>) -> Self {
88 m.inner
89 }
90}
91
92static PARENT: LazyLock<Name> = LazyLock::new(|| "parent".parse().unwrap());
95
96static ENVIRONMENT: LazyLock<Name> = LazyLock::new(|| "environment".parse().unwrap());
98
99static DEBUG: LazyLock<Name> = LazyLock::new(|| "debug".parse().unwrap());
101
102static RUNNERS: LazyLock<Name> = LazyLock::new(|| "runners".parse().unwrap());
104
105static RESOLVERS: LazyLock<Name> = LazyLock::new(|| "resolvers".parse().unwrap());
107
108static FRAMEWORK: LazyLock<Name> = LazyLock::new(|| "framework".parse().unwrap());
110
111static STOP_TIMEOUT: LazyLock<Name> = LazyLock::new(|| "stop_timeout".parse().unwrap());
113
114static NAME: LazyLock<Name> = LazyLock::new(|| "name".parse().unwrap());
116
117#[derive(Clone, Debug)]
120pub struct ComponentInput(Dictionary);
121
122impl Default for ComponentInput {
123 fn default() -> Self {
124 Self::new(ComponentEnvironment::new())
125 }
126}
127
128impl StructuredDict for ComponentInput {
129 fn from_dict(dict: Dictionary) -> Self {
130 Self(dict)
131 }
132}
133
134impl ComponentInput {
135 pub fn new(environment: ComponentEnvironment) -> Self {
136 let dict = Dictionary::new();
137
138 if !environment.0.is_empty() {
139 dict.insert(ENVIRONMENT.clone(), Dictionary::from(environment).into());
140 }
141 Self(dict)
142 }
143
144 pub fn shallow_copy(&self) -> Self {
149 let dest = Dictionary::new();
153 shallow_copy(&self.0, &dest, &*PARENT);
154 shallow_copy(&self.0, &dest, &*ENVIRONMENT);
155 Self(dest)
156 }
157
158 pub fn capabilities(&self) -> Dictionary {
160 get_or_insert(&self.0, &*PARENT)
161 }
162
163 pub fn environment(&self) -> ComponentEnvironment {
165 ComponentEnvironment(get_or_insert(&self.0, &*ENVIRONMENT))
166 }
167
168 pub fn insert_capability(
169 &self,
170 path: &impl IterablePath,
171 capability: Capability,
172 ) -> Option<Capability> {
173 self.capabilities().insert_capability(path, capability.into())
174 }
175}
176
177impl From<ComponentInput> for Dictionary {
178 fn from(e: ComponentInput) -> Self {
179 e.0
180 }
181}
182
183#[derive(Clone, Debug)]
186pub struct ComponentEnvironment(Dictionary);
187
188impl Default for ComponentEnvironment {
189 fn default() -> Self {
190 Self(Dictionary::new())
191 }
192}
193
194impl StructuredDict for ComponentEnvironment {
195 fn from_dict(dict: Dictionary) -> Self {
196 Self(dict)
197 }
198}
199
200impl ComponentEnvironment {
201 pub fn new() -> Self {
202 Self::default()
203 }
204
205 pub fn debug(&self) -> Dictionary {
207 get_or_insert(&self.0, &*DEBUG)
208 }
209
210 pub fn runners(&self) -> Dictionary {
212 get_or_insert(&self.0, &*RUNNERS)
213 }
214
215 pub fn resolvers(&self) -> Dictionary {
217 get_or_insert(&self.0, &*RESOLVERS)
218 }
219
220 pub fn set_stop_timeout(&self, timeout: i64) {
222 let _ = self.0.insert(STOP_TIMEOUT.clone(), Data::Int64(timeout).into());
223 }
224
225 pub fn stop_timeout(&self) -> Option<i64> {
227 let Some(Capability::Data(Data::Int64(timeout))) = self.0.get(&*STOP_TIMEOUT) else {
228 return None;
229 };
230 Some(timeout)
231 }
232
233 pub fn set_name(&self, name: &Name) {
235 let _ = self.0.insert(NAME.clone(), Data::String(name.as_str().into()).into());
236 }
237
238 pub fn name(&self) -> Option<Name> {
240 let Some(Capability::Data(Data::String(name))) = self.0.get(&*NAME) else {
241 return None;
242 };
243 Some(Name::new(name).unwrap())
244 }
245
246 pub fn shallow_copy(&self) -> Self {
247 let dest = Dictionary::new();
251 shallow_copy(&self.0, &dest, &*DEBUG);
252 shallow_copy(&self.0, &dest, &*RUNNERS);
253 shallow_copy(&self.0, &dest, &*RESOLVERS);
254 Self(dest)
255 }
256}
257
258impl From<ComponentEnvironment> for Dictionary {
259 fn from(e: ComponentEnvironment) -> Self {
260 e.0
261 }
262}
263
264#[derive(Clone, Debug)]
268pub struct ComponentOutput(Dictionary);
269
270impl Default for ComponentOutput {
271 fn default() -> Self {
272 Self::new()
273 }
274}
275
276impl StructuredDict for ComponentOutput {
277 fn from_dict(dict: Dictionary) -> Self {
278 Self(dict)
279 }
280}
281
282impl ComponentOutput {
283 pub fn new() -> Self {
284 Self(Dictionary::new())
285 }
286
287 pub fn shallow_copy(&self) -> Self {
292 let dest = Dictionary::new();
296 shallow_copy(&self.0, &dest, &*PARENT);
297 shallow_copy(&self.0, &dest, &*FRAMEWORK);
298 Self(dest)
299 }
300
301 pub fn capabilities(&self) -> Dictionary {
304 get_or_insert(&self.0, &*PARENT)
305 }
306
307 pub fn framework(&self) -> Dictionary {
310 get_or_insert(&self.0, &*FRAMEWORK)
311 }
312}
313
314impl From<ComponentOutput> for Dictionary {
315 fn from(e: ComponentOutput) -> Self {
316 e.0
317 }
318}
319
320fn shallow_copy(src: &Dictionary, dest: &Dictionary, key: &Name) {
321 if let Some(d) = src.get(key) {
322 let Capability::Dictionary(d) = d else {
323 unreachable!("{key} entry must be a dictionary: {d:?}");
324 };
325 dest.insert(key.clone(), d.shallow_copy().into());
326 }
327}
328
329fn get_or_insert(this: &Dictionary, key: &Name) -> Dictionary {
330 let cap = this.get_or_insert(&key, || Capability::Dictionary(Dictionary::new()));
331 let Capability::Dictionary(dict) = cap else {
332 unreachable!("{key} entry must be a dict: {cap:?}");
333 };
334 dict
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use assert_matches::assert_matches;
341 use runtime_capabilities::DictKey;
342
343 impl StructuredDict for Dictionary {
344 fn from_dict(dict: Dictionary) -> Self {
345 dict
346 }
347 }
348
349 #[fuchsia::test]
350 async fn structured_dict_map() {
351 let dict1 = {
352 let dict = Dictionary::new();
353 dict.insert("a".parse().unwrap(), Dictionary::new().into());
354 dict
355 };
356 let dict2 = {
357 let dict = Dictionary::new();
358 dict.insert("b".parse().unwrap(), Dictionary::new().into());
359 dict
360 };
361 let dict2_alt = {
362 let dict = Dictionary::new();
363 dict.insert("c".parse().unwrap(), Dictionary::new().into());
364 dict
365 };
366 let name1 = Name::new("1").unwrap();
367 let name2 = Name::new("2").unwrap();
368
369 let map: StructuredDictMap<Dictionary> = Default::default();
370 assert_matches!(map.get(&name1), None);
371 assert!(map.insert(name1.clone(), dict1).is_none());
372 let d = map.get(&name1).unwrap();
373 let key = DictKey::new("a").unwrap();
374 assert_matches!(d.get(&key), Some(_));
375
376 assert!(map.insert(name2.clone(), dict2).is_none());
377 let d = map.remove(&name2).unwrap();
378 assert_matches!(map.remove(&name2), None);
379 let key = DictKey::new("b").unwrap();
380 assert_matches!(d.get(&key), Some(_));
381
382 assert!(map.insert(name2.clone(), dict2_alt).is_none());
383 let d = map.get(&name2).unwrap();
384 let key = DictKey::new("c").unwrap();
385 assert_matches!(d.get(&key), Some(_));
386 }
387}