use std::collections::HashMap;
use crate::EvaluationContextFieldValue;
#[derive(Clone, Default, PartialEq, Debug)]
pub struct EvaluationContext {
pub targeting_key: Option<String>,
pub custom_fields: HashMap<String, EvaluationContextFieldValue>,
}
impl EvaluationContext {
#[must_use]
pub fn with_targeting_key(mut self, targeting_key: impl Into<String>) -> Self {
self.targeting_key = Some(targeting_key.into());
self
}
#[must_use]
pub fn with_custom_field(
mut self,
key: impl Into<String>,
value: impl Into<EvaluationContextFieldValue>,
) -> Self {
self.add_custom_field(key, value);
self
}
pub fn add_custom_field(
&mut self,
key: impl Into<String>,
value: impl Into<EvaluationContextFieldValue>,
) {
self.custom_fields.insert(key.into(), value.into());
}
pub fn merge_missing(&mut self, other: &Self) {
if self.targeting_key.is_none() {
if let Some(targeting_key) = &other.targeting_key {
self.targeting_key = Some(targeting_key.clone());
}
}
other.custom_fields.iter().for_each(|(key, value)| {
if !self.custom_fields.contains_key(key) {
self.custom_fields.insert(key.clone(), value.clone());
}
});
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use spec::spec;
use time::OffsetDateTime;
use super::*;
#[test]
fn merge_missig_given_empty() {
let mut context = EvaluationContext::default()
.with_targeting_key("Targeting Key")
.with_custom_field("Some", "Value");
let expected = context.clone();
context.merge_missing(&EvaluationContext::default());
assert_eq!(context, expected);
}
#[test]
fn merge_missing_given_targeting_key() {
let mut context = EvaluationContext::default()
.with_targeting_key("Targeting Key")
.to_owned();
let expected = context.clone();
context.merge_missing(&EvaluationContext::default().with_targeting_key("Another Key"));
assert_eq!(context, expected);
}
#[test]
fn merge_missing_given_custom_fields() {
let mut context = EvaluationContext::default()
.with_targeting_key("Targeting Key")
.with_custom_field("Key", "Value");
context.merge_missing(
&EvaluationContext::default()
.with_custom_field("Key", "Another Value")
.with_custom_field("Another Key", "Value"),
);
assert_eq!(
context,
EvaluationContext::default()
.with_targeting_key("Targeting Key")
.with_custom_field("Key", "Value")
.with_custom_field("Another Key", "Value")
)
}
#[test]
fn merge_missing_given_full() {
let mut context = EvaluationContext::default();
let other = EvaluationContext::default()
.with_targeting_key("Targeting Key")
.with_custom_field("Key", "Value");
context.merge_missing(&other);
assert_eq!(context, other);
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct DummyStruct {
pub id: i64,
pub name: String,
}
#[spec(
number = "3.1.1",
text = "The evaluation context structure MUST define an optional targeting key field of type string, identifying the subject of the flag evaluation."
)]
#[spec(
number = "3.1.2",
text = "The evaluation context MUST support the inclusion of custom fields, having keys of type string, and values of type boolean | string | number | datetime | structure."
)]
#[spec(
number = "3.1.3",
text = "The evaluation context MUST support fetching the custom fields by key and also fetching all key value pairs."
)]
#[spec(
number = "3.1.4",
text = "The evaluation context fields MUST have an unique key."
)]
#[test]
fn fields_access() {
let now_time = OffsetDateTime::now_utc();
let struct_value = DummyStruct {
id: 200,
name: "Bob".to_string(),
};
let context = EvaluationContext::default()
.with_targeting_key("Key")
.with_custom_field("Bool", true)
.with_custom_field("Int", 100)
.with_custom_field("Float", 3.14)
.with_custom_field("String", "Hello")
.with_custom_field("Datetime", now_time)
.with_custom_field(
"Struct",
EvaluationContextFieldValue::Struct(Arc::new(struct_value.clone())),
);
assert_eq!(context.targeting_key, Some("Key".to_string()));
assert_eq!(
context.custom_fields.get("Int"),
Some(&EvaluationContextFieldValue::Int(100))
);
assert_eq!(
context.custom_fields.get("Float"),
Some(&EvaluationContextFieldValue::Float(3.14))
);
assert_eq!(
context.custom_fields.get("String"),
Some(&EvaluationContextFieldValue::String("Hello".to_string()))
);
assert_eq!(
context.custom_fields.get("Datetime"),
Some(&EvaluationContextFieldValue::DateTime(now_time))
);
assert_eq!(
*context
.custom_fields
.get("Struct")
.unwrap()
.as_struct()
.unwrap()
.downcast::<DummyStruct>()
.unwrap(),
struct_value
);
}
}