Skip to main content

bel/
json.rs

1#[cfg(feature = "time")]
2use chrono::Duration;
3use thiserror::Error;
4
5use crate::Value;
6
7#[derive(Debug, Clone, Error)]
8#[error("unable to convert value to json: {0:?}")]
9pub enum ConvertToJsonError<'a> {
10    /// We cannot convert the CEL value to JSON. Some CEL types (like functions) are
11    /// not representable in JSON.
12    #[error("unable to convert value to json: {0:?}")]
13    Value(&'a Value),
14
15    #[cfg(feature = "time")]
16    /// The duration is too large to convert to nanoseconds. Any duration of 2^63
17    /// nanoseconds or more will overflow. We'll return the duration type in the
18    /// error message.
19    #[error("duration too large to convert to nanoseconds: {0:?}")]
20    DurationOverflow(&'a Duration),
21}
22
23impl Value {
24    /// Converts a CEL value to a JSON value.
25    ///
26    /// # Example
27    /// ```
28    /// use bel::{Context, Program};
29    ///
30    /// let program = Program::compile("null").unwrap();
31    /// let value = program.execute(&Context::default()).unwrap();
32    /// let result = value.json().unwrap();
33    ///
34    /// assert_eq!(result, serde_json::Value::Null);
35    /// ```
36    pub fn json(&self) -> Result<serde_json::Value, ConvertToJsonError<'_>> {
37        Ok(match *self {
38            Value::List(ref vec) => {
39                serde_json::Value::Array(vec.iter().map(|v| v.json()).collect::<Result<Vec<_>, _>>()?)
40            }
41            Value::Map(ref map) => {
42                let mut obj = serde_json::Map::new();
43                for (k, v) in map.map.iter() {
44                    obj.insert(k.to_string(), v.json()?);
45                }
46                serde_json::Value::Object(obj)
47            }
48            Value::Int(i) => i.into(),
49            // Value::UInt(u) => u.into(),
50            Value::Float(f) => f.into(),
51            Value::String(ref s) => s.to_string().into(),
52            Value::Bool(b) => b.into(),
53            Value::Bytes(ref b) => base64::encode(b.as_slice(), base64::Alphabet::Standard).into(),
54            Value::Null => serde_json::Value::Null,
55            #[cfg(feature = "time")]
56            Value::Timestamp(ref dt) => dt.to_rfc3339().into(),
57            #[cfg(feature = "time")]
58            Value::Duration(ref v) => serde_json::Value::Number(serde_json::Number::from(
59                v.num_nanoseconds().ok_or(ConvertToJsonError::DurationOverflow(v))?,
60            )),
61            _ => return Err(ConvertToJsonError::Value(self)),
62        })
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use std::collections::HashMap;
69
70    #[cfg(feature = "time")]
71    use chrono::Duration;
72    use serde_json::json;
73
74    use crate::{Value as CelValue, objects::Map};
75
76    #[test]
77    fn test_cel_value_to_json() {
78        let mut tests = vec![
79            (json!("hello"), CelValue::String("hello".to_string().into())),
80            (json!(42), CelValue::Int(42)),
81            (json!(42.0), CelValue::Float(42.0)),
82            (json!(true), CelValue::Bool(true)),
83            (json!(null), CelValue::Null),
84            (
85                json!([true, null]),
86                CelValue::List(vec![CelValue::Bool(true), CelValue::Null].into()),
87            ),
88            (
89                json!({"hello": "world"}),
90                CelValue::Map(Map::from(HashMap::from([(
91                    "hello".to_string(),
92                    CelValue::String("world".to_string().into()),
93                )]))),
94            ),
95        ];
96
97        #[cfg(feature = "time")]
98        if true {
99            tests.push((json!(1_000_000_000), CelValue::Duration(Duration::seconds(1))));
100        }
101
102        for (expected, value) in tests.iter() {
103            assert_eq!(value.json().unwrap(), *expected, "{value:?}={expected:?}");
104        }
105    }
106}