diff --git a/Cargo.toml b/Cargo.toml index a48102c..ab797df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,15 @@ maintenance = { status = "actively-developed" } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0" async-trait = "0.1.74" lazy_static = "1.4.0" +serde_json = { version = "1.0.108", optional = true } time = "0.3.30" tokio = { version = "1.35.0", features = [ "full" ] } typed-builder = "0.18.0" [dev-dependencies] spec = { path = "spec" } + +[features] +serde_json = ["dep:serde_json"] diff --git a/src/lib.rs b/src/lib.rs index effe421..fd2c5e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,3 +28,7 @@ pub use evaluation::*; /// Feature provider related. pub mod provider; + +/// Optional support for [`serde_json::Value`]. +#[cfg(feature = "serde_json")] +pub mod serde_json; diff --git a/src/provider/no_op_provider.rs b/src/provider/no_op_provider.rs index 2e9663c..e348408 100644 --- a/src/provider/no_op_provider.rs +++ b/src/provider/no_op_provider.rs @@ -115,7 +115,7 @@ impl FeatureProvider for NoOpProvider { _flag_key: &str, evaluation_context: &EvaluationContext, ) -> Result, EvaluationError> { - let (reason, variant) = Self::create_reason_variant(self.bool_value == Default::default()); + let (reason, variant) = Self::create_reason_variant(self.bool_value == bool::default()); let mut flag_metadata = FlagMetadata::default().with_value("Type", "Bool"); Self::populate_evaluation_context_values(&mut flag_metadata, evaluation_context); @@ -133,7 +133,7 @@ impl FeatureProvider for NoOpProvider { _flag_key: &str, evaluation_context: &EvaluationContext, ) -> Result, EvaluationError> { - let (reason, variant) = Self::create_reason_variant(self.int_value == Default::default()); + let (reason, variant) = Self::create_reason_variant(self.int_value == i64::default()); let mut flag_metadata = FlagMetadata::default().with_value("Type", "Int"); Self::populate_evaluation_context_values(&mut flag_metadata, evaluation_context); diff --git a/src/serde_json.rs b/src/serde_json.rs new file mode 100644 index 0000000..a3c246e --- /dev/null +++ b/src/serde_json.rs @@ -0,0 +1,111 @@ +use crate::{EvaluationError, EvaluationResult, StructValue, Value}; + +impl TryFrom for Value { + type Error = EvaluationError; + + fn try_from(value: serde_json::Value) -> Result { + json_value_to_value(&value) + } +} + +fn json_value_to_value(value: &serde_json::Value) -> EvaluationResult { + match value { + serde_json::Value::Bool(value) => Ok(Value::Bool(*value)), + serde_json::Value::Number(value) if value.is_i64() => { + Ok(Value::Int(value.as_i64().unwrap())) + } + serde_json::Value::Number(value) if value.is_f64() => { + Ok(Value::Float(value.as_f64().unwrap())) + } + serde_json::Value::String(value) => Ok(Value::String(value.to_string())), + serde_json::Value::Array(array) => Ok(Value::Array( + array + .iter() + .map(|x| json_value_to_value(x)) + .collect::, _>>()?, + )), + serde_json::Value::Object(object) => { + let mut result = StructValue::default(); + + for (key, value) in object { + result.add_field(key, json_value_to_value(value)?); + } + + Ok(Value::Struct(result)) + } + _ => Err(EvaluationError::builder() + .code(crate::EvaluationErrorCode::TypeMismatch) + .message("Failed to convert from JSON") + .build()), + } +} + +#[cfg(test)] +mod tests { + use crate::{StructValue, Value}; + + #[test] + fn convert_data() { + let json = serde_json::json!({ + "id": 100, + "name": "Bob", + "age": 23.5, + "active": true, + "phones": [ + "12345", + "67890", + [123, 456] + ], + "children": [{ + "name": "Carl", + "gender": "male" + }, { + "name": "Dina", + "gender": "female", + }] + }); + + let expected_value = Value::Struct( + StructValue::default() + .with_field("id", 100) + .with_field("name", "Bob") + .with_field("age", 23.5) + .with_field("active", true) + .with_field( + "phones", + Value::Array(vec![ + "12345".into(), + "67890".into(), + Value::Array(vec![123.into(), 456.into()]), + ]), + ) + .with_field( + "children", + Value::Array(vec![ + Value::Struct( + StructValue::default() + .with_field("name", "Carl") + .with_field("gender", "male"), + ), + Value::Struct( + StructValue::default() + .with_field("name", "Dina") + .with_field("gender", "female"), + ), + ]), + ), + ); + + let actual_value: Value = json.try_into().unwrap(); + + assert_eq!(expected_value, actual_value); + } + + #[test] + fn convert_invalid_data() { + let json = serde_json::Value::Null; + let result = Value::try_from(json); + + assert!(result.is_err()); + } +}