stav 0.1.0

A statically typed, eager and total configuration language
Documentation
use super::{AttrPath, Value};

fn job() -> Value {
    Value::Struct {
        name: "Job".to_owned(),
        fields: vec![(
            "build".to_owned(),
            Value::Variant {
                ty: "Build".to_owned(),
                name: "native".to_owned(),
                fields: Some(vec![(
                    "features".to_owned(),
                    Value::List(vec![Value::Str("simd".to_owned())]),
                )]),
            },
        )],
    }
}

#[test]
fn paths_step_through_fields_keys_and_variants() {
    let root = Value::Map(vec![("finder".to_owned(), job())]);
    let features = root.at(&AttrPath::new(["finder", "build", "native", "features"]));
    assert_eq!(
        features,
        Some(&Value::List(vec![Value::Str("simd".to_owned())]))
    );
    assert!(
        root.at(&AttrPath::new(["finder", "build", "script"]))
            .is_none()
    );
    assert_eq!(root.at(&AttrPath::root()), Some(&root));
}

#[test]
fn paths_display_like_attribute_statements() {
    let path = AttrPath::new(["tunables", "mem.reclaim"]).join("x");
    assert_eq!(path.to_string(), "tunables.\"mem.reclaim\".x");
}

#[test]
fn quoted_keys_use_string_literal_escapes() {
    let path = AttrPath::new(["a\"b\\c", "line\n\ttab\r", "#{x} #y", "\u{1b}", ""]);
    assert_eq!(
        path.to_string(),
        r#""a\"b\\c"."line\n\ttab\r"."\#{x} #y"."\u{1b}"."""#
    );
}

#[test]
fn displayed_paths_parse_back() {
    let paths = [
        AttrPath::root(),
        AttrPath::new(["server", "port"]),
        AttrPath::new(["tunables", "mem.reclaim", "x-y_z"]),
        AttrPath::new(["a\"b\\c", "line\n\ttab\r\0", "#{x} #y", "\u{1b}\u{7f}", ""]),
        AttrPath::new(["caf\u{e9}", "with space", "\u{1F600}"]),
    ];
    for path in paths {
        let text = path.to_string();
        assert_eq!(text.parse::<AttrPath>(), Ok(path), "{text}");
    }
}

#[test]
fn quoted_keys_accept_unicode_escapes() {
    let path: AttrPath = r#""\u{48}i""#.parse().unwrap();
    assert_eq!(path.keys(), ["Hi"]);
}

#[test]
fn malformed_paths_are_rejected_with_a_reason() {
    let cases = [
        ("a..b", "empty key"),
        ("a.", "empty key"),
        (".a", "empty key"),
        ("a b", "`a b` must be quoted"),
        ("caf\u{e9}", "`caf\u{e9}` must be quoted"),
        (r#""open"#, "unterminated quoted key"),
        (r#""a"b"#, "expected `.` after a quoted key, found `b`"),
        (r#""a\q""#, r"unknown escape `\q`"),
        (r#""\u{d800}""#, r"invalid unicode escape `\u{d800}`"),
        (r#""\u{48""#, r"invalid unicode escape `\u{48`"),
    ];
    for (text, message) in cases {
        let error = text.parse::<AttrPath>().unwrap_err();
        assert_eq!(error.to_string(), message, "{text}");
    }
}