Skip to main content

flagd_evaluation_engine/model/
flag_parser.rs

1use super::feature_flag::FeatureFlag;
2use super::feature_flag::ParsingResult;
3use crate::error::FlagdEvaluationError;
4use serde_json::{Map, Value};
5use std::collections::HashMap;
6
7pub struct FlagParser;
8
9impl FlagParser {
10    pub fn parse_string(configuration: &str) -> Result<ParsingResult, FlagdEvaluationError> {
11        let mut value: Value = serde_json::from_str(configuration)?;
12        Self::transpose_evaluator_refs(&mut value)?;
13
14        let obj = value
15            .as_object()
16            .ok_or_else(|| FlagdEvaluationError::Parse("Invalid JSON structure".to_string()))?;
17
18        let flags = obj
19            .get("flags")
20            .and_then(|v| v.as_object())
21            .ok_or_else(|| {
22                FlagdEvaluationError::Parse(
23                    "No flag configurations found in the payload".to_string(),
24                )
25            })?;
26
27        let flag_set_metadata = obj
28            .get("metadata")
29            .and_then(|v| v.as_object())
30            .map(Self::convert_map_to_hashmap)
31            .unwrap_or_default();
32
33        let mut flag_map = HashMap::new();
34        for (key, value) in flags {
35            let flag: FeatureFlag = serde_json::from_value(value.clone())?;
36            flag_map.insert(key.clone(), flag);
37        }
38
39        Ok(ParsingResult {
40            flags: flag_map,
41            flag_set_metadata,
42        })
43    }
44
45    fn transpose_evaluator_refs(configuration: &mut Value) -> Result<(), FlagdEvaluationError> {
46        let evaluators = configuration
47            .get("$evaluators")
48            .and_then(Value::as_object)
49            .cloned()
50            .unwrap_or_default();
51
52        let Some(flags) = configuration
53            .get_mut("flags")
54            .and_then(Value::as_object_mut)
55        else {
56            return Ok(());
57        };
58
59        for flag in flags.values_mut() {
60            if let Some(targeting) = flag.get_mut("targeting") {
61                Self::resolve_refs(targeting, &evaluators, &mut Vec::new())?;
62            }
63        }
64
65        Ok(())
66    }
67
68    fn resolve_refs(
69        value: &mut Value,
70        evaluators: &Map<String, Value>,
71        stack: &mut Vec<String>,
72    ) -> Result<(), FlagdEvaluationError> {
73        match value {
74            Value::Object(obj) => {
75                if obj.len() == 1
76                    && let Some(ref_name) = obj.get("$ref").and_then(Value::as_str)
77                {
78                    if stack.iter().any(|name| name == ref_name) {
79                        return Err(FlagdEvaluationError::Parse(format!(
80                            "Circular evaluator reference detected: {}",
81                            ref_name
82                        )));
83                    }
84
85                    let mut replacement = evaluators.get(ref_name).cloned().ok_or_else(|| {
86                        FlagdEvaluationError::Parse(format!(
87                            "Evaluator reference '{}' was not found",
88                            ref_name
89                        ))
90                    })?;
91
92                    stack.push(ref_name.to_string());
93                    Self::resolve_refs(&mut replacement, evaluators, stack)?;
94                    stack.pop();
95
96                    *value = replacement;
97                    return Ok(());
98                }
99
100                for child in obj.values_mut() {
101                    Self::resolve_refs(child, evaluators, stack)?;
102                }
103                Ok(())
104            }
105            Value::Array(items) => {
106                for item in items {
107                    Self::resolve_refs(item, evaluators, stack)?;
108                }
109                Ok(())
110            }
111            _ => Ok(()),
112        }
113    }
114
115    fn convert_map_to_hashmap(map: &Map<String, Value>) -> HashMap<String, serde_json::Value> {
116        map.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
117    }
118}
119
120#[cfg(test)]
121#[path = "flag_parser_test.rs"]
122mod tests;