open_feature_flagd/resolver/in_process/targeting/
mod.rs

1use anyhow::Result;
2use datalogic_rs::{JsonLogic, Rule};
3use open_feature::{EvaluationContext, EvaluationContextFieldValue};
4use serde_json::{json, Map, Value};
5
6mod fractional;
7mod semver;
8mod string_comp;
9
10use fractional::Fractional;
11use semver::SemVer;
12use string_comp::{StringComp, StringCompType};
13
14#[derive(Clone)]
15pub struct Operator {}
16
17impl Operator {
18    pub fn new() -> Operator {
19        Operator {}
20    }
21
22    pub fn apply(
23        &self,
24        flag_key: &str,
25        targeting_rule: &str,
26        ctx: &EvaluationContext,
27    ) -> Result<Option<String>> {
28        let rule_value: Value = serde_json::from_str(targeting_rule)?;
29        let result = self.evaluate_rule(&rule_value, flag_key, ctx)?;
30        Ok(result.as_str().map(String::from))
31    }
32
33    fn evaluate_rule(
34        &self,
35        rule: &Value,
36        flag_key: &str,
37        ctx: &EvaluationContext,
38    ) -> Result<Value> {
39        match rule {
40            Value::Object(map) => {
41                if let Some((op, args)) = map.iter().next() {
42                    match op.as_str() {
43                        "if" => {
44                            if let Value::Array(conditions) = args {
45                                if conditions.len() >= 2 {
46                                    let condition =
47                                        self.evaluate_rule(&conditions[0], flag_key, ctx)?;
48                                    match condition {
49                                        Value::Bool(true) => Ok(conditions[1].clone()),
50                                        Value::Bool(false) if conditions.len() > 2 => {
51                                            Ok(conditions[2].clone())
52                                        }
53                                        _ => Ok(Value::Null),
54                                    }
55                                } else {
56                                    Ok(Value::Null)
57                                }
58                            } else {
59                                Ok(Value::Null)
60                            }
61                        }
62                        "fractional" => {
63                            if let Value::Array(args) = args {
64                                let data = self.build_evaluation_data(flag_key, ctx);
65                                let data_map = data
66                                    .as_object()
67                                    .map(|obj| {
68                                        obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
69                                    })
70                                    .unwrap_or_default();
71                                Fractional::evaluate(args, &data_map)
72                            } else {
73                                Ok(Value::Null)
74                            }
75                        }
76                        "ends_with" => {
77                            if let Value::Array(args) = args {
78                                let mut resolved_args = Vec::new();
79                                for arg in args {
80                                    match arg {
81                                        Value::Object(_) => {
82                                            resolved_args
83                                                .push(self.evaluate_rule(arg, flag_key, ctx)?);
84                                        }
85                                        _ => resolved_args.push(arg.clone()),
86                                    }
87                                }
88                                StringComp::evaluate(StringCompType::EndsWith, &resolved_args)
89                            } else {
90                                Ok(Value::Null)
91                            }
92                        }
93                        "starts_with" => {
94                            if let Value::Array(args) = args {
95                                let mut resolved_args = Vec::new();
96                                for arg in args {
97                                    match arg {
98                                        Value::Object(_) => {
99                                            resolved_args
100                                                .push(self.evaluate_rule(arg, flag_key, ctx)?);
101                                        }
102                                        _ => resolved_args.push(arg.clone()),
103                                    }
104                                }
105                                StringComp::evaluate(StringCompType::StartsWith, &resolved_args)
106                            } else {
107                                Ok(Value::Null)
108                            }
109                        }
110                        "sem_ver" => {
111                            if let Value::Array(args) = args {
112                                let mut resolved_args = Vec::new();
113                                for arg in args {
114                                    match arg {
115                                        Value::Object(_) => {
116                                            resolved_args
117                                                .push(self.evaluate_rule(arg, flag_key, ctx)?);
118                                        }
119                                        _ => resolved_args.push(arg.clone()),
120                                    }
121                                }
122                                SemVer::evaluate(&resolved_args)
123                            } else {
124                                Ok(Value::Null)
125                            }
126                        }
127                        "var" => {
128                            if let Some(path) = args.as_str() {
129                                let data = self.build_evaluation_data(flag_key, ctx);
130                                Ok(data.get(path).cloned().unwrap_or(Value::Null))
131                            } else {
132                                Ok(Value::Null)
133                            }
134                        }
135                        _ => {
136                            let rule = Rule::from_value(rule)?;
137                            let data = self.build_evaluation_data(flag_key, ctx);
138                            Ok(JsonLogic::apply(&rule, &data)?)
139                        }
140                    }
141                } else {
142                    Ok(rule.clone())
143                }
144            }
145            _ => Ok(rule.clone()),
146        }
147    }
148
149    fn build_evaluation_data(&self, flag_key: &str, ctx: &EvaluationContext) -> Value {
150        let mut data = Map::new();
151
152        if let Some(targeting_key) = &ctx.targeting_key {
153            data.insert(
154                "targetingKey".to_string(),
155                Value::String(targeting_key.clone()),
156            );
157        }
158
159        let flagd_props = json!({
160            "flagKey": flag_key,
161            "timestamp": std::time::SystemTime::now()
162                .duration_since(std::time::UNIX_EPOCH)
163                .unwrap()
164                .as_secs()
165        });
166        data.insert("$flagd".to_string(), flagd_props);
167
168        for (key, value) in &ctx.custom_fields {
169            data.insert(key.clone(), context_value_to_json(value));
170        }
171
172        Value::Object(data)
173    }
174}
175
176fn context_value_to_json(value: &EvaluationContextFieldValue) -> Value {
177    match value {
178        EvaluationContextFieldValue::String(s) => Value::String(s.clone()),
179        EvaluationContextFieldValue::Bool(b) => Value::Bool(*b),
180        EvaluationContextFieldValue::Int(i) => Value::Number((*i).into()),
181        EvaluationContextFieldValue::Float(f) => {
182            if let Some(n) = serde_json::Number::from_f64(*f) {
183                Value::Number(n)
184            } else {
185                Value::Null
186            }
187        }
188        EvaluationContextFieldValue::DateTime(dt) => Value::String(dt.to_string()),
189        EvaluationContextFieldValue::Struct(s) => Value::String(format!("{:?}", s)),
190    }
191}