open_feature_flagd/resolver/in_process/targeting/
mod.rs1use 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}