kodumaro-http-cli 0.5.5

Kodumaro HTTP CLI inspired by HTTPie
Documentation
use serde_json::{Map, Number, Value};

pub fn extend_object(target: &mut Map<String, Value>, source: &Map<String, Value>) {
    for (key, value) in source {
        append_object(target, key, value.clone());
    }
}

pub fn append_object(target: &mut Map<String, Value>, key: impl ToString, value: Value) {
    do_append(target, key.to_string(), process_value(value));
}

pub fn do_append(target: &mut Map<String, Value>, key: String, value: Value) {
    if let Some((base, rest)) = key.split_once(".") {
        if let Some(inner) = target.get_mut(base) {
            if let Some(inner) = inner.as_object_mut() {
                do_append(inner, rest.to_string(), value);
                return;
            }
        } else {
            let mut inner: Value = serde_json::from_str("{}").unwrap();
            let proxy = inner.as_object_mut().unwrap();
            do_append(proxy, rest.to_string(), value);
            target.insert(base.to_string(), inner);
        }
        return
    }

    if let Some(mut current) = target.remove(&key) {
        if let Some(current) = current.as_array_mut() {
            current.push(value);
        } else {
            current = vec![current, value].into();
        }
        target.insert(key, current);
    } else {
        target.insert(key, value);
    }
}

fn process_value(value: Value) -> Value {
    if !value.is_string() {
        return value;
    }

    if let Some(value) = value.as_str() {
        if let Some(value) = value.strip_prefix("!!bool ") {
            return Value::Bool(value.trim() == "true");
        }
        if let Some(value) = value.strip_prefix("!!float ") {
            return Value::Number(
                Number::from_f64(value.trim().parse::<f64>().unwrap())
                    .unwrap()
            );
        }
        if let Some(value) = value.strip_prefix("!!int ") {
            return Value::Number(value.trim().parse::<i64>().unwrap().into());
        }
        if let Some(value) = value.strip_prefix("!!str ") {
            return Value::String(value.trim().into());
        }
    };

    value
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_append_object_to_empty_target() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        append_object(proxy, "test", true.into());
        assert!(target["test"].as_bool().unwrap());
    }

    #[test]
    fn test_append_object_to_target_containing_data() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        proxy.insert("a".to_string(), 42.into());
        proxy.insert("b".to_string(), "test".into());
        append_object(proxy, "test", true.into());
        assert!(target["test"].as_bool().unwrap());
        assert_eq!(42, target["a"].as_i64().unwrap());
        assert_eq!("test", target["b"].as_str().unwrap());
    }

    #[test]
    fn test_append_object_to_target_containing_the_same_key() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        append_object(proxy, "test", "a".into());
        append_object(proxy, "test", "b".into());
        append_object(proxy, "test", 42.into());
        let proxy = target["test"].as_array().unwrap();
        assert_eq!("a", proxy[0].as_str().unwrap());
        assert_eq!("b", proxy[1].as_str().unwrap());
        assert_eq!(42, proxy[2].as_i64().unwrap());
    }

    #[test]
    fn test_append_inner_object() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        append_object(proxy, "name", "test".into());
        append_object(proxy, "test.a", 2.into());
        append_object(proxy, "test.b", 3.into());
        append_object(proxy, "test.c", 5.into());
        append_object(proxy, "test.d.e", true.into());
        assert_eq!("test", target["name"].as_str().unwrap());
        assert_eq!(2, target["test"]["a"].as_i64().unwrap());
        assert_eq!(3, target["test"]["b"].as_i64().unwrap());
        assert_eq!(5, target["test"]["c"].as_i64().unwrap());
        assert!(target["test"]["d"]["e"].as_bool().unwrap());
    }

    #[test]
    fn test_extend_object() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        let source: Value = serde_json::from_str("{\"x\": 3, \"y\": 4, \"a\": 5}").unwrap();
        extend_object(proxy, source.as_object().unwrap());
        let source: Value = serde_json::from_str("{\"a\": true, \"name\": \"test\"}").unwrap();
        extend_object(proxy, source.as_object().unwrap());
        assert_eq!(3, proxy["x"].as_i64().unwrap());
        assert_eq!(4, proxy["y"].as_i64().unwrap());
        let proxy = target["a"].as_array().unwrap();
        assert_eq!(5, proxy[0].as_i64().unwrap());
        assert!(proxy[1].as_bool().unwrap());
    }

    #[test]
    fn test_yaml_style_boolean() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        append_object(proxy, "a", Value::String("!!bool true".into()));
        append_object(proxy, "b", Value::String("!!bool false".into()));
        debug_assert!(target["a"].as_bool().unwrap());
        debug_assert!(!target["b"].as_bool().unwrap());
    }

    #[test]
    fn test_yaml_style_float() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        append_object(proxy, "test", Value::String("!!float 123".into()));
        debug_assert_eq!(123.0, target["test"].as_f64().unwrap());
    }

    #[test]
    fn test_yaml_style_integer() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        append_object(proxy, "test", Value::String("!!int 123".into()));
        debug_assert_eq!(123, target["test"].as_i64().unwrap());
    }

    #[test]
    fn test_yaml_style_string() {
        let mut target: Value = serde_json::from_str("{}").unwrap();
        let proxy = target.as_object_mut().unwrap();
        append_object(proxy, "test", Value::String("!!str 123".into()));
        debug_assert_eq!("123", target["test"].as_str().unwrap());
    }
}