use std::collections::BTreeMap;
use yaml_rust2::{Yaml, YamlLoader};
use crate::{ConfigError, ConfigNode};
pub fn parse(input: &str) -> Result<ConfigNode, ConfigError> {
let docs = YamlLoader::load_from_str(input)
.map_err(|source| ConfigError::Yaml { source, span: None })?;
let document = docs
.into_iter()
.next()
.unwrap_or(Yaml::Null);
Ok(convert(document))
}
fn convert(value: Yaml) -> ConfigNode {
match value {
Yaml::Null | Yaml::BadValue => ConfigNode::Null,
Yaml::Real(text) | Yaml::String(text) => ConfigNode::Scalar(text),
Yaml::Boolean(flag) => ConfigNode::Scalar(flag.to_string()),
Yaml::Integer(num) => ConfigNode::Scalar(num.to_string()),
Yaml::Array(values) => ConfigNode::Array(
values
.into_iter()
.map(convert)
.collect(),
),
Yaml::Hash(map) => {
let entries = map
.into_iter()
.map(|(key, value)| (scalar_key(key), convert(value)))
.collect::<BTreeMap<_, _>>();
ConfigNode::Table(entries)
},
Yaml::Alias(_) => ConfigNode::Null,
}
}
fn scalar_key(key: Yaml) -> String {
match key {
Yaml::String(text) => text,
Yaml::Integer(num) => num.to_string(),
Yaml::Real(text) => text,
other => format!("{other:?}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_basic_yaml() {
let input = r#"
port: 8080
host: localhost
"#;
let node = parse(input).unwrap();
let table = node
.as_table()
.unwrap();
assert_eq!(
table
.get("port")
.unwrap()
.to_string(),
"8080"
);
assert_eq!(
table
.get("host")
.unwrap()
.to_string(),
"localhost"
);
}
#[test]
fn parses_nested_maps() {
let input = r#"
database:
host: localhost
port: 5432
"#;
let node = parse(input).unwrap();
let table = node
.as_table()
.unwrap();
let db = table
.get("database")
.unwrap()
.as_table()
.unwrap();
assert_eq!(
db.get("host")
.unwrap()
.to_string(),
"localhost"
);
assert_eq!(
db.get("port")
.unwrap()
.to_string(),
"5432"
);
}
#[test]
fn parses_sequences() {
let input = r#"
tags:
- api
- production
- v2
"#;
let node = parse(input).unwrap();
let table = node
.as_table()
.unwrap();
match table
.get("tags")
.unwrap()
{
ConfigNode::Array(items) => {
assert_eq!(items.len(), 3);
assert_eq!(items[0].to_string(), "api");
},
_ => panic!("expected array"),
}
}
#[test]
fn handles_null_values() {
let input = "key: null";
let node = parse(input).unwrap();
let table = node
.as_table()
.unwrap();
assert!(matches!(
table
.get("key")
.unwrap(),
ConfigNode::Null
));
}
#[test]
fn returns_error_on_invalid_yaml() {
let input = "invalid:\n - broken\n - indentation";
let err = parse(input).unwrap_err();
assert!(matches!(err, ConfigError::Yaml { .. }));
}
}