hocon-parser 1.6.1

Full Lightbend HOCON specification-compliant parser for Rust
Documentation
// Copyright 2026 1o1 Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0

#[cfg(feature = "serde")]
mod from_map_tests {
    use hocon::from_map;
    use serde_json::json;

    #[test]
    fn scalar_types_round_trip() {
        let values = json!({
            "flag": true,
            "count": 42,
            "ratio": 2.72,
            "label": "hello",
            "nothing": null
        });
        let map = values.as_object().unwrap().clone();
        let c = from_map(map, None).expect("from_map must succeed");
        assert!(c.is_resolved(), "from_map must produce a resolved Config");
        assert!(c.get_bool("flag").unwrap());
        assert_eq!(c.get_i64("count").unwrap(), 42);
        assert!((c.get_f64("ratio").unwrap() - 2.72).abs() < 1e-10);
        assert_eq!(c.get_string("label").unwrap(), "hello");
        // null scalar -> get_string errors per S17.6 (HOCON L1252, fixed in #80).
        // Probe via get() to confirm the field is structurally present as a
        // null scalar — the round-trip preserves null, the typed accessor
        // refuses to coerce it.
        assert!(
            c.get_string("nothing").is_err(),
            "null scalar -> get_string must error (S17.6)"
        );
        match c.get("nothing") {
            Some(hocon::HoconValue::Scalar(ref s)) => assert_eq!(
                s.value_type,
                hocon::ScalarType::Null,
                "null scalar must round-trip as Scalar(Null)"
            ),
            other => panic!("expected Scalar(Null), got {:?}", other),
        }
    }

    #[test]
    fn nested_object() {
        let values = json!({"nested": {"inner": "deep"}});
        let map = values.as_object().unwrap().clone();
        let c = from_map(map, None).expect("from_map must succeed");
        assert_eq!(c.get_string("nested.inner").unwrap(), "deep");
    }

    #[test]
    fn array_of_numbers() {
        let values = json!({"items": [1, 2, 3]});
        let map = values.as_object().unwrap().clone();
        let c = from_map(map, None).expect("from_map must succeed");
        let list = c.get_list("items").unwrap();
        assert_eq!(list.len(), 3);
    }

    #[test]
    fn empty_map_returns_empty_config() {
        let map = serde_json::Map::new();
        let c = from_map(map, None).expect("from_map must succeed");
        assert!(c.is_resolved());
        assert!(c.keys().is_empty());
    }

    #[test]
    fn origin_description_stored() {
        let map = serde_json::Map::new();
        let c = from_map(map, Some("runtime-config")).expect("from_map must succeed");
        assert_eq!(c.origin_description(), Some("runtime-config"));
    }

    #[test]
    fn nan_f64_errors() {
        let mut map = serde_json::Map::new();
        // serde_json::Number does not allow NaN/Inf natively, but we test
        // that our coerce_value correctly handles the serde_json::Number
        // path for finite values.  NaN cannot be inserted via json! macro;
        // test that a normal float works and our guard path is covered.
        map.insert(
            "f".to_string(),
            serde_json::Value::Number(serde_json::Number::from_f64(1.5).expect("1.5 is finite")),
        );
        let c = from_map(map, None).expect("finite float must succeed");
        assert!((c.get_f64("f").unwrap() - 1.5).abs() < 1e-10);
    }

    /// T4 fix: u64 values above i64::MAX must round-trip exactly.
    /// Previously the code called as_i64() then as_f64(), silently losing
    /// precision for integers in the u64-but-not-i64 range.
    #[test]
    fn u64_max_round_trips_exactly() {
        let mut map = serde_json::Map::new();
        map.insert(
            "big".to_string(),
            serde_json::Value::Number(serde_json::Number::from(u64::MAX)),
        );
        let c = from_map(map, None).expect("u64::MAX must succeed");
        assert_eq!(
            c.get_string("big").unwrap(),
            u64::MAX.to_string(),
            "u64::MAX must round-trip exactly through from_map"
        );
    }
}

mod empty_tests {
    use hocon::empty;

    #[test]
    fn has_no_keys() {
        let c = empty(None);
        assert!(c.is_resolved(), "empty must be resolved");
        assert!(c.keys().is_empty());
    }

    #[test]
    fn as_fallback_is_noop() {
        let c = hocon::parse(r#"a = 1"#).unwrap();
        let m = c.with_fallback(&empty(None));
        assert_eq!(m.get_i64("a").unwrap(), 1);
    }

    #[test]
    fn as_receiver_with_fallback() {
        let c = hocon::parse(r#"a = 1"#).unwrap();
        let m = empty(None).with_fallback(&c);
        assert_eq!(m.get_i64("a").unwrap(), 1);
    }

    #[test]
    fn resolve_is_noop() {
        use hocon::ResolveOptions;
        let c = empty(None).resolve(ResolveOptions::defaults()).unwrap();
        assert!(c.is_resolved());
        assert!(c.keys().is_empty());
    }

    #[test]
    fn origin_description_stored() {
        let c = empty(Some("empty-test"));
        assert_eq!(c.origin_description(), Some("empty-test"));
    }
}