jsonlogic_fast/
extract.rs1use 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}