Skip to main content

jsonlogic_fast/
extract.rs

1use serde_json::{Map, Value};
2
3use crate::error::{RuleEngineError, RuleEngineResult};
4
5pub fn extract_f64(result: Value) -> RuleEngineResult<f64> {
6    match result {
7        Value::Number(n) => n.as_f64().ok_or_else(|| {
8            RuleEngineError::NumericCoercion(
9                "Numeric result could not be converted to f64".to_string(),
10            )
11        }),
12        Value::String(s) => s.parse::<f64>().map_err(|_| {
13            RuleEngineError::NumericCoercion(format!("String result '{}' is not a valid number", s))
14        }),
15        Value::Bool(b) => Ok(if b { 1.0 } else { 0.0 }),
16        Value::Null => Ok(0.0),
17        other => Err(RuleEngineError::NumericCoercion(format!(
18            "Expected numeric result, got: {}",
19            serde_json::to_string(&other).unwrap_or_default()
20        ))),
21    }
22}
23
24pub fn extract_bool(result: Value) -> RuleEngineResult<bool> {
25    match result {
26        Value::Bool(value) => Ok(value),
27        Value::Number(n) => Ok(n.as_f64().unwrap_or_default() != 0.0),
28        Value::String(s) => match s.to_ascii_lowercase().as_str() {
29            "true" | "1" => Ok(true),
30            "false" | "0" | "" => Ok(false),
31            _ => Err(RuleEngineError::NumericCoercion(format!(
32                "String result '{}' is not a valid boolean",
33                s
34            ))),
35        },
36        Value::Null => Ok(false),
37        other => Err(RuleEngineError::NumericCoercion(format!(
38            "Expected boolean result, got: {}",
39            serde_json::to_string(&other).unwrap_or_default()
40        ))),
41    }
42}
43
44pub fn extract_string(result: Value) -> RuleEngineResult<String> {
45    match result {
46        Value::String(value) => Ok(value),
47        Value::Number(n) => Ok(n.to_string()),
48        Value::Bool(b) => Ok(b.to_string()),
49        Value::Null => Ok(String::new()),
50        other => Err(RuleEngineError::NumericCoercion(format!(
51            "Expected string result, got: {}",
52            serde_json::to_string(&other).unwrap_or_default()
53        ))),
54    }
55}
56
57pub fn extract_array(result: Value) -> RuleEngineResult<Vec<Value>> {
58    match result {
59        Value::Array(values) => Ok(values),
60        other => Err(RuleEngineError::NumericCoercion(format!(
61            "Expected array result, got: {}",
62            serde_json::to_string(&other).unwrap_or_default()
63        ))),
64    }
65}
66
67pub fn extract_object(result: Value) -> RuleEngineResult<Map<String, Value>> {
68    match result {
69        Value::Object(values) => Ok(values),
70        other => Err(RuleEngineError::NumericCoercion(format!(
71            "Expected object result, got: {}",
72            serde_json::to_string(&other).unwrap_or_default()
73        ))),
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use serde_json::json;
80
81    use super::*;
82
83    #[test]
84    fn extract_f64_accepts_bool_and_null() {
85        assert_eq!(extract_f64(Value::Bool(true)).unwrap(), 1.0);
86        assert_eq!(extract_f64(Value::Null).unwrap(), 0.0);
87    }
88
89    #[test]
90    fn extract_bool_accepts_common_scalar_values() {
91        assert!(extract_bool(Value::String("true".to_string())).unwrap());
92        assert!(!extract_bool(json!(0)).unwrap());
93    }
94
95    #[test]
96    fn extract_string_serializes_scalars() {
97        assert_eq!(extract_string(json!(7)).unwrap(), "7");
98        assert_eq!(extract_string(Value::Bool(false)).unwrap(), "false");
99    }
100
101    #[test]
102    fn extract_array_rejects_non_arrays() {
103        let error = extract_array(json!({"a": 1})).unwrap_err();
104        assert!(matches!(error, RuleEngineError::NumericCoercion(_)));
105    }
106
107    #[test]
108    fn extract_object_accepts_json_objects() {
109        let object = extract_object(json!({"country": "MX"})).unwrap();
110        assert_eq!(object.get("country"), Some(&json!("MX")));
111    }
112}