confql_data_resolver/
values.rs1use itertools::Itertools;
2use serde_yaml::Value;
3use std::path::Path;
4
5use super::DataResolverError;
6
7pub fn take_sub_value_at_address(
8 value: &mut Value,
9 address: &[&str],
10) -> Result<Value, DataResolverError> {
11 use itertools::FoldWhile::{Continue, Done};
12 return address
13 .iter()
14 .fold_while(Ok(value), |acc, i| match acc.unwrap().get_mut(i) {
15 Some(v) => Continue(Ok(v)),
16 _ => Done(Err(DataResolverError::KeyNotFound(i.to_string()))),
17 })
18 .into_inner()
19 .map(|v| std::mem::replace(v, Value::Null));
20}
21
22pub fn value_from_file(path: &Path) -> Result<Value, DataResolverError> {
23 let file = std::fs::File::open(&path)?;
24 let value = serde_yaml::from_reader::<_, Value>(file)?;
25 Ok(value)
26}
27
28pub trait Merge {
33 fn merge(&mut self, mergee: Self) -> Result<&mut Self, DataResolverError>;
35 fn merge_at(&mut self, key: &str, mergee: Self) -> Result<&mut Self, DataResolverError>;
37 fn take(&mut self) -> Self;
39}
40
41macro_rules! merge_compat_err {
42 ($self:expr, $mergee:expr) => {
43 Err(DataResolverError::IncompatibleYamlMerge {
44 dst: $self.clone(),
45 src: $mergee,
46 })
47 };
48}
49
50impl Merge for serde_yaml::Value {
51 fn merge(&mut self, mut mergee: Self) -> Result<&mut Self, DataResolverError> {
52 use serde_yaml::Value::{Bool, Mapping, Null, Number, Sequence, String};
53 if let Null = mergee {
54 return Ok(self);
55 }
56 match self {
57 Null => {
58 *self = mergee;
59 }
60 Bool(_) => {
61 if !mergee.is_bool() {
62 return merge_compat_err! {self, mergee};
63 }
64 *self = mergee;
65 }
66 Number(_) => {
67 if !mergee.is_number() {
68 return merge_compat_err! {self, mergee};
69 }
70 *self = mergee;
71 }
72 String(_) => {
73 if !mergee.is_string() {
74 return merge_compat_err! {self, mergee};
75 }
76 *self = mergee;
77 }
78 Sequence(list) => {
79 if let Sequence(ref mut appendee) = mergee {
80 list.append(appendee);
81 } else {
82 return merge_compat_err! {self, mergee};
83 }
84 }
85 Mapping(mapping) => {
86 if let Mapping(superimposee) = mergee {
87 for (key, src) in superimposee {
88 if let Some(dst) = mapping.get_mut(&key) {
89 dst.merge(src)?;
90 } else {
91 mapping.insert(key, src);
92 }
93 }
94 } else {
95 return merge_compat_err! {self, mergee};
96 }
97 }
98 };
99 Ok(self)
100 }
101 fn merge_at(&mut self, key: &str, mergee: Self) -> Result<&mut Self, DataResolverError> {
102 match self {
103 Self::Mapping(mapping) => {
104 let key: Self = key.into();
105 match mapping.get_mut(&key) {
106 Some(value) => {
107 value.merge(mergee)?;
108 }
109 None => {
110 mapping.insert(key, mergee);
111 }
112 }
113 Ok(self)
114 }
115 Self::Null => {
116 let mut mapping = serde_yaml::Mapping::new();
117 mapping.insert(key.into(), mergee);
118 *self = Self::Mapping(mapping);
119 Ok(self)
120 }
121 _ => Err(DataResolverError::CannotMergeIntoNonMapping(self.clone())),
122 }
123 }
124 fn take(&mut self) -> Self {
127 std::mem::replace(self, serde_yaml::Value::Null)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use color_eyre::Result;
135 use indoc::indoc;
136 use test_files::TestFiles;
137 use test_utils::yaml;
138
139 #[test]
142 fn gets_value_from_file() -> Result<()> {
143 let filename = "index.yml";
144 let content = indoc! {"
145 ---
146 ok: true
147 go: home
148 "};
149 let mocks = TestFiles::new();
150 mocks.file(filename, content);
151 let file_path = mocks.path().join(filename);
152
153 let read_value = value_from_file(&file_path)?;
154
155 assert_eq!(serde_yaml::to_string(&read_value)?, content);
156 Ok(())
157 }
158
159 #[test]
160 fn takes_sub_value_at_address() -> Result<()> {
161 let mut value = yaml! {"
162 ---
163 my:
164 yaml:
165 is:
166 - hella
167 - deep
168 "};
169
170 let taken = take_sub_value_at_address(&mut value, &["my", "yaml"])?;
171
172 assert_eq!(
173 taken,
174 yaml! {"
175 ---
176 is:
177 - hella
178 - deep
179 "}
180 );
181
182 assert_eq!(
183 value,
184 yaml! {"
185 ---
186 my:
187 yaml:
188 "}
189 );
190
191 Ok(())
192 }
193}