use std::collections::BTreeMap;
use serde::ser::{Serialize, SerializeMap, Serializer};
use serde_json::Value;
const TARGETING_KEY: &str = "targetingKey";
#[derive(Clone, Debug, Default, PartialEq)]
pub struct EvaluationContext {
targeting_key: Option<String>,
attributes: BTreeMap<String, Value>,
}
impl EvaluationContext {
pub fn new(targeting_key: impl Into<String>) -> Self {
Self {
targeting_key: Some(targeting_key.into()),
attributes: BTreeMap::new(),
}
}
#[must_use]
pub fn with(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
self.set(name, value);
self
}
pub fn set(&mut self, name: impl Into<String>, value: impl Into<Value>) {
let name = name.into();
let value = value.into();
if name == TARGETING_KEY {
if let Value::String(key) = value {
self.targeting_key = Some(key);
}
return;
}
self.attributes.insert(name, value);
}
pub fn set_targeting_key(&mut self, targeting_key: impl Into<String>) {
self.targeting_key = Some(targeting_key.into());
}
pub fn targeting_key(&self) -> Option<&str> {
self.targeting_key.as_deref()
}
pub fn attribute(&self, name: &str) -> Option<&Value> {
self.attributes.get(name)
}
pub fn attributes(&self) -> &BTreeMap<String, Value> {
&self.attributes
}
pub(crate) fn has_targeting_key(&self) -> bool {
self.targeting_key
.as_deref()
.is_some_and(|key| !key.is_empty())
}
}
impl Serialize for EvaluationContext {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(self.attributes.len() + 1))?;
map.serialize_entry(TARGETING_KEY, self.targeting_key.as_deref().unwrap_or(""))?;
for (name, value) in &self.attributes {
map.serialize_entry(name, value)?;
}
map.end()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn serializes_targeting_key_first() {
let context = EvaluationContext::new("user-1")
.with("country", "CA")
.with("age", 42);
assert_eq!(
serde_json::to_string(&context).unwrap(),
r#"{"targetingKey":"user-1","age":42,"country":"CA"}"#
);
}
#[test]
fn targeting_key_attribute_sets_the_key() {
let context = EvaluationContext::default().with("targetingKey", "user-2");
assert_eq!(context.targeting_key(), Some("user-2"));
assert!(context.attributes().is_empty());
assert_eq!(
serde_json::to_value(&context).unwrap(),
json!({"targetingKey": "user-2"})
);
}
#[test]
fn empty_key_is_missing() {
assert!(!EvaluationContext::new("").has_targeting_key());
assert!(!EvaluationContext::default().has_targeting_key());
}
}