lmn_core/response_template/
extractor.rs1use 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
17pub 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}