open_feature_flagd/resolver/in_process/targeting/
mod.rs1use crate::error::FlagdError;
2use datalogic_rs::DataLogic;
3use open_feature::{EvaluationContext, EvaluationContextFieldValue};
4use serde_json::Value;
5use std::sync::Arc;
6
7mod fractional;
8mod semver;
9
10use fractional::FractionalOperator;
11use semver::SemVerOperator;
12
13pub struct Operator {
19 logic: Arc<DataLogic>,
20}
21
22impl Default for Operator {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl Operator {
29 pub fn new() -> Self {
30 let mut logic = DataLogic::new();
32
33 logic.add_operator("fractional".to_string(), Box::new(FractionalOperator));
35 logic.add_operator("sem_ver".to_string(), Box::new(SemVerOperator));
36
37 Operator {
38 logic: Arc::new(logic),
39 }
40 }
41
42 pub fn apply(
43 &self,
44 flag_key: &str,
45 targeting_rule: &str,
46 ctx: &EvaluationContext,
47 ) -> Result<Option<String>, FlagdError> {
48 let rule_value: Value = serde_json::from_str(targeting_rule)?;
50
51 let compiled = self.logic.compile(&rule_value).map_err(|e| {
53 FlagdError::Provider(format!("Failed to compile targeting rule: {:?}", e))
54 })?;
55
56 let context_data = Arc::new(self.build_context(flag_key, ctx));
58
59 match self.logic.evaluate(&compiled, context_data) {
61 Ok(result) => {
62 match result {
64 Value::String(s) => Ok(Some(s)),
65 Value::Null => Ok(None),
66 _ => Ok(Some(result.to_string())),
67 }
68 }
69 Err(e) => {
70 tracing::debug!("DataLogic evaluation error: {:?}", e);
72 Ok(None)
73 }
74 }
75 }
76
77 fn build_context(&self, flag_key: &str, ctx: &EvaluationContext) -> Value {
78 let mut root = serde_json::Map::new();
80
81 if let Some(targeting_key) = &ctx.targeting_key {
83 root.insert(
84 "targetingKey".to_string(),
85 Value::String(targeting_key.clone()),
86 );
87 }
88
89 let timestamp = std::time::SystemTime::now()
91 .duration_since(std::time::UNIX_EPOCH)
92 .unwrap()
93 .as_secs();
94
95 let mut flagd_props = serde_json::Map::new();
97 flagd_props.insert("flagKey".to_string(), Value::String(flag_key.to_string()));
98 flagd_props.insert(
99 "timestamp".to_string(),
100 Value::Number(serde_json::Number::from(timestamp)),
101 );
102
103 root.insert("$flagd".to_string(), Value::Object(flagd_props));
105
106 for (key, value) in &ctx.custom_fields {
108 root.insert(key.clone(), self.evaluation_context_value_to_json(value));
109 }
110
111 Value::Object(root)
113 }
114
115 fn evaluation_context_value_to_json(&self, value: &EvaluationContextFieldValue) -> Value {
117 match value {
118 EvaluationContextFieldValue::String(s) => Value::String(s.clone()),
119 EvaluationContextFieldValue::Bool(b) => Value::Bool(*b),
120 EvaluationContextFieldValue::Int(i) => Value::Number(serde_json::Number::from(*i)),
121 EvaluationContextFieldValue::Float(f) => {
122 if let Some(num) = serde_json::Number::from_f64(*f) {
123 Value::Number(num)
124 } else {
125 Value::Null
126 }
127 }
128 EvaluationContextFieldValue::DateTime(dt) => Value::String(dt.to_string()),
129 EvaluationContextFieldValue::Struct(s) => {
130 if let Some(struct_value) = s.downcast_ref::<open_feature::StructValue>() {
132 self.struct_value_to_json(struct_value)
133 } else {
134 Value::Object(serde_json::Map::new())
136 }
137 }
138 }
139 }
140
141 fn struct_value_to_json(&self, struct_value: &open_feature::StructValue) -> Value {
143 let mut map = serde_json::Map::new();
144 for (key, value) in &struct_value.fields {
145 map.insert(key.clone(), self.open_feature_value_to_json(value));
146 }
147 Value::Object(map)
148 }
149
150 fn open_feature_value_to_json(&self, value: &open_feature::Value) -> Value {
152 match value {
153 open_feature::Value::String(s) => Value::String(s.clone()),
154 open_feature::Value::Bool(b) => Value::Bool(*b),
155 open_feature::Value::Int(i) => Value::Number(serde_json::Number::from(*i)),
156 open_feature::Value::Float(f) => {
157 if let Some(num) = serde_json::Number::from_f64(*f) {
158 Value::Number(num)
159 } else {
160 Value::Null
161 }
162 }
163 open_feature::Value::Struct(s) => self.struct_value_to_json(s),
164 open_feature::Value::Array(arr) => Value::Array(
165 arr.iter()
166 .map(|v| self.open_feature_value_to_json(v))
167 .collect(),
168 ),
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use open_feature::{EvaluationContext, StructValue, Value as OFValue};
177 use std::collections::HashMap;
178
179 #[test]
180 fn test_build_context_with_targeting_key() {
181 let operator = Operator::new();
182 let ctx = EvaluationContext::default().with_targeting_key("user-123");
183
184 let result = operator.build_context("test-flag", &ctx);
185
186 assert!(result.is_object());
187 let obj = result.as_object().unwrap();
188 assert_eq!(obj.get("targetingKey").unwrap(), "user-123");
189 assert!(obj.contains_key("$flagd"));
190
191 let flagd = obj.get("$flagd").unwrap().as_object().unwrap();
192 assert_eq!(flagd.get("flagKey").unwrap(), "test-flag");
193 assert!(flagd.contains_key("timestamp"));
194 }
195
196 #[test]
197 fn test_build_context_with_custom_fields() {
198 let operator = Operator::new();
199 let ctx = EvaluationContext::default()
200 .with_custom_field("string_field", "value")
201 .with_custom_field("int_field", 42i64)
202 .with_custom_field("bool_field", true)
203 .with_custom_field("float_field", 3.14f64);
204
205 let result = operator.build_context("test-flag", &ctx);
206 let obj = result.as_object().unwrap();
207
208 assert_eq!(obj.get("string_field").unwrap(), "value");
209 assert_eq!(obj.get("int_field").unwrap(), 42);
210 assert_eq!(obj.get("bool_field").unwrap(), true);
211 assert_eq!(obj.get("float_field").unwrap(), 3.14);
212 }
213
214 #[test]
215 fn test_open_feature_value_to_json_primitives() {
216 let operator = Operator::new();
217
218 assert_eq!(
219 operator.open_feature_value_to_json(&OFValue::String("test".to_string())),
220 Value::String("test".to_string())
221 );
222 assert_eq!(
223 operator.open_feature_value_to_json(&OFValue::Bool(true)),
224 Value::Bool(true)
225 );
226 assert_eq!(
227 operator.open_feature_value_to_json(&OFValue::Int(42)),
228 Value::Number(42.into())
229 );
230 assert_eq!(
231 operator.open_feature_value_to_json(&OFValue::Float(3.14)),
232 Value::Number(serde_json::Number::from_f64(3.14).unwrap())
233 );
234 }
235
236 #[test]
237 fn test_struct_value_to_json() {
238 let operator = Operator::new();
239
240 let mut fields = HashMap::new();
241 fields.insert("name".to_string(), OFValue::String("test".to_string()));
242 fields.insert("count".to_string(), OFValue::Int(5));
243 fields.insert("enabled".to_string(), OFValue::Bool(true));
244
245 let struct_value = StructValue { fields };
246 let result = operator.struct_value_to_json(&struct_value);
247
248 assert!(result.is_object());
249 let obj = result.as_object().unwrap();
250 assert_eq!(obj.get("name").unwrap(), "test");
251 assert_eq!(obj.get("count").unwrap(), 5);
252 assert_eq!(obj.get("enabled").unwrap(), true);
253 }
254
255 #[test]
256 fn test_nested_struct_value_to_json() {
257 let operator = Operator::new();
258
259 let mut inner_fields = HashMap::new();
261 inner_fields.insert(
262 "inner_key".to_string(),
263 OFValue::String("inner_value".to_string()),
264 );
265 let inner_struct = StructValue {
266 fields: inner_fields,
267 };
268
269 let mut outer_fields = HashMap::new();
270 outer_fields.insert(
271 "outer_key".to_string(),
272 OFValue::String("outer_value".to_string()),
273 );
274 outer_fields.insert("nested".to_string(), OFValue::Struct(inner_struct));
275
276 let outer_struct = StructValue {
277 fields: outer_fields,
278 };
279 let result = operator.struct_value_to_json(&outer_struct);
280
281 assert!(result.is_object());
282 let obj = result.as_object().unwrap();
283 assert_eq!(obj.get("outer_key").unwrap(), "outer_value");
284
285 let nested = obj.get("nested").unwrap().as_object().unwrap();
286 assert_eq!(nested.get("inner_key").unwrap(), "inner_value");
287 }
288
289 #[test]
290 fn test_array_value_to_json() {
291 let operator = Operator::new();
292
293 let array = vec![
294 OFValue::String("a".to_string()),
295 OFValue::Int(1),
296 OFValue::Bool(true),
297 ];
298
299 let result = operator.open_feature_value_to_json(&OFValue::Array(array));
300
301 assert!(result.is_array());
302 let arr = result.as_array().unwrap();
303 assert_eq!(arr.len(), 3);
304 assert_eq!(arr[0], "a");
305 assert_eq!(arr[1], 1);
306 assert_eq!(arr[2], true);
307 }
308
309 #[test]
310 fn test_apply_simple_targeting_rule() {
311 let operator = Operator::new();
312 let ctx = EvaluationContext::default().with_custom_field("tier", "premium");
313
314 let rule = r#"{
316 "if": [
317 {"==": [{"var": "tier"}, "premium"]},
318 "gold",
319 "silver"
320 ]
321 }"#;
322
323 let result = operator.apply("test-flag", rule, &ctx).unwrap();
324 assert_eq!(result, Some("gold".to_string()));
325 }
326
327 #[test]
328 fn test_apply_targeting_rule_with_default() {
329 let operator = Operator::new();
330 let ctx = EvaluationContext::default().with_custom_field("tier", "basic");
331
332 let rule = r#"{
333 "if": [
334 {"==": [{"var": "tier"}, "premium"]},
335 "gold",
336 "silver"
337 ]
338 }"#;
339
340 let result = operator.apply("test-flag", rule, &ctx).unwrap();
341 assert_eq!(result, Some("silver".to_string()));
342 }
343
344 #[test]
345 fn test_apply_empty_targeting_returns_none() {
346 let operator = Operator::new();
347 let ctx = EvaluationContext::default();
348
349 let rule = "null";
350 let result = operator.apply("test-flag", rule, &ctx).unwrap();
351 assert_eq!(result, None);
352 }
353}