1use std::collections::HashSet;
31use std::path::{
32 Path,
33 PathBuf,
34};
35
36use serde_norway as yaml_backend;
37use yaml_backend::Value as YamlValue;
38
39use crate::{
40 Config,
41 ConfigError,
42 ConfigResult,
43 utils,
44};
45
46use super::{
47 ConfigSource,
48 config_source::load_transactionally,
49};
50
51#[derive(Debug, Clone)]
69pub struct YamlConfigSource {
70 path: PathBuf,
71}
72
73impl YamlConfigSource {
74 #[inline]
80 pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
81 Self {
82 path: path.as_ref().to_path_buf(),
83 }
84 }
85}
86
87impl ConfigSource for YamlConfigSource {
88 fn load(&self, config: &mut Config) -> ConfigResult<()> {
89 load_transactionally(self, config)
90 }
91
92 fn load_into(&self, config: &mut Config) -> ConfigResult<()> {
93 let content = std::fs::read_to_string(&self.path).map_err(|e| {
94 ConfigError::IoError(std::io::Error::new(
95 e.kind(),
96 format!("Failed to read YAML file '{}': {}", self.path.display(), e),
97 ))
98 })?;
99
100 let value: YamlValue = yaml_backend::from_str(&content).map_err(|e| {
101 ConfigError::ParseError(format!("Failed to parse YAML file '{}': {}", self.path.display(), e))
102 })?;
103
104 let mut seen = HashSet::new();
105 flatten_yaml_value("", &value, config, &mut seen)?;
106 Ok(())
107 }
108}
109
110pub(crate) fn flatten_yaml_value(
119 prefix: &str,
120 value: &YamlValue,
121 config: &mut Config,
122 seen: &mut HashSet<String>,
123) -> ConfigResult<()> {
124 match value {
125 YamlValue::Mapping(map) => {
126 for (k, v) in map {
127 let key_str = yaml_key_to_string(k)?;
128 let key = if prefix.is_empty() {
129 key_str
130 } else {
131 format!("{}.{}", prefix, key_str)
132 };
133 flatten_yaml_value(&key, v, config, seen)?;
134 }
135 }
136 YamlValue::Sequence(seq) => {
137 utils::ensure_unique_flattened_key(seen, prefix)?;
138 flatten_yaml_sequence(prefix, seq, config)?;
139 }
140 YamlValue::Null => {
141 use qubit_datatype::DataType;
143 utils::ensure_unique_flattened_key(seen, prefix)?;
144 config.set_null(prefix, DataType::String)?;
145 }
146 YamlValue::Bool(b) => {
147 utils::ensure_unique_flattened_key(seen, prefix)?;
148 config.set(prefix, *b)?;
149 }
150 YamlValue::Number(n) => {
151 utils::ensure_unique_flattened_key(seen, prefix)?;
152 if let Some(i) = n.as_i64() {
153 config.set(prefix, i)?;
154 } else {
155 let f = n.as_f64().expect("YAML number should be representable as i64 or f64");
156 config.set(prefix, f)?;
157 }
158 }
159 YamlValue::String(s) => {
160 utils::ensure_unique_flattened_key(seen, prefix)?;
161 config.set(prefix, s.clone())?;
162 }
163 YamlValue::Tagged(tagged) => {
164 flatten_yaml_value(prefix, &tagged.value, config, seen)?;
165 }
166 }
167 Ok(())
168}
169
170fn flatten_yaml_sequence(prefix: &str, seq: &[YamlValue], config: &mut Config) -> ConfigResult<()> {
180 if seq.is_empty() {
181 config.set(prefix, Vec::<String>::new())?;
182 return Ok(());
183 }
184
185 enum SeqKind {
186 Integer,
187 Float,
188 Bool,
189 String,
190 }
191
192 let kind = match &seq[0] {
193 YamlValue::Number(n) if n.is_i64() => SeqKind::Integer,
194 YamlValue::Number(_) => SeqKind::Float,
195 YamlValue::Bool(_) => SeqKind::Bool,
196 YamlValue::Mapping(_) | YamlValue::Sequence(_) | YamlValue::Tagged(_) => {
197 return Err(unsupported_yaml_sequence_element_error(prefix, &seq[0]));
198 }
199 _ => SeqKind::String,
200 };
201
202 let all_same = seq.iter().all(|item| match (&kind, item) {
203 (SeqKind::Integer, YamlValue::Number(n)) => n.is_i64(),
204 (SeqKind::Float, YamlValue::Number(_)) => true,
205 (SeqKind::Bool, YamlValue::Bool(_)) => true,
206 (SeqKind::String, YamlValue::String(_)) => true,
207 _ => false,
208 });
209
210 if !all_same {
211 let values = seq
212 .iter()
213 .map(|item| yaml_scalar_to_string(item, prefix))
214 .collect::<ConfigResult<Vec<_>>>()?;
215 config.set(prefix, values)?;
216 return Ok(());
217 }
218
219 match kind {
220 SeqKind::Integer => {
221 let values = seq
222 .iter()
223 .map(|item| {
224 item.as_i64()
225 .expect("YAML integer sequence was validated before insertion")
226 })
227 .collect::<Vec<_>>();
228 config.set(prefix, values)?;
229 }
230 SeqKind::Float => {
231 let values = seq
232 .iter()
233 .map(|item| {
234 item.as_f64()
235 .expect("YAML float sequence was validated before insertion")
236 })
237 .collect::<Vec<_>>();
238 config.set(prefix, values)?;
239 }
240 SeqKind::Bool => {
241 let values = seq
242 .iter()
243 .map(|item| {
244 item.as_bool()
245 .expect("YAML bool sequence was validated before insertion")
246 })
247 .collect::<Vec<_>>();
248 config.set(prefix, values)?;
249 }
250 SeqKind::String => {
251 let values = seq
252 .iter()
253 .map(|item| {
254 yaml_scalar_to_string(item, prefix).expect("YAML string sequence was validated before insertion")
255 })
256 .collect::<Vec<_>>();
257 config.set(prefix, values)?;
258 }
259 }
260
261 Ok(())
262}
263
264fn yaml_key_to_string(value: &YamlValue) -> ConfigResult<String> {
266 match value {
267 YamlValue::String(s) => Ok(s.clone()),
268 YamlValue::Number(n) => Ok(n.to_string()),
269 YamlValue::Bool(b) => Ok(b.to_string()),
270 YamlValue::Null => Ok("null".to_string()),
271 _ => Err(ConfigError::ParseError(format!(
272 "Unsupported YAML mapping key type: {value:?}"
273 ))),
274 }
275}
276
277fn yaml_scalar_to_string(value: &YamlValue, key: &str) -> ConfigResult<String> {
283 match value {
284 YamlValue::String(s) => Ok(s.clone()),
285 YamlValue::Number(n) => Ok(n.to_string()),
286 YamlValue::Bool(b) => Ok(b.to_string()),
287 YamlValue::Null => Ok(String::new()),
288 YamlValue::Sequence(_) | YamlValue::Mapping(_) | YamlValue::Tagged(_) => {
289 Err(unsupported_yaml_sequence_element_error(key, value))
290 }
291 }
292}
293
294fn unsupported_yaml_sequence_element_error(key: &str, value: &YamlValue) -> ConfigError {
296 let key = if key.is_empty() { "<root>" } else { key };
297 ConfigError::ParseError(format!("Unsupported nested YAML structure at key '{key}': {value:?}"))
298}