open_feature/evaluation/
context.rs

1use std::collections::HashMap;
2
3use crate::EvaluationContextFieldValue;
4
5/// The evaluation context provides ambient information for the purposes of flag evaluation.
6/// Contextual data may be used as the basis for targeting, including rule-based evaluation,
7/// overrides for specific subjects, or fractional flag evaluation.
8///
9/// The context might contain information about the end-user, the application, the host, or any
10/// other ambient data that might be useful in flag evaluation. For example, a flag system might
11/// define rules that return a specific value based on the user's email address, locale, or the
12/// time of day. The context provides this information. The context can be optionally provided at
13/// evaluation, and mutated in before hooks.
14#[derive(Clone, Default, PartialEq, Debug)]
15pub struct EvaluationContext {
16    /// The targeting key uniquely identifies the subject (end-user, or client service) of a flag
17    /// evaluation. Providers may require this field for fractional flag evaluation, rules, or
18    /// overrides targeting specific users. Such providers may behave unpredictably if a targeting
19    /// key is not specified at flag resolution.
20    pub targeting_key: Option<String>,
21
22    /// The evaluation context MUST support the inclusion of custom fields, having keys of type
23    /// string, and values of type boolean | string | number | datetime | structure.
24    pub custom_fields: HashMap<String, EvaluationContextFieldValue>,
25}
26
27impl EvaluationContext {
28    /// Set the `targeting_key` of the evaluation context.
29    #[must_use]
30    pub fn with_targeting_key(mut self, targeting_key: impl Into<String>) -> Self {
31        self.targeting_key = Some(targeting_key.into());
32        self
33    }
34
35    /// Add `key` and `value` to the custom field of evaluation context.
36    #[must_use]
37    pub fn with_custom_field(
38        mut self,
39        key: impl Into<String>,
40        value: impl Into<EvaluationContextFieldValue>,
41    ) -> Self {
42        self.add_custom_field(key, value);
43        self
44    }
45
46    /// Add `key` and `value` to the custom field of evaluation context.
47    pub fn add_custom_field(
48        &mut self,
49        key: impl Into<String>,
50        value: impl Into<EvaluationContextFieldValue>,
51    ) {
52        self.custom_fields.insert(key.into(), value.into());
53    }
54
55    /// Merge `other` into `self` if corresponding field is not set.
56    /// Meaning values set into `self` has higher precedence.
57    pub fn merge_missing(&mut self, other: &Self) {
58        if self.targeting_key.is_none() {
59            if let Some(targeting_key) = &other.targeting_key {
60                self.targeting_key = Some(targeting_key.clone());
61            }
62        }
63
64        other.custom_fields.iter().for_each(|(key, value)| {
65            if !self.custom_fields.contains_key(key) {
66                self.custom_fields.insert(key.clone(), value.clone());
67            }
68        });
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use std::sync::Arc;
75
76    use spec::spec;
77    use time::OffsetDateTime;
78
79    use super::*;
80
81    #[test]
82    fn merge_missig_given_empty() {
83        let mut context = EvaluationContext::default()
84            .with_targeting_key("Targeting Key")
85            .with_custom_field("Some", "Value");
86
87        let expected = context.clone();
88
89        context.merge_missing(&EvaluationContext::default());
90
91        assert_eq!(context, expected);
92    }
93
94    #[test]
95    fn merge_missing_given_targeting_key() {
96        let mut context = EvaluationContext::default()
97            .with_targeting_key("Targeting Key")
98            .to_owned();
99
100        let expected = context.clone();
101
102        context.merge_missing(&EvaluationContext::default().with_targeting_key("Another Key"));
103
104        assert_eq!(context, expected);
105    }
106
107    #[test]
108    fn merge_missing_given_custom_fields() {
109        let mut context = EvaluationContext::default()
110            .with_targeting_key("Targeting Key")
111            .with_custom_field("Key", "Value");
112
113        context.merge_missing(
114            &EvaluationContext::default()
115                .with_custom_field("Key", "Another Value")
116                .with_custom_field("Another Key", "Value"),
117        );
118
119        assert_eq!(
120            context,
121            EvaluationContext::default()
122                .with_targeting_key("Targeting Key")
123                .with_custom_field("Key", "Value")
124                .with_custom_field("Another Key", "Value")
125        )
126    }
127
128    #[test]
129    fn merge_missing_given_full() {
130        let mut context = EvaluationContext::default();
131
132        let other = EvaluationContext::default()
133            .with_targeting_key("Targeting Key")
134            .with_custom_field("Key", "Value");
135
136        context.merge_missing(&other);
137
138        assert_eq!(context, other);
139    }
140
141    #[derive(Clone, PartialEq, Eq, Debug)]
142    pub struct DummyStruct {
143        pub id: i64,
144        pub name: String,
145    }
146
147    #[spec(
148        number = "3.1.1",
149        text = "The evaluation context structure MUST define an optional targeting key field of type string, identifying the subject of the flag evaluation."
150    )]
151    #[spec(
152        number = "3.1.2",
153        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."
154    )]
155    #[spec(
156        number = "3.1.3",
157        text = "The evaluation context MUST support fetching the custom fields by key and also fetching all key value pairs."
158    )]
159    #[spec(
160        number = "3.1.4",
161        text = "The evaluation context fields MUST have an unique key."
162    )]
163    #[test]
164    fn fields_access() {
165        let now_time = OffsetDateTime::now_utc();
166        let struct_value = DummyStruct {
167            id: 200,
168            name: "Bob".to_string(),
169        };
170
171        let context = EvaluationContext::default()
172            .with_targeting_key("Key")
173            .with_custom_field("Bool", true)
174            .with_custom_field("Int", 100)
175            .with_custom_field("Float", 3.14)
176            .with_custom_field("String", "Hello")
177            .with_custom_field("Datetime", now_time)
178            .with_custom_field(
179                "Struct",
180                EvaluationContextFieldValue::Struct(Arc::new(struct_value.clone())),
181            );
182
183        assert_eq!(context.targeting_key, Some("Key".to_string()));
184        assert_eq!(
185            context.custom_fields.get("Int"),
186            Some(&EvaluationContextFieldValue::Int(100))
187        );
188        assert_eq!(
189            context.custom_fields.get("Float"),
190            Some(&EvaluationContextFieldValue::Float(3.14))
191        );
192        assert_eq!(
193            context.custom_fields.get("String"),
194            Some(&EvaluationContextFieldValue::String("Hello".to_string()))
195        );
196        assert_eq!(
197            context.custom_fields.get("Datetime"),
198            Some(&EvaluationContextFieldValue::DateTime(now_time))
199        );
200        assert_eq!(
201            *context
202                .custom_fields
203                .get("Struct")
204                .unwrap()
205                .as_struct()
206                .unwrap()
207                .downcast::<DummyStruct>()
208                .unwrap(),
209            struct_value
210        );
211    }
212}