json_ops 0.1.0

Implement json pointer following the json pointer syntax, with type Option<&toml::Value>. Overload / as path operator to point into a node in json tree, as well as some other meaningfull operator overload.
Documentation
// require enable the toml feature such as:
// cargo test -F toml (or --all-features)

use json_ops::ValuePath;
use toml::Value;

fn load_test_toml() -> Value
{
    let str_toml = include_str!("./sample.toml");
    let v: Value = str_toml.parse().unwrap();
    return v;
}

#[test]
fn path_test() {
    let v = load_test_toml();
    assert_eq!(v.pathto("ip").unwrap().as_str(), Some("127.0.0.1"));
    assert_eq!(v["ip"].as_str(), Some("127.0.0.1"));

    let op = v.path();
    let ip = op / "ip";
    assert_eq!(ip.unwrap().as_str(), Some("127.0.0.1"));

    let ip = op / "host" / "ip";
    assert_eq!(ip.unwrap().as_str(), Some("127.0.1.1"));

    let host = v.path() / "host";
    let ip = host / "ip";
    assert_eq!(ip.unwrap().as_str(), Some("127.0.1.1"));
    let port = host / "port";
    assert_eq!(port.unwrap().as_integer(), Some(8080));

    let proto = host / "protocol" / 1;
    assert_eq!(proto.unwrap().as_str(), Some("udp"));

    let proto = v.path() / "host" / "protocol" / 2;
    assert_eq!(proto.unwrap().as_str(), Some("mmp"));

    let proto = v.path() / "host/protocol/2";
    assert_eq!(proto.unwrap().as_str(), Some("mmp"));

    let proto = v.pathto("/host/protocol/2");
    assert_eq!(proto.unwrap().as_str(), Some("mmp"));

    let server = v.path() / "service" / 0 / "name";
    assert_eq!(server.unwrap().as_str(), Some("serv_1"));

    // path() produce immutable reference, cannot write or assign
    // let server = server.unwrap();
    // *server = Value::String(String::from("serv 1"));

    let mut mv = load_test_toml();
    let ip = mv.get_mut("ip").unwrap();
    *ip = Value::String(String::from("127.0.0.2"));
    assert_eq!((mv.path() / "ip").unwrap().as_str(), Some("127.0.0.2"));
}

#[test]
fn path_none_test() {
    let v = load_test_toml();

    let root = v.path();
    assert_eq!(root.is_none(), false);

    let node = root / "ip";
    assert_eq!(node.is_none(), false);
    let node = root / "IP";
    assert_eq!(node.is_none(), true);

    let node = root / "host" /"protocol";
    assert_eq!(node.is_none(), false);
    let node = root / "host" /"protocol" / 1;
    assert_eq!(node.is_none(), false);
    let node = root / "host" /"protocol" / 3;
    assert_eq!(node.is_none(), true);

    let node = root / "service" / 0;
    assert_eq!(node.is_none(), false);
    let node = root / "service" / 0 / "description";
    assert_eq!(node.is_none(), true);
    let node = root / "service" / 0 / "desc";
    assert_eq!(node.is_none(), false);
    let node = root / "service" / 2;
    assert_eq!(node.is_none(), true);
}


#[test]
fn path_mut_test() {
    let mut v = load_test_toml();

    let root = v.path();
    assert_eq!(root.is_none(), false);

    let node = root / "ip";
    assert_eq!(node.is_none(), false);

    let node = root / "IP";
    assert_eq!(node.is_none(), true);
    assert_eq!((*node).is_none(), true);

    let node = v.path_mut() / "ip";
    assert_eq!(node.is_none(), false);

    let node = v.path_mut() / "IP";
    assert_eq!(node.is_none(), true);
    assert_eq!(node.is_none(), true);
}

#[test]
fn pipe_test() {
    let v = load_test_toml();

    // pipe ending slash operator to get inner scalar primitive value
    let ip = v.path() / "ip" | "";
    assert_eq!(ip, "127.0.0.1");

    let ip = v.path() / "host" / "ip" | "";
    assert_eq!(ip, "127.0.1.1");

    let ip_default = "127";
    let ip = v.path() / "host" / "ip" | ip_default;
    assert_eq!(ip, "127.0.1.1");

    // pipe convertor accept &'static str or String,
    // but ofcourse &str is more efficient
    let ip_default: String = String::from("127");
    let ip = v.path() / "host" / "ip" | ip_default;
    assert_eq!(ip, "127.0.1.1");
    // ip_default is moved by | operator, and cannot use non-static &String
    // println!("{ip_default}");

    let port = v.path() / "host" / "port" | 0;
    assert_eq!(port, 8080);

    let port_default = 80;
    let port = v.path() / "host" / "port" | port_default;
    assert_eq!(port, 8080);
    assert_eq!(port_default, 80); // simple primitive wont moved

    // can save intermedia tmp value
    let misc = v.path() / "misc";
    let value = misc / "int" | 0;
    assert_eq!(value, 1234);
    let value = misc / "float" | 0.0;
    assert_eq!(value, 3.14);
    let value = misc / "bool" | false;
    assert_eq!(value, true);

    // path ignore repeated slash or dot
    let value = v.pathto("/misc/int") | 0;
    assert_eq!(value, 1234);
    let value = v.pathto("misc/int") | 0;
    assert_eq!(value, 1234);
    let value = v.pathto("misc/int/") | 0;
    assert_eq!(value, 0); //< tail / means an extar empty string key
    let value = v.pathto("misc.int") | 0;
    assert_eq!(value, 1234);
    let value = v.pathto("/misc/./int/") | 0;
    assert_eq!(value, 0);
}

#[test]
fn pipe_mut_test() {
    let mut v = load_test_toml();

    let ip = v.path_mut() / "ip" | "";
    assert_eq!(ip, "127.0.0.1");

    let ip = v.path_mut() / "host" / "ip" | "";
    assert_eq!(ip, "127.0.1.1");

    let ip_default = "127";
    let ip = v.path_mut() / "host" / "ip" | ip_default;
    assert_eq!(ip, "127.0.1.1");

    let ip_default: String = String::from("127");
    let ip = v.path_mut() / "host" / "ip" | ip_default;
    assert_eq!(ip, "127.0.1.1");

    let port = v.path_mut() / "host" / "port" | 0;
    assert_eq!(port, 8080);

    let port_default = 80;
    let port = v.path_mut() / "host" / "port" | port_default;
    assert_eq!(port, 8080);
    assert_eq!(port_default, 80);

    // can save intermedia tmp value
    let misc = v.path_mut() / "misc";
    let value = misc / "int" | 0;
    assert_eq!(value, 1234);

    let value = v.pathto_mut("/misc.int") | 0;
    assert_eq!(value, 1234);

    let value = v.path_mut() / "misc" / "float" | 0.0;
    assert_eq!(value, 3.14);
    let value = v.path_mut() / "misc" / "bool" | false;
    assert_eq!(value, true);
}

#[test]
fn push_test() {
    let mut v = load_test_toml();

    let ip = v.path() / "ip" | "";
    assert_eq!(ip, "127.0.0.1");

    let ip_node = v.path_mut() / "ip" << "127.0.0.2";
    let ip = ip_node | "";
    assert_eq!(ip, "127.0.0.2");
    let ip = v.path() / "ip" | "";
    assert_eq!(ip, "127.0.0.2");

    // push mistype value silently overwrite
    let ip_node = v.path_mut() / "ip";
    let ip_node = ip_node << 127;
    assert_eq!(ip_node.is_none(), false);
    let ip = v.path() / "ip" | "";
    assert_eq!(ip, "");
    let ip = v.path() / "ip" | 0;
    assert_eq!(ip, 127);
    let ip = v.path() / "ip" | "".to_string();
    assert_eq!(ip, "127");

    // push scalar to leat node with supported type.
    let node = v.path_mut() / "misc" / "int" << 4242;
    let val = node | 0;
    assert_eq!(val, 4242);

    let node = v.path_mut() / "misc" / "float";
    let _ = node << 31.4;
    let val = v.path() / "misc" / "float" | 0.0;
    assert_eq!(val, 31.4);

    let node = v.path_mut() / "misc" / "float";
    let node = node << 3142;
    assert_eq!(node.is_none(), false);
    let val = node | 0.0;
    assert_eq!(val, 0.0);
    let val = v.path() / "misc" / "float" | 0;
    assert_eq!(val, 3142);

    let node = v.path_mut() / "misc" / "bool";
    let _ = node << false;
    let val = v.path() / "misc" / "bool" | true;
    assert_eq!(val, false);

    // push a item to toml array
    let node = v.path_mut() / "host" / "protocol";
    let node = node << ("abc", ) << ["edf"];
    // enable print by: cargo test -- --nocapture
    // dbg!(node);
    let val = node / 3 | "";
    assert_eq!(val, "abc");
    let val = v.path() / "host" / "protocol" / 4 | "";
    assert_eq!(val, "edf");

    // push slice to toml array
    let node = v.path_mut() / "host" / "protocol";
    let _ = node << &["xyz"][..] << &["ABC", "DEF"][..];
    let node = v.path() / "host" / "protocol";
    println!("{}", node.unwrap());
    assert_eq!(node.unwrap().as_array().unwrap().len(), 8);

    // push key-val pair to toml table
    let node = v.path_mut() / "host";
    let _ = node << ("newkey1", 1) << ("newkey2", "2");
    let val = v.path() / "host" / "newkey1" | 0;
    assert_eq!(val, 1);
    let val = v.path() / "host" / "newkey2" | "";
    assert_eq!(val, "2");

    // use i32, must cast to i64, because toml integer save i64 
    let input: i32 = 32;
    let output_default: i32 = 2;
    let node = v.path_mut() / "misc" / "int";
    let _ = node << input as i64;
    let val = v.path() / "misc" / "int" | output_default as i64;
    assert_eq!(val, input as i64);
}

#[test]
fn path_if_test() {
    let mut v = load_test_toml();

    let node = v.path() / "ip";
    if node.is_some() {
        let ip = node | "";
        assert_eq!(ip, "127.0.0.1");
    }

    let node = v.path() / "IP";
    if node.is_none() {
        let ip = node | "";
        assert_eq!(ip, "");
    }

    let node = v.path_mut() / "ip";
    // in mut version operator !node would move self
    if node.is_some() {
        let ip = node | "";
        assert_eq!(ip, "127.0.0.1");
    }

    let mut node = v.path_mut() / "ip";
    if !node.is_none() {
        node = node << "127.0.0.2";
        let ip = node | "";
        assert_eq!(ip, "127.0.0.2");
    }

    let mut node = v.path_mut() / "host" / "port";
    if !node.is_none() {
        node = node << "127.0.0.2";
        assert_eq!(node.is_none(), false);
        let val = node | "";
        assert_eq!(val, "127.0.0.2");
    }
}