use crate::error::ParseResult;
use crate::value::Value;
use saphyr::{LoadableYamlNode, ScalarOwned, YamlLoader};
use saphyr_parser::{BufferedInput, Parser as SaphyrParser};
#[derive(Debug)]
pub struct Parser;
impl Parser {
pub fn parse_str(input: &str) -> ParseResult<Option<Value>> {
let docs = Value::load_from_str(input)?;
Ok(docs.into_iter().next().map(canonicalize))
}
pub fn parse_all(input: &str) -> ParseResult<Vec<Value>> {
Ok(Value::load_from_str(input)?
.into_iter()
.map(canonicalize)
.collect())
}
pub fn parse_all_preserving_styles(input: &str) -> ParseResult<Vec<Value>> {
let mut saphyr_parser = SaphyrParser::new(BufferedInput::new(input.chars()));
let mut loader = YamlLoader::<Value>::default();
loader.early_parse(false);
saphyr_parser.load(&mut loader, true)?;
Ok(loader.into_documents())
}
}
pub fn canonicalize(value: Value) -> Value {
match value {
Value::Value(ScalarOwned::String(ref s)) => match s.as_str() {
"True" | "TRUE" => Value::Value(ScalarOwned::Boolean(true)),
"False" | "FALSE" => Value::Value(ScalarOwned::Boolean(false)),
"Null" | "NULL" => Value::Value(ScalarOwned::Null),
_ => value,
},
Value::Sequence(seq) => Value::Sequence(seq.into_iter().map(canonicalize).collect()),
Value::Mapping(map) => Value::Mapping(
map.into_iter()
.map(|(k, v)| (canonicalize(k), canonicalize(v)))
.collect(),
),
other => other,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_str_simple() {
let result = Parser::parse_str("name: test\nvalue: 123").unwrap();
assert!(result.is_some());
}
#[test]
fn test_parse_str_empty() {
let result = Parser::parse_str("").unwrap();
assert!(result.is_none());
}
#[test]
fn test_parse_all_multiple_docs() {
let docs = Parser::parse_all("---\nfoo: 1\n---\nbar: 2").unwrap();
assert_eq!(docs.len(), 2);
}
#[test]
fn test_yaml12_bool_true_variants() {
use saphyr::ScalarOwned;
for variant in &["True", "TRUE"] {
let result = Parser::parse_str(&format!("val: {variant}"))
.unwrap()
.unwrap();
if let Value::Mapping(map) = result {
let v = map.values().next().unwrap();
assert!(
matches!(v, Value::Value(ScalarOwned::Boolean(true))),
"{variant} should be Bool(true)"
);
} else {
panic!("expected mapping");
}
}
}
#[test]
fn test_yaml12_bool_false_variants() {
use saphyr::ScalarOwned;
for variant in &["False", "FALSE"] {
let result = Parser::parse_str(&format!("val: {variant}"))
.unwrap()
.unwrap();
if let Value::Mapping(map) = result {
let v = map.values().next().unwrap();
assert!(
matches!(v, Value::Value(ScalarOwned::Boolean(false))),
"{variant} should be Bool(false)"
);
} else {
panic!("expected mapping");
}
}
}
#[test]
fn test_yaml12_null_variant() {
use saphyr::ScalarOwned;
let result = Parser::parse_str("val: Null").unwrap().unwrap();
if let Value::Mapping(map) = result {
let v = map.values().next().unwrap();
assert!(
matches!(v, Value::Value(ScalarOwned::Null)),
"Null should be Null"
);
} else {
panic!("expected mapping");
}
}
#[test]
fn test_parse_str_invalid() {
let result = Parser::parse_str("invalid: [\n missing: bracket");
assert!(result.is_err());
}
#[test]
fn test_parse_nested() {
let yaml = r"
person:
name: John
age: 30
hobbies:
- reading
- coding
";
let result = Parser::parse_str(yaml).unwrap();
assert!(result.is_some());
}
#[test]
fn test_parse_anchors() {
let yaml = r"
defaults: &defaults
adapter: postgres
host: localhost
development:
<<: *defaults
database: dev_db
";
let result = Parser::parse_str(yaml).unwrap();
assert!(result.is_some());
}
}