open_feature/evaluation/
context.rs1use std::collections::HashMap;
2
3use crate::EvaluationContextFieldValue;
4
5#[derive(Clone, Default, PartialEq, Debug)]
15pub struct EvaluationContext {
16 pub targeting_key: Option<String>,
21
22 pub custom_fields: HashMap<String, EvaluationContextFieldValue>,
25}
26
27impl EvaluationContext {
28 #[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 #[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 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 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}