confql_data_resolver/
data_path.rs1use std::ffi::OsStr;
21use std::fs;
22use std::path::{Path, PathBuf};
23
24use super::values::{take_sub_value_at_address, value_from_file};
25use super::DataResolverError;
26
27enum Level {
28 Dir,
29 File,
30}
31
32pub struct DataPath<'a> {
34 level: Level,
35 path: PathBuf,
36 address: &'a [&'a str],
37}
38
39impl<'a> DataPath<'a> {
40 pub fn descend(mut self) -> Option<Self> {
43 use Level::{Dir, File};
44 match &self.level {
45 File => {
46 if !self.path.is_dir() {
47 return None;
48 }
49 self.level = Dir;
50 }
51 Dir => {
52 if let Some((head, tail)) = self.address.split_first() {
53 self.path.push(head);
54 self.address = tail;
55 self.level = File;
56 }
57 }
58 }
59 Some(self)
60 }
61 pub fn done(&self) -> bool {
64 use Level::{Dir, File};
65 match &self.level {
66 File => false,
67 Dir => self.address.is_empty(),
68 }
69 }
70 fn file(&self) -> PathBuf {
71 self.path.with_extension("yml")
72 }
73 pub fn file_stem(&self) -> Option<&OsStr> {
75 self.path.file_stem()
76 }
77 fn get_value(&self, path: &Path) -> Result<serde_yaml::Value, DataResolverError> {
78 let mut value = value_from_file(path)?;
79 take_sub_value_at_address(&mut value, self.address)
80 }
81 fn index(&self) -> PathBuf {
82 self.path.join("index.yml")
83 }
84 pub fn join<P: AsRef<Path>>(&self, tail: P) -> Self {
86 Self {
87 level: Level::File,
88 path: self.path.join(tail),
89 address: self.address,
90 }
91 }
92 pub fn new<P: Into<PathBuf>>(path: P, address: &'a [&'a str]) -> Self {
94 Self {
95 address,
96 level: Level::Dir,
97 path: path.into(),
98 }
99 }
100 pub fn sub_paths(&self) -> Vec<Self> {
102 fs::read_dir(&self.path).map_or_else(
103 |_| vec![],
104 |reader| {
105 reader
106 .filter_map(|dir_entry| dir_entry.ok())
107 .map(|dir_entry| dir_entry.file_name())
108 .map(|p| self.join(p))
109 .collect()
110 },
111 )
112 }
113 pub fn value(&self) -> Result<serde_yaml::Value, DataResolverError> {
115 match &self.level {
116 Level::Dir => self.get_value(&self.index()),
117 Level::File => self.get_value(&self.file()),
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use color_eyre::Result;
126 use indoc::indoc;
127 use test_files::TestFiles;
128 use test_utils::yaml;
129
130 trait GetDataPath<'a> {
131 fn data_path(&self, address: &'a [&'a str]) -> DataPath<'a>;
132 }
133
134 impl<'a> GetDataPath<'a> for TestFiles {
135 fn data_path(&self, address: &'a [&'a str]) -> DataPath<'a> {
136 DataPath::new(self.path(), address)
137 }
138 }
139
140 #[test]
141 fn resolves_num_at_root() -> Result<()> {
142 let mocks = TestFiles::new();
143 mocks.file(
144 "index.yml",
145 indoc! {"
146 ---
147 3
148 "},
149 );
150 let v = mocks.data_path(&[]).value()?;
151 assert_eq!(v, yaml! {"3"});
152 Ok(())
153 }
154
155 #[test]
156 fn resolves_num_deeper() -> Result<()> {
157 let mocks = TestFiles::new();
158 mocks.file(
159 "index.yml",
160 indoc! {"
161 ---
162 a:
163 b:
164 c: 3
165 "},
166 );
167 let v = mocks.data_path(&["a", "b", "c"]).value()?;
168 assert_eq!(v, yaml! {"3"});
169 Ok(())
170 }
171
172 #[test]
173 fn resolves_list_num_at_index() -> Result<()> {
174 let mocks = TestFiles::new();
175 mocks.file(
176 "index.yml",
177 indoc! {"
178 ---
179 a:
180 - 4
181 - 5
182 - 6
183 "},
184 );
185 let v = mocks.data_path(&["a"]).value()?;
186 assert_eq!(v, yaml! {"[4, 5, 6]"});
187 Ok(())
188 }
189}