Skip to main content

bare_config/
yaml.rs

1//! YAML format support for configuration content.
2//!
3//! This module provides the `YamlContent` type for working
4//! with YAML configuration files.
5//!
6//! # Features
7//!
8//! - Full CRUD operations (select, insert, update, delete, upsert)
9//! - Pretty-printed output using `serde_yaml`
10//! - Consistent round-trip serialization
11//!
12//! # Example
13//!
14//! ```
15//! use bare_config::yaml::YamlContent;
16//! use bare_config::{ConfigContent, key::Key};
17//! use bare_config::value::Value;
18//! use std::str::FromStr;
19//!
20//! // Parse YAML configuration
21//! let config = YamlContent::from_str(r#"
22//! database:
23//!   url: localhost
24//! "#).unwrap();
25//!
26//! // Select a value
27//! let value = config.select(&Key::from_str(".database.url").unwrap()).unwrap();
28//! assert_eq!(value.as_str(), Some("localhost"));
29//!
30//! // Insert a new value
31//! let mut config = YamlContent::from_str("{}").unwrap();
32//! config.insert(&Key::from_str(".server.port").unwrap(), &Value::string("8080")).unwrap();
33//!
34//! // Format as string (pretty-printed)
35//! let output = config.to_string();
36//! println!("{}", output);
37//! ```
38
39use core::fmt;
40use core::str::FromStr;
41
42use crate::ConfigContent;
43use crate::error::{ConfigError, ConfigResult};
44use crate::key::{Key, KeySegment};
45use crate::value::Value;
46use serde_yaml::Value as YamlValue;
47
48/// YAML configuration content.
49///
50/// This type provides CRUD operations for YAML configuration data.
51#[derive(Debug, Clone)]
52pub struct YamlContent {
53    data: YamlValue,
54}
55
56impl PartialEq for YamlContent {
57    fn eq(&self, other: &Self) -> bool {
58        // Compare internal data directly for efficiency and correctness
59        self.data == other.data
60    }
61}
62
63#[allow(dead_code)]
64impl YamlContent {
65    /// Creates a new YamlContent from a YAML value.
66    pub fn from_value(value: YamlValue) -> Self {
67        Self { data: value }
68    }
69
70    /// Returns a reference to the underlying YAML value.
71    pub fn as_value(&self) -> &YamlValue {
72        &self.data
73    }
74
75    /// Converts to the underlying YAML value.
76    pub fn into_value(self) -> YamlValue {
77        self.data
78    }
79
80    fn get_value_at_key(&self, key: &Key) -> ConfigResult<&YamlValue> {
81        let mut current = &self.data;
82
83        for segment in key.segments() {
84            match segment {
85                KeySegment::Key(s) => {
86                    current = match current {
87                        YamlValue::Mapping(m) => {
88                            let yaml_key = YamlValue::String(s.clone());
89                            m.get(&yaml_key)
90                                .ok_or_else(|| ConfigError::KeyNotFound(Key::to_key_string(key)))?
91                        }
92                        _ => {
93                            return Err(ConfigError::KeyNotFound(Key::to_key_string(key)));
94                        }
95                    };
96                }
97                KeySegment::Index(i) => {
98                    current = match current {
99                        YamlValue::Sequence(seq) => seq
100                            .get(*i)
101                            .ok_or_else(|| ConfigError::KeyNotFound(Key::to_key_string(key)))?,
102                        _ => {
103                            return Err(ConfigError::KeyNotFound(Key::to_key_string(key)));
104                        }
105                    };
106                }
107                KeySegment::Attribute(_) => {
108                    return Err(ConfigError::InvalidKey(
109                        "YAML does not support attributes".to_string(),
110                    ));
111                }
112            }
113        }
114
115        Ok(current)
116    }
117
118    fn contains_key_at(&self, key: &Key) -> bool {
119        self.get_value_at_key(key).is_ok()
120    }
121
122    fn keys_at(&self) -> Vec<Key> {
123        match &self.data {
124            YamlValue::Mapping(m) => m
125                .keys()
126                .filter_map(|k| {
127                    if let YamlValue::String(s) = k {
128                        Key::from_str(&format!(".{}", s)).ok()
129                    } else {
130                        None
131                    }
132                })
133                .collect(),
134            YamlValue::Sequence(seq) => (0..seq.len())
135                .filter_map(|i| Key::from_str(&format!(".[{i}]")).ok())
136                .collect(),
137            _ => vec![],
138        }
139    }
140
141    fn len_at(&self) -> usize {
142        match &self.data {
143            YamlValue::Mapping(m) => m.len(),
144            YamlValue::Sequence(seq) => seq.len(),
145            _ => 0,
146        }
147    }
148
149    fn delete_at(&mut self, key: &Key) -> ConfigResult<()> {
150        if key.segments().is_empty() {
151            return Err(ConfigError::DeleteError("Cannot delete root".to_string()));
152        }
153
154        let segments = key.segments();
155        let last_idx = segments.len() - 1;
156
157        let mut current = &mut self.data;
158
159        for (i, segment) in segments.iter().enumerate() {
160            if i == last_idx {
161                match segment {
162                    KeySegment::Key(s) => {
163                        if let YamlValue::Mapping(m) = current {
164                            let yaml_key = YamlValue::String(s.clone());
165                            drop(m.remove(&yaml_key));
166                            return Ok(());
167                        }
168                    }
169                    KeySegment::Index(idx) => {
170                        if let YamlValue::Sequence(seq) = current {
171                            if *idx < seq.len() {
172                                drop(seq.remove(*idx));
173                                return Ok(());
174                            }
175                        }
176                    }
177                    KeySegment::Attribute(_) => {
178                        return Err(ConfigError::InvalidKey(
179                            "YAML does not support attributes".to_string(),
180                        ));
181                    }
182                }
183            } else {
184                match segment {
185                    KeySegment::Key(s) => {
186                        if let YamlValue::Mapping(m) = current {
187                            let yaml_key = YamlValue::String(s.clone());
188                            if let Some(next) = m.get_mut(&yaml_key) {
189                                current = next;
190                            } else {
191                                return Err(ConfigError::KeyNotFound(key.to_key_string()));
192                            }
193                        } else {
194                            return Err(ConfigError::KeyNotFound(key.to_key_string()));
195                        }
196                    }
197                    KeySegment::Index(idx) => {
198                        if let YamlValue::Sequence(seq) = current {
199                            if let Some(next) = seq.get_mut(*idx) {
200                                current = next;
201                            } else {
202                                return Err(ConfigError::KeyNotFound(key.to_key_string()));
203                            }
204                        } else {
205                            return Err(ConfigError::KeyNotFound(key.to_key_string()));
206                        }
207                    }
208                    KeySegment::Attribute(_) => {
209                        return Err(ConfigError::InvalidKey(
210                            "YAML does not support attributes".to_string(),
211                        ));
212                    }
213                }
214            }
215        }
216
217        Err(ConfigError::KeyNotFound(key.to_key_string()))
218    }
219
220    fn upsert_at(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
221        if key.segments().is_empty() {
222            self.data = yaml_value_from_value(value);
223            return Ok(());
224        }
225
226        let segments = key.segments();
227        let last_idx = segments.len() - 1;
228
229        let mut current = &mut self.data;
230
231        for (i, segment) in segments.iter().enumerate() {
232            if i == last_idx {
233                match segment {
234                    KeySegment::Key(s) => {
235                        if let YamlValue::Mapping(m) = current {
236                            drop(m.insert(
237                                YamlValue::String(s.clone()),
238                                yaml_value_from_value(value),
239                            ));
240                            return Ok(());
241                        }
242                    }
243                    KeySegment::Index(idx) => {
244                        if let YamlValue::Sequence(seq) = current {
245                            if *idx < seq.len() {
246                                seq[*idx] = yaml_value_from_value(value);
247                            } else if *idx == seq.len() {
248                                seq.push(yaml_value_from_value(value));
249                            } else {
250                                return Err(ConfigError::KeyNotFound(key.to_key_string()));
251                            }
252                            return Ok(());
253                        }
254                    }
255                    KeySegment::Attribute(_) => {
256                        return Err(ConfigError::InvalidKey(
257                            "YAML does not support attributes".to_string(),
258                        ));
259                    }
260                }
261            } else {
262                match segment {
263                    KeySegment::Key(s) => {
264                        let next = match current {
265                            YamlValue::Mapping(m) => {
266                                let yaml_key = YamlValue::String(s.clone());
267                                let key_exists = m.contains_key(&yaml_key);
268                                if !key_exists {
269                                    drop(m.insert(
270                                        yaml_key.clone(),
271                                        YamlValue::Mapping(serde_yaml::Mapping::new()),
272                                    ));
273                                }
274                                m.get_mut(&yaml_key).ok_or_else(|| {
275                                    ConfigError::KeyNotFound(Key::to_key_string(key))
276                                })?
277                            }
278                            _ => {
279                                return Err(ConfigError::KeyNotFound(Key::to_key_string(key)));
280                            }
281                        };
282                        current = next;
283                    }
284                    KeySegment::Index(idx) => {
285                        let next = match current {
286                            YamlValue::Sequence(seq) => {
287                                if *idx < seq.len() {
288                                    &mut seq[*idx]
289                                } else if *idx == seq.len() {
290                                    seq.push(YamlValue::Null);
291                                    seq.last_mut().ok_or_else(|| {
292                                        ConfigError::KeyNotFound(Key::to_key_string(key))
293                                    })?
294                                } else {
295                                    return Err(ConfigError::KeyNotFound(Key::to_key_string(key)));
296                                }
297                            }
298                            _ => {
299                                return Err(ConfigError::KeyNotFound(Key::to_key_string(key)));
300                            }
301                        };
302                        current = next;
303                    }
304                    KeySegment::Attribute(_) => {
305                        return Err(ConfigError::InvalidKey(
306                            "YAML does not support attributes".to_string(),
307                        ));
308                    }
309                }
310            }
311        }
312
313        Ok(())
314    }
315}
316
317impl ConfigContent for YamlContent {
318    fn select(&self, key: &Key) -> ConfigResult<Value> {
319        let yaml_value = self.get_value_at_key(key)?;
320        Ok(value_from_yaml_value(yaml_value))
321    }
322
323    fn insert(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
324        if self.contains_key_at(key) {
325            return Err(ConfigError::KeyAlreadyExists(key.to_key_string()));
326        }
327        self.upsert_at(key, value)
328    }
329
330    fn update(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
331        if !self.contains_key_at(key) {
332            return Err(ConfigError::KeyDoesNotExist(key.to_key_string()));
333        }
334        self.upsert_at(key, value)
335    }
336
337    fn delete(&mut self, key: &Key) -> ConfigResult<()> {
338        self.delete_at(key)
339    }
340
341    fn upsert(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
342        self.upsert_at(key, value)
343    }
344}
345
346impl FromStr for YamlContent {
347    type Err = ConfigError;
348
349    fn from_str(s: &str) -> Result<Self, Self::Err> {
350        let value = serde_yaml::from_str(s).map_err(|e| ConfigError::ParseError(e.to_string()))?;
351        Ok(Self { data: value })
352    }
353}
354
355impl fmt::Display for YamlContent {
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357        let output = serde_yaml::to_string(&self.data).map_err(|_| fmt::Error)?;
358        write!(f, "{}", output)
359    }
360}
361
362fn value_from_yaml_value(yaml: &YamlValue) -> Value {
363    match yaml {
364        YamlValue::Null => Value::Null,
365        YamlValue::Bool(b) => Value::Bool(*b),
366        YamlValue::Number(n) => {
367            if let Some(i) = n.as_i64() {
368                Value::Integer(i)
369            } else if let Some(f) = n.as_f64() {
370                Value::Float(f)
371            } else {
372                Value::Null
373            }
374        }
375        YamlValue::String(s) => Value::String(s.clone()),
376        YamlValue::Sequence(seq) => Value::Array(seq.iter().map(value_from_yaml_value).collect()),
377        YamlValue::Mapping(m) => Value::Map(
378            m.iter()
379                .filter_map(|(k, v)| {
380                    if let YamlValue::String(s) = k {
381                        Some((s.clone(), value_from_yaml_value(v)))
382                    } else {
383                        None
384                    }
385                })
386                .collect(),
387        ),
388        YamlValue::Tagged(tagged) => value_from_yaml_value(&tagged.value),
389    }
390}
391
392fn yaml_value_from_value(value: &Value) -> YamlValue {
393    match value {
394        Value::Null => YamlValue::Null,
395        Value::Bool(b) => YamlValue::Bool(*b),
396        Value::Integer(i) => YamlValue::Number(serde_yaml::Number::from(*i)),
397        Value::Float(f) => {
398            // Handle special float values - YAML supports NaN and Infinity natively
399            if f.is_nan() {
400                YamlValue::String(".nan".to_string())
401            } else if f.is_infinite() {
402                if f.is_sign_positive() {
403                    YamlValue::String(".inf".to_string())
404                } else {
405                    YamlValue::String("-.inf".to_string())
406                }
407            } else {
408                YamlValue::Number(serde_yaml::Number::from(*f))
409            }
410        }
411        Value::String(s) => YamlValue::String(s.clone()),
412        Value::Array(arr) => YamlValue::Sequence(arr.iter().map(yaml_value_from_value).collect()),
413        Value::Map(m) => {
414            let mut mapping = serde_yaml::Mapping::new();
415            for (k, v) in m {
416                drop(mapping.insert(YamlValue::String(k.clone()), yaml_value_from_value(v)));
417            }
418            YamlValue::Mapping(mapping)
419        }
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    #[test]
428    fn test_yaml_display_is_stable_and_pretty() {
429        let content = YamlContent::from_str("b: 2\na:\n  x: 1").expect("test should succeed");
430        let first = content.to_string();
431        let second = content.to_string();
432        assert_eq!(first, second);
433        assert!(first.contains("\n"));
434    }
435
436    #[test]
437    fn test_yaml_from_str() {
438        let content = YamlContent::from_str("key: value").expect("test should succeed");
439        assert!(
440            content
441                .select(&Key::from_str(".key").expect("test should succeed"))
442                .is_ok()
443        );
444    }
445
446    #[test]
447    fn test_yaml_select() {
448        let content = YamlContent::from_str("name: test").expect("test should succeed");
449        let value = content
450            .select(&Key::from_str(".name").expect("test should succeed"))
451            .expect("test should succeed");
452        assert_eq!(value.as_str(), Some("test"));
453    }
454
455    #[test]
456    fn test_yaml_insert() {
457        let mut content = YamlContent::from_str("{}").expect("test should succeed");
458        content
459            .insert(
460                &Key::from_str(".name").expect("test should succeed"),
461                &Value::string("test"),
462            )
463            .expect("test should succeed");
464        let value = content
465            .select(&Key::from_str(".name").expect("test should succeed"))
466            .expect("test should succeed");
467        assert_eq!(value.as_str(), Some("test"));
468    }
469
470    #[test]
471    fn test_yaml_update() {
472        let mut content = YamlContent::from_str("name: old").expect("test should succeed");
473        content
474            .update(
475                &Key::from_str(".name").expect("test should succeed"),
476                &Value::string("new"),
477            )
478            .expect("test should succeed");
479        let value = content
480            .select(&Key::from_str(".name").expect("test should succeed"))
481            .expect("test should succeed");
482        assert_eq!(value.as_str(), Some("new"));
483    }
484
485    #[test]
486    fn test_yaml_delete() {
487        let mut content = YamlContent::from_str("name: test").expect("test should succeed");
488        content
489            .delete(&Key::from_str(".name").expect("test should succeed"))
490            .expect("test should succeed");
491        assert!(
492            content
493                .select(&Key::from_str(".name").expect("test should succeed"))
494                .is_err()
495        );
496    }
497
498    #[test]
499    fn test_yaml_upsert() {
500        let mut content = YamlContent::from_str("{}").expect("test should succeed");
501        content
502            .upsert(
503                &Key::from_str(".name").expect("test should succeed"),
504                &Value::string("test"),
505            )
506            .expect("test should succeed");
507        let value = content
508            .select(&Key::from_str(".name").expect("test should succeed"))
509            .expect("test should succeed");
510        assert_eq!(value.as_str(), Some("test"));
511    }
512
513    #[test]
514    fn test_yaml_nested() {
515        let content =
516            YamlContent::from_str("database:\n  url: localhost").expect("test should succeed");
517        let value = content
518            .select(&Key::from_str(".database.url").expect("test should succeed"))
519            .expect("test should succeed");
520        assert_eq!(value.as_str(), Some("localhost"));
521    }
522
523    #[test]
524    fn test_yaml_array() {
525        let content =
526            YamlContent::from_str("items:\n  - 1\n  - 2\n  - 3").expect("test should succeed");
527        let value = content
528            .select(&Key::from_str(".items[0]").expect("test should succeed"))
529            .expect("test should succeed");
530        assert_eq!(value.as_integer(), Some(1));
531    }
532
533    #[test]
534    fn test_yaml_string() {
535        let content = YamlContent::from_str("text: hello").expect("test should succeed");
536        let value = content
537            .select(&Key::from_str(".text").expect("test should succeed"))
538            .expect("test should succeed");
539        assert_eq!(value.as_str(), Some("hello"));
540    }
541
542    #[test]
543    fn test_yaml_integer() {
544        let content = YamlContent::from_str("num: 42").expect("test should succeed");
545        let value = content
546            .select(&Key::from_str(".num").expect("test should succeed"))
547            .expect("test should succeed");
548        assert_eq!(value.as_integer(), Some(42));
549    }
550
551    #[test]
552    fn test_yaml_negative_integer() {
553        let content = YamlContent::from_str("num: -17").expect("test should succeed");
554        let value = content
555            .select(&Key::from_str(".num").expect("test should succeed"))
556            .expect("test should succeed");
557        assert_eq!(value.as_integer(), Some(-17));
558    }
559
560    #[test]
561    fn test_yaml_float() {
562        let content = YamlContent::from_str("num: 3.14").expect("test should succeed");
563        let value = content
564            .select(&Key::from_str(".num").expect("test should succeed"))
565            .expect("test should succeed");
566        let f = value.as_float().expect("test should succeed");
567        assert!((f - 3.14).abs() < 1e-6);
568    }
569
570    #[test]
571    fn test_yaml_boolean() {
572        let content = YamlContent::from_str("t: true\nf: false").expect("test should succeed");
573        assert_eq!(
574            content
575                .select(&Key::from_str(".t").expect("test should succeed"))
576                .expect("test should succeed")
577                .as_bool(),
578            Some(true)
579        );
580        assert_eq!(
581            content
582                .select(&Key::from_str(".f").expect("test should succeed"))
583                .expect("test should succeed")
584                .as_bool(),
585            Some(false)
586        );
587    }
588
589    #[test]
590    fn test_yaml_null() {
591        let content = YamlContent::from_str("value: null").expect("test should succeed");
592        let value = content
593            .select(&Key::from_str(".value").expect("test should succeed"))
594            .expect("test should succeed");
595        assert!(value.is_null());
596    }
597
598    #[test]
599    fn test_yaml_unicode() {
600        let content = YamlContent::from_str("text: hello").expect("test should succeed");
601        let value = content
602            .select(&Key::from_str(".text").expect("test should succeed"))
603            .expect("test should succeed");
604        assert_eq!(value.as_str(), Some("hello"));
605    }
606
607    #[test]
608    fn test_yaml_nested_array() {
609        let content =
610            YamlContent::from_str("matrix:\n  - [1, 2]\n  - [3, 4]").expect("test should succeed");
611        let value = content
612            .select(&Key::from_str(".matrix[0][1]").expect("test should succeed"))
613            .expect("test should succeed");
614        assert_eq!(value.as_integer(), Some(2));
615    }
616
617    #[test]
618    fn test_yaml_mixed_array() {
619        let content = YamlContent::from_str("data:\n  - name: Alice\n  - name: Bob")
620            .expect("test should succeed");
621        let value = content
622            .select(&Key::from_str(".data[0].name").expect("test should succeed"))
623            .expect("test should succeed");
624        assert_eq!(value.as_str(), Some("Alice"));
625    }
626
627    #[test]
628    fn test_yaml_delete_nested() {
629        let mut content = YamlContent::from_str("server:\n  host: localhost\n  port: 8080")
630            .expect("test should succeed");
631        content
632            .delete(&Key::from_str(".server.port").expect("test should succeed"))
633            .expect("test should succeed");
634        assert!(
635            content
636                .select(&Key::from_str(".server.port").expect("test should succeed"))
637                .is_err()
638        );
639    }
640
641    #[test]
642    fn test_yaml_update_nested() {
643        let mut content =
644            YamlContent::from_str("server:\n  host: old").expect("test should succeed");
645        content
646            .update(
647                &Key::from_str(".server.host").expect("test should succeed"),
648                &Value::string("new"),
649            )
650            .expect("test should succeed");
651        let value = content
652            .select(&Key::from_str(".server.host").expect("test should succeed"))
653            .expect("test should succeed");
654        assert_eq!(value.as_str(), Some("new"));
655    }
656
657    #[test]
658    fn test_yaml_explicit_tag() {
659        let content = YamlContent::from_str("num: !!int 42").expect("test should succeed");
660        let value = content
661            .select(&Key::from_str(".num").expect("test should succeed"))
662            .expect("test should succeed");
663        assert_eq!(value.as_integer(), Some(42));
664    }
665}