Skip to main content

lmn_core/response_template/
extractor.rs

1use serde_json::Value;
2
3use crate::response_template::field::{ResponseFieldType, TrackedField};
4
5#[derive(Debug)]
6pub enum ExtractedValue {
7    String(String),
8    Float(f64),
9}
10
11#[derive(Debug)]
12pub struct ExtractionResult {
13    pub values: Vec<(String, ExtractedValue)>,
14    pub mismatches: Vec<String>,
15}
16
17/// Extracts tracked field values from a response body.
18/// Fields that are missing or have an unexpected type are recorded as mismatches.
19pub fn extract(body: &Value, fields: &[TrackedField]) -> ExtractionResult {
20    let mut values = Vec::new();
21    let mut mismatches = Vec::new();
22
23    for field in fields {
24        let path_label = field.path.join(".");
25
26        match resolve_path(body, &field.path) {
27            None => {
28                mismatches.push(path_label);
29            }
30            Some(value) => match (&field.field_type, value) {
31                (ResponseFieldType::String, Value::String(s)) => {
32                    values.push((path_label, ExtractedValue::String(s.clone())));
33                }
34                (ResponseFieldType::Float, Value::Number(n)) => {
35                    if let Some(f) = n.as_f64() {
36                        values.push((path_label, ExtractedValue::Float(f)));
37                    } else {
38                        mismatches.push(path_label);
39                    }
40                }
41                _ => {
42                    mismatches.push(path_label);
43                }
44            },
45        }
46    }
47
48    ExtractionResult { values, mismatches }
49}
50
51fn resolve_path<'a>(value: &'a Value, path: &[String]) -> Option<&'a Value> {
52    let mut current = value;
53    for key in path {
54        current = current.get(key)?;
55    }
56    Some(current)
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use serde_json::json;
63
64    #[test]
65    fn resolve_path_returns_none_for_missing_key() {
66        let body = json!({ "a": { "b": 1 } });
67        let path: Vec<String> = vec!["a".into(), "c".into()];
68        assert!(resolve_path(&body, &path).is_none());
69    }
70
71    #[test]
72    fn resolve_path_returns_deeply_nested_value() {
73        let body = json!({ "a": { "b": { "c": "deep" } } });
74        let path: Vec<String> = vec!["a".into(), "b".into(), "c".into()];
75        assert_eq!(resolve_path(&body, &path), Some(&json!("deep")));
76    }
77}