use anyhow::anyhow;
use yaml_rust::{yaml, Yaml, YamlLoader, YamlEmitter};
use crate::value::{Array, Object, UniNode, Expected};
use super::{UniNodeFormat, UniNodeFmtError};
#[derive(Clone, Copy, Default)]
pub struct YamlFormat;
impl UniNodeFormat for YamlFormat {
fn extensions(&self) -> &[&str] {
&["yaml", "yml"]
}
fn parse(&self, text: &str) -> Result<UniNode, UniNodeFmtError> {
let mut root = YamlLoader::load_from_str(text)
.map_err(|e| UniNodeFmtError::Format(e.into()))?;
fn convert(node: Yaml) -> Result<UniNode, UniNodeFmtError> {
match node {
Yaml::Null => Ok(UniNode::Null),
Yaml::Boolean(val) => Ok(UniNode::Boolean(val)),
Yaml::Integer(val) => Ok(UniNode::Integer(val)),
Yaml::String(val) => Ok(UniNode::String(val)),
Yaml::Real(val) => Ok(UniNode::Float(val.parse::<f64>()?)),
Yaml::Array(val) => {
let mut data = Array::with_capacity(val.len());
for node in val {
data.push(convert(node)?);
}
Ok(UniNode::Array(data))
},
Yaml::Hash(val) => {
let mut data = Object::new();
for (key, node) in val {
let key = key.as_str().ok_or_else(|| {
UniNodeFmtError::Conv("Non-string key".to_string())
})?;
data.insert(key.to_string(), convert(node)?);
}
Ok(UniNode::Object(data))
},
Yaml::Alias(_) => Err(UniNodeFmtError::Conv(
"Yaml alias, not fully supported yet".to_string(),
)),
_ => {
Err(UniNodeFmtError::Conv("Yaml invalid type".to_string()))
},
}
}
let root = match root.len() {
0 => Yaml::Hash(yaml::Hash::new()),
1 => std::mem::replace(&mut root[0], Yaml::Null),
n => {
return Err(UniNodeFmtError::Format(anyhow!(
"Got {} YAML documents, expected 1",
n
)))
},
};
convert(root)
}
fn build(&self, node: &UniNode) -> Result<String, UniNodeFmtError> {
fn convert(node: &UniNode) -> Result<Yaml, UniNodeFmtError> {
match node {
UniNode::Null => Ok(Yaml::Null),
UniNode::Boolean(v) => Ok(Yaml::Boolean(*v)),
UniNode::Integer(v) => Ok(Yaml::Integer(*v)),
UniNode::UInteger(v) => Ok(Yaml::Integer(i64::try_from(*v)?)),
UniNode::Float(v) => Ok(Yaml::Real(v.to_string())),
UniNode::String(v) => Ok(Yaml::String(v.clone())),
UniNode::Bytes(_) => {
Err(UniNodeFmtError::NotSupportedType(Expected::Bytes))
},
UniNode::Array(v) => {
let mut array =
yaml_rust::yaml::Array::with_capacity(v.len());
for sub in v {
array.push(convert(sub)?);
}
Ok(Yaml::Array(array))
},
UniNode::Object(v) => {
let mut table =
yaml_rust::yaml::Hash::with_capacity(v.len());
for (key, sub) in v {
let key = Yaml::String(key.clone());
table.insert(key, convert(sub)?);
}
Ok(Yaml::Hash(table))
},
}
}
let mut out = String::new();
let mut emitter = YamlEmitter::new(&mut out);
emitter
.dump(&convert(node)?)
.map_err(|e| UniNodeFmtError::Format(e.into()))?;
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn yaml_format() {
let text = r#"
client:
host: "localhost"
port: 404
can: 2001-12-15T02:59:43.1Z
collection:
- "one"
- "two"
"#;
let node = YamlFormat.parse(text).unwrap();
assert_eq!(node.find_int("client.port").unwrap(), 404);
assert_eq!(node.find_str("client.host").unwrap(), "localhost");
let text = YamlFormat.build(&node).unwrap();
let node = YamlFormat.parse(&text).unwrap();
assert_eq!(node.find_int("client.port").unwrap(), 404);
assert_eq!(node.find_str("client.host").unwrap(), "localhost");
}
}