Skip to content

Commit

Permalink
feat: add (optional) serde_json conversion support (#45)
Browse files Browse the repository at this point in the history
Signed-off-by: YUE Daian <[email protected]>
  • Loading branch information
sheepduke authored Dec 19, 2023
1 parent 76e17b5 commit 6dde097
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 3 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 2 additions & 2 deletions src/provider/no_op_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl FeatureProvider for NoOpProvider {
_flag_key: &str,
evaluation_context: &EvaluationContext,
) -> Result<ResolutionDetails<bool>, 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);
Expand All @@ -133,7 +133,7 @@ impl FeatureProvider for NoOpProvider {
_flag_key: &str,
evaluation_context: &EvaluationContext,
) -> Result<ResolutionDetails<i64>, 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);
Expand Down
111 changes: 111 additions & 0 deletions src/serde_json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use crate::{EvaluationError, EvaluationResult, StructValue, Value};

impl TryFrom<serde_json::Value> for Value {
type Error = EvaluationError;

fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
json_value_to_value(&value)
}
}

fn json_value_to_value(value: &serde_json::Value) -> EvaluationResult<Value> {
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::<Result<Vec<_>, _>>()?,
)),
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());
}
}

0 comments on commit 6dde097

Please sign in to comment.