use hocon::HoconError;
fn assert_array_root_config_error(err: HoconError, label: &str) -> String {
match err {
HoconError::Config(ce) => {
assert!(
ce.message.contains("array rather than object at file root"),
"{}: message must name the array-at-file-root condition, got: {}",
label,
ce.message
);
assert!(
ce.path.is_empty(),
"{}: file-root errors carry no access path, got: {}",
label,
ce.path
);
ce.message
}
HoconError::Parse(pe) => panic!(
"{}: got ParseError {:?}; array-root documents are valid syntax — expected HoconError::Config",
label, pe.message
),
other => panic!("{}: expected HoconError::Config, got {:?}", label, other),
}
}
#[test]
fn s3_5_top_level_is_type_error() {
for (label, src) in [
("basic", "[1,2]"),
("multiline-objects", "[\n { a : 1 },\n { b : 2 }\n]\n"),
("empty-array", "[]"),
] {
let err = hocon::parse(src).expect_err(label);
assert_array_root_config_error(err, label);
}
}
#[test]
fn s3_5_error_carries_position() {
let err = hocon::parse("\n [1,2]").expect_err("position");
let msg = assert_array_root_config_error(err, "position");
assert!(
msg.contains("2:3"),
"message must carry the opening bracket position 2:3, got: {}",
msg
);
}
#[test]
fn s3_5_origin_naming() {
let opts = hocon::ParseOptions::defaults().with_origin_description("my-source".to_string());
let err = hocon::parse_string_with_options("[1,2]", opts).expect_err("custom origin");
let msg = assert_array_root_config_error(err, "custom origin");
assert!(
msg.contains("my-source"),
"message must carry the custom origin, got: {}",
msg
);
let dir = tempfile::tempdir().unwrap();
let f = dir.path().join("arr.conf");
std::fs::write(&f, "[1,2]\n").unwrap();
let err = hocon::parse_file_with_options(&f, hocon::ParseOptions::defaults())
.expect_err("file origin");
let msg = assert_array_root_config_error(err, "file origin");
assert!(
msg.contains("arr.conf") && !msg.starts_with("input:"),
"file parse must name the file in the origin, got: {}",
msg
);
}
#[test]
fn s3_5_deferred_lifecycle() {
let opts = hocon::ParseOptions::defaults().with_resolve_substitutions(false);
let err = hocon::parse_string_with_options("[1,2]", opts).expect_err("deferred");
assert_array_root_config_error(err, "deferred");
}
#[test]
fn s3_5_malformed_arrays_stay_syntax_errors() {
for (label, src) in [
("unterminated", "[1,2"),
("trailing-content", "[1,2]\na = 1"),
] {
match hocon::parse(src) {
Err(HoconError::Parse(_)) => {}
other => panic!(
"{}: expected HoconError::Parse (syntax), got {:?}",
label, other
),
}
}
}
#[test]
fn s3_5_include_of_array_root_names_included_file() {
let dir = tempfile::tempdir().unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
std::fs::write(dir.path().join("arr.conf"), "[1,2]\n").unwrap();
let input = format!("include \"{}/arr.conf\"\na = 1\n", dir_str);
match hocon::parse(&input) {
Err(HoconError::Resolve(re)) => {
assert!(
re.message.contains("array at file root"),
"message must name the array-at-file-root condition, got: {}",
re.message
);
assert!(
re.path.contains("arr.conf"),
"path must name the included file, got: {:?}",
re
);
}
other => panic!("expected HoconError::Resolve, got {:?}", other),
}
}
#[test]
fn s3_5_nested_include_names_innermost_file() {
let dir = tempfile::tempdir().unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
std::fs::write(dir.path().join("arr.conf"), "[1,2]\n").unwrap();
std::fs::write(dir.path().join("mid.conf"), "include \"arr.conf\"\nb = 2\n").unwrap();
let input = format!("include \"{}/mid.conf\"\na = 1\n", dir_str);
match hocon::parse(&input) {
Err(HoconError::Resolve(re)) => {
assert!(
re.message.contains("array at file root"),
"message must name the array-at-file-root condition, got: {}",
re.message
);
assert!(
re.path.contains("arr.conf"),
"path must name the innermost file arr.conf, got: {:?}",
re
);
assert!(
!re.path.contains("mid.conf") && !re.message.contains("mid.conf"),
"error must not accuse the intermediate file mid.conf, got: {:?}",
re
);
}
other => panic!("expected HoconError::Resolve, got {:?}", other),
}
}
#[cfg(feature = "include-package")]
#[test]
fn s3_5_package_include_of_array_root_is_error() {
let input = r#"
a = 1
include package("test/s3-5-array-root", "ref.conf")
"#;
match hocon::Parser::new()
.register_package("test/s3-5-array-root", "ref.conf", "[1,2]\n")
.parse(input)
{
Err(HoconError::Resolve(re)) => {
assert!(
re.message.contains("array at file root"),
"message must name the array-at-file-root condition, got: {}",
re.message
);
}
other => panic!("expected HoconError::Resolve, got {:?}", other),
}
}
#[test]
fn s3_5_non_root_arrays_unaffected() {
let cfg = hocon::parse("a = [1,2]").expect("field-value array");
let items = cfg.get_list("a").expect("a must be a list");
assert_eq!(items.len(), 2, "a must have 2 elements, got {:?}", items);
assert!(
hocon::parse("{ a = [1,2] }").is_ok(),
"braced root with array field"
);
}
#[test]
fn s3_5_conformance_fixture_loop() {
let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/testdata/hocon/array-root");
if !dir.is_dir() {
eprintln!("skip: array-root fixtures missing; run `make testdata` (requires xx.hocon#64)");
return;
}
let env: std::collections::HashMap<String, String> = std::collections::HashMap::new();
let mut ran = 0;
for entry in std::fs::read_dir(&dir).unwrap() {
let path = entry.unwrap().path();
let name = path.file_name().unwrap().to_string_lossy().to_string();
if !name.ends_with(".conf") || name.ends_with("-inner.conf") {
continue;
}
let err = hocon::parse_file_with_env(&path, &env)
.err()
.unwrap_or_else(|| panic!("{}: expected array-at-file-root error, got Ok", name));
let msg = format!("{}", err);
assert!(
msg.contains("array") && msg.contains("file root"),
"{}: error must name the array-at-file-root condition, got: {}",
name,
msg
);
ran += 1;
}
assert!(
ran >= 3,
"expected at least 3 array-root fixtures, ran {}",
ran
);
}