yaml_edit/mapping_view.rs
1//! Read-only mapping interface shared by [`Mapping`] and
2//! [`MergedMapping`](crate::anchor_resolution::MergedMapping).
3//!
4//! [`MappingView`] abstracts over "anything that looks like a YAML mapping
5//! for the purposes of reading it." It lets you write code that works
6//! generically against both the underlying CST [`Mapping`] and the
7//! alias/merge-key-resolving [`MergedMapping`](crate::anchor_resolution::MergedMapping)
8//! view.
9//!
10//! Iterators are returned as boxed trait objects so the trait stays
11//! object-safe and works on the crate's MSRV. Most uses pay one heap
12//! allocation per iteration - negligible for an editing library.
13//!
14//! # Example
15//!
16//! ```
17//! use yaml_edit::{Document, MappingView};
18//! use yaml_edit::anchor_resolution::{DocumentMergedExt, MappingMergedExt};
19//! use std::str::FromStr;
20//!
21//! fn count_keys(view: &impl MappingView) -> usize {
22//! view.keys().count()
23//! }
24//!
25//! let yaml = "\
26//! defaults: &d
27//! timeout: 30
28//! prod:
29//! <<: *d
30//! host: prod.example.com
31//! ";
32//! let doc = Document::from_str(yaml).unwrap();
33//! let root = doc.as_mapping().unwrap();
34//!
35//! // Works on a plain Mapping…
36//! assert_eq!(count_keys(&root), 2);
37//!
38//! // …and on a MergedMapping.
39//! let registry = doc.merged().unwrap().unwrap();
40//! let prod = registry.as_mapping().get_merged("prod").unwrap().unwrap();
41//! assert_eq!(count_keys(&prod), 2); // timeout + host
42//! ```
43//!
44//! The trait is intentionally read-only: mutations only make sense on the
45//! CST [`Mapping`], so [`set`](crate::yaml::Mapping::set),
46//! [`remove`](crate::yaml::Mapping::remove), etc. remain on that type.
47//!
48//! [`Mapping`]: crate::yaml::Mapping
49
50use crate::as_yaml::YamlNode;
51
52/// A read-only "view" of a YAML mapping, implemented by both the CST
53/// [`Mapping`](crate::yaml::Mapping) and the alias/merge-key-resolving
54/// [`MergedMapping`](crate::anchor_resolution::MergedMapping).
55///
56/// See the [module docs](self) for an overview and example.
57pub trait MappingView {
58 /// Look up the value associated with `key`.
59 ///
60 /// Key matching is semantic (quoting style is ignored), so `"foo"`,
61 /// `'foo'`, and `foo` all match the scalar `foo`.
62 fn get(&self, key: &dyn crate::AsYaml) -> Option<YamlNode>;
63
64 /// Returns `true` if [`get`](Self::get) would return `Some` for `key`.
65 fn contains_key(&self, key: &dyn crate::AsYaml) -> bool {
66 self.get(key).is_some()
67 }
68
69 /// Number of entries visible through this view.
70 fn len(&self) -> usize {
71 self.iter().count()
72 }
73
74 /// Returns `true` if the view has no entries.
75 fn is_empty(&self) -> bool {
76 self.iter().next().is_none()
77 }
78
79 /// Iterate over the keys.
80 fn keys<'a>(&'a self) -> Box<dyn Iterator<Item = YamlNode> + 'a> {
81 Box::new(self.iter().map(|(k, _)| k))
82 }
83
84 /// Iterate over the values.
85 fn values<'a>(&'a self) -> Box<dyn Iterator<Item = YamlNode> + 'a> {
86 Box::new(self.iter().map(|(_, v)| v))
87 }
88
89 /// Iterate over `(key, value)` pairs.
90 fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (YamlNode, YamlNode)> + 'a>;
91}
92
93impl MappingView for crate::yaml::Mapping {
94 fn get(&self, key: &dyn crate::AsYaml) -> Option<YamlNode> {
95 crate::yaml::Mapping::get(self, key)
96 }
97
98 fn contains_key(&self, key: &dyn crate::AsYaml) -> bool {
99 crate::yaml::Mapping::contains_key(self, key)
100 }
101
102 fn len(&self) -> usize {
103 crate::yaml::Mapping::len(self)
104 }
105
106 fn is_empty(&self) -> bool {
107 crate::yaml::Mapping::is_empty(self)
108 }
109
110 fn keys<'a>(&'a self) -> Box<dyn Iterator<Item = YamlNode> + 'a> {
111 Box::new(crate::yaml::Mapping::keys(self))
112 }
113
114 fn values<'a>(&'a self) -> Box<dyn Iterator<Item = YamlNode> + 'a> {
115 Box::new(crate::yaml::Mapping::values(self))
116 }
117
118 fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (YamlNode, YamlNode)> + 'a> {
119 Box::new(crate::yaml::Mapping::iter(self))
120 }
121}
122
123impl MappingView for crate::anchor_resolution::MergedMapping<'_> {
124 fn get(&self, key: &dyn crate::AsYaml) -> Option<YamlNode> {
125 crate::anchor_resolution::MergedMapping::get(self, key)
126 }
127
128 fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (YamlNode, YamlNode)> + 'a> {
129 Box::new(crate::anchor_resolution::MergedMapping::iter(self))
130 }
131}