atom-engine 5.0.2

A component-oriented template engine built on Tera with props, slots, and provide/inject context
Documentation
use crate::types::value::Value;
use serde_json;

pub fn is_defined(v: &Value) -> Value {
    Value::Bool(!matches!(v, Value::Null))
}

pub fn is_null(v: &Value) -> Value {
    Value::Bool(matches!(v, Value::Null))
}

pub fn is_empty(v: &Value) -> Value {
    Value::Bool(match v {
        Value::Null => true,
        Value::Str(s) => s.is_empty(),
        Value::Array(arr) => arr.is_empty(),
        Value::Object(obj) => obj.is_empty(),
        _ => false,
    })
}

pub fn is_string(v: &Value) -> Value {
    Value::Bool(matches!(v, Value::Str(_)))
}

pub fn is_number(v: &Value) -> Value {
    Value::Bool(matches!(v, Value::Num(_)))
}

pub fn is_bool(v: &Value) -> Value {
    Value::Bool(matches!(v, Value::Bool(_)))
}

pub fn is_array(v: &Value) -> Value {
    Value::Bool(matches!(v, Value::Array(_)))
}

pub fn is_object(v: &Value) -> Value {
    Value::Bool(matches!(v, Value::Object(_)))
}

pub fn default_val(v: &Value, fallback: Value) -> Value {
    if is_defined(v).is_truthy() && !is_null(v).is_truthy() {
        v.clone()
    } else {
        fallback
    }
}

pub fn coalesce(values: &[Value]) -> Value {
    for v in values {
        if is_defined(v).is_truthy() && !is_null(v).is_truthy() {
            return v.clone();
        }
    }
    Value::Null
}

pub fn str_val(v: &Value) -> Value {
    Value::Str(v.coerce_str())
}

pub fn int_val(v: &Value) -> Value {
    if let Some(n) = v.as_f64() {
        Value::Num(n.trunc())
    } else {
        Value::Num(0.0)
    }
}

pub fn float_val(v: &Value) -> Value {
    v.as_f64().map(Value::Num).unwrap_or(Value::Num(0.0))
}

pub fn bool_val(v: &Value) -> Value {
    Value::Bool(v.is_truthy())
}

pub fn json_val(v: &Value) -> Value {
    match serde_json::to_string(v) {
        Ok(s) => Value::Str(s),
        Err(_) => Value::Null,
    }
}

pub fn parse_json(s: &str) -> Value {
    match serde_json::from_str(s) {
        Ok(v) => v,
        Err(_) => Value::Null,
    }
}

pub fn inspect(v: &Value) -> Value {
    Value::Str(format!("{:#?}", v))
}

pub fn type_of(v: &Value) -> Value {
    Value::Str(v.type_name().to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_defined() {
        assert!(is_defined(&Value::Num(1.0)).is_truthy());
        assert!(!is_defined(&Value::Null).is_truthy());
    }

    #[test]
    fn test_is_null() {
        assert!(is_null(&Value::Null).is_truthy());
        assert!(!is_null(&Value::Num(1.0)).is_truthy());
    }

    #[test]
    fn test_is_empty() {
        assert!(is_empty(&Value::Str("".to_string())).is_truthy());
        assert!(is_empty(&Value::Array(vec![])).is_truthy());
        assert!(!is_empty(&Value::Str("x".to_string())).is_truthy());
    }

    #[test]
    fn test_type_of() {
        assert_eq!(
            type_of(&Value::Str("hi".to_string())),
            Value::Str("string".to_string())
        );
        assert_eq!(type_of(&Value::Num(1.0)), Value::Str("number".to_string()));
    }

    #[test]
    fn test_coalesce() {
        let values = vec![Value::Null, Value::Null, Value::Str("found".to_string())];
        assert_eq!(coalesce(&values), Value::Str("found".to_string()));
    }

    #[test]
    fn test_json_roundtrip() {
        let original = Value::Object(
            vec![("key".to_string(), Value::Str("value".to_string()))]
                .into_iter()
                .collect(),
        );
        let json_str = json_val(&original);
        if let Value::Str(s) = json_str {
            let parsed = parse_json(&s);
            assert_eq!(original, parsed);
        } else {
            panic!("Expected string");
        }
    }
}