qubit_config/source/
yaml_config_source.rs1use std::path::{Path, PathBuf};
31
32use serde_norway as yaml_backend;
33use yaml_backend::Value as YamlValue;
34
35use crate::{Config, ConfigError, ConfigResult};
36
37use super::ConfigSource;
38
39#[derive(Debug, Clone)]
57pub struct YamlConfigSource {
58 path: PathBuf,
59}
60
61impl YamlConfigSource {
62 #[inline]
68 pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
69 Self {
70 path: path.as_ref().to_path_buf(),
71 }
72 }
73}
74
75impl ConfigSource for YamlConfigSource {
76 fn load(&self, config: &mut Config) -> ConfigResult<()> {
77 let content = std::fs::read_to_string(&self.path).map_err(|e| {
78 ConfigError::IoError(std::io::Error::new(
79 e.kind(),
80 format!("Failed to read YAML file '{}': {}", self.path.display(), e),
81 ))
82 })?;
83
84 let value: YamlValue = yaml_backend::from_str(&content).map_err(|e| {
85 ConfigError::ParseError(format!(
86 "Failed to parse YAML file '{}': {}",
87 self.path.display(),
88 e
89 ))
90 })?;
91
92 let mut staged = config.clone();
93 flatten_yaml_value("", &value, &mut staged)?;
94 *config = staged;
95 Ok(())
96 }
97}
98
99pub(crate) fn flatten_yaml_value(
108 prefix: &str,
109 value: &YamlValue,
110 config: &mut Config,
111) -> ConfigResult<()> {
112 match value {
113 YamlValue::Mapping(map) => {
114 for (k, v) in map {
115 let key_str = yaml_key_to_string(k)?;
116 let key = if prefix.is_empty() {
117 key_str
118 } else {
119 format!("{}.{}", prefix, key_str)
120 };
121 flatten_yaml_value(&key, v, config)?;
122 }
123 }
124 YamlValue::Sequence(seq) => {
125 flatten_yaml_sequence(prefix, seq, config)?;
126 }
127 YamlValue::Null => {
128 use qubit_datatype::DataType;
130 config.set_null(prefix, DataType::String)?;
131 }
132 YamlValue::Bool(b) => {
133 config.set(prefix, *b)?;
134 }
135 YamlValue::Number(n) => {
136 if let Some(i) = n.as_i64() {
137 config.set(prefix, i)?;
138 } else {
139 let f = n
140 .as_f64()
141 .expect("YAML number should be representable as i64 or f64");
142 config.set(prefix, f)?;
143 }
144 }
145 YamlValue::String(s) => {
146 config.set(prefix, s.clone())?;
147 }
148 YamlValue::Tagged(tagged) => {
149 flatten_yaml_value(prefix, &tagged.value, config)?;
150 }
151 }
152 Ok(())
153}
154
155fn flatten_yaml_sequence(prefix: &str, seq: &[YamlValue], config: &mut Config) -> ConfigResult<()> {
165 if seq.is_empty() {
166 config.set(prefix, Vec::<String>::new())?;
167 return Ok(());
168 }
169
170 enum SeqKind {
171 Integer,
172 Float,
173 Bool,
174 String,
175 }
176
177 let kind = match &seq[0] {
178 YamlValue::Number(n) if n.is_i64() => SeqKind::Integer,
179 YamlValue::Number(_) => SeqKind::Float,
180 YamlValue::Bool(_) => SeqKind::Bool,
181 YamlValue::Mapping(_) | YamlValue::Sequence(_) | YamlValue::Tagged(_) => {
182 return Err(unsupported_yaml_sequence_element_error(prefix, &seq[0]));
183 }
184 _ => SeqKind::String,
185 };
186
187 let all_same = seq.iter().all(|item| match (&kind, item) {
188 (SeqKind::Integer, YamlValue::Number(n)) => n.is_i64(),
189 (SeqKind::Float, YamlValue::Number(_)) => true,
190 (SeqKind::Bool, YamlValue::Bool(_)) => true,
191 (SeqKind::String, YamlValue::String(_)) => true,
192 _ => false,
193 });
194
195 if !all_same {
196 let values = seq
197 .iter()
198 .map(|item| yaml_scalar_to_string(item, prefix))
199 .collect::<ConfigResult<Vec<_>>>()?;
200 config.set(prefix, values)?;
201 return Ok(());
202 }
203
204 match kind {
205 SeqKind::Integer => {
206 let values = seq
207 .iter()
208 .map(|item| {
209 item.as_i64()
210 .expect("YAML integer sequence was validated before insertion")
211 })
212 .collect::<Vec<_>>();
213 config.set(prefix, values)?;
214 }
215 SeqKind::Float => {
216 let values = seq
217 .iter()
218 .map(|item| {
219 item.as_f64()
220 .expect("YAML float sequence was validated before insertion")
221 })
222 .collect::<Vec<_>>();
223 config.set(prefix, values)?;
224 }
225 SeqKind::Bool => {
226 let values = seq
227 .iter()
228 .map(|item| {
229 item.as_bool()
230 .expect("YAML bool sequence was validated before insertion")
231 })
232 .collect::<Vec<_>>();
233 config.set(prefix, values)?;
234 }
235 SeqKind::String => {
236 let values = seq
237 .iter()
238 .map(|item| {
239 yaml_scalar_to_string(item, prefix)
240 .expect("YAML string sequence was validated before insertion")
241 })
242 .collect::<Vec<_>>();
243 config.set(prefix, values)?;
244 }
245 }
246
247 Ok(())
248}
249
250fn yaml_key_to_string(value: &YamlValue) -> ConfigResult<String> {
252 match value {
253 YamlValue::String(s) => Ok(s.clone()),
254 YamlValue::Number(n) => Ok(n.to_string()),
255 YamlValue::Bool(b) => Ok(b.to_string()),
256 YamlValue::Null => Ok("null".to_string()),
257 _ => Err(ConfigError::ParseError(format!(
258 "Unsupported YAML mapping key type: {value:?}"
259 ))),
260 }
261}
262
263fn yaml_scalar_to_string(value: &YamlValue, key: &str) -> ConfigResult<String> {
269 match value {
270 YamlValue::String(s) => Ok(s.clone()),
271 YamlValue::Number(n) => Ok(n.to_string()),
272 YamlValue::Bool(b) => Ok(b.to_string()),
273 YamlValue::Null => Ok(String::new()),
274 YamlValue::Sequence(_) | YamlValue::Mapping(_) | YamlValue::Tagged(_) => {
275 Err(unsupported_yaml_sequence_element_error(key, value))
276 }
277 }
278}
279
280fn unsupported_yaml_sequence_element_error(key: &str, value: &YamlValue) -> ConfigError {
282 let key = if key.is_empty() { "<root>" } else { key };
283 ConfigError::ParseError(format!(
284 "Unsupported nested YAML structure at key '{key}': {value:?}"
285 ))
286}