use crate::Value;
use base64::prelude::*;
#[cfg(feature = "chrono")]
use chrono::Duration;
use thiserror::Error;
#[derive(Debug, Clone, Error)]
#[error("unable to convert value to json: {0:?}")]
pub enum ConvertToJsonError<'a> {
#[error("unable to convert value to json: {0:?}")]
Value(&'a Value),
#[cfg(feature = "chrono")]
#[error("duration too large to convert to nanoseconds: {0:?}")]
DurationOverflow(&'a Duration),
}
impl Value {
pub fn json(&self) -> Result<serde_json::Value, ConvertToJsonError> {
Ok(match *self {
Value::List(ref vec) => serde_json::Value::Array(
vec.iter()
.map(|v| v.json())
.collect::<Result<Vec<_>, _>>()?,
),
Value::Map(ref map) => {
let mut obj = serde_json::Map::new();
for (k, v) in map.map.iter() {
obj.insert(k.to_string(), v.json()?);
}
serde_json::Value::Object(obj)
}
Value::Int(i) => i.into(),
Value::UInt(u) => u.into(),
Value::Float(f) => f.into(),
Value::String(ref s) => s.to_string().into(),
Value::Bool(b) => b.into(),
Value::Bytes(ref b) => BASE64_STANDARD.encode(b.as_slice()).to_string().into(),
Value::Null => serde_json::Value::Null,
#[cfg(feature = "chrono")]
Value::Timestamp(ref dt) => dt.to_rfc3339().into(),
#[cfg(feature = "chrono")]
Value::Duration(ref v) => serde_json::Value::Number(serde_json::Number::from(
v.num_nanoseconds()
.ok_or(ConvertToJsonError::DurationOverflow(v))?,
)),
_ => return Err(ConvertToJsonError::Value(self)),
})
}
}
#[cfg(test)]
mod tests {
use crate::objects::Map;
use crate::Value as CelValue;
#[cfg(feature = "chrono")]
use chrono::Duration;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn test_cel_value_to_json() {
let mut tests = vec![
(json!("hello"), CelValue::String("hello".to_string().into())),
(json!(42), CelValue::Int(42)),
(json!(42.0), CelValue::Float(42.0)),
(json!(true), CelValue::Bool(true)),
(json!(null), CelValue::Null),
(
json!([true, null]),
CelValue::List(vec![CelValue::Bool(true), CelValue::Null].into()),
),
(
json!({"hello": "world"}),
CelValue::Map(Map::from(HashMap::from([(
"hello".to_string(),
CelValue::String("world".to_string().into()),
)]))),
),
];
#[cfg(feature = "chrono")]
if true {
tests.push((
json!(1_000_000_000),
CelValue::Duration(Duration::seconds(1)),
));
}
for (expected, value) in tests.iter() {
assert_eq!(value.json().unwrap(), *expected, "{value:?}={expected:?}");
}
}
}