Skip to main content

brainwires_proxy/convert/
json_transform.rs

1//! JSON structural transformation utilities.
2
3use crate::error::{ProxyError, ProxyResult};
4use bytes::Bytes;
5use serde_json::Value;
6
7/// A rule for transforming a JSON structure.
8#[derive(Debug, Clone)]
9pub enum JsonRule {
10    /// Rename a top-level field.
11    RenameField { from: String, to: String },
12    /// Remove a field by path (dot-separated).
13    RemoveField(String),
14    /// Set a field to a constant value.
15    SetField { path: String, value: Value },
16    /// Wrap the entire body inside a new object field.
17    WrapIn(String),
18    /// Unwrap: extract the value at a given path and use it as the new root.
19    Unwrap(String),
20}
21
22/// Applies a sequence of JSON transformation rules.
23pub struct JsonTransformer {
24    rules: Vec<JsonRule>,
25}
26
27impl JsonTransformer {
28    pub fn new(rules: Vec<JsonRule>) -> Self {
29        Self { rules }
30    }
31
32    pub fn transform(&self, input: &[u8]) -> ProxyResult<Bytes> {
33        let mut value: Value =
34            serde_json::from_slice(input).map_err(|e| ProxyError::Conversion(e.to_string()))?;
35
36        for rule in &self.rules {
37            value = apply_rule(value, rule)?;
38        }
39
40        let out = serde_json::to_vec(&value).map_err(|e| ProxyError::Conversion(e.to_string()))?;
41        Ok(Bytes::from(out))
42    }
43}
44
45fn apply_rule(mut value: Value, rule: &JsonRule) -> ProxyResult<Value> {
46    match rule {
47        JsonRule::RenameField { from, to } => {
48            if let Value::Object(ref mut map) = value
49                && let Some(v) = map.remove(from)
50            {
51                map.insert(to.clone(), v);
52            }
53            Ok(value)
54        }
55        JsonRule::RemoveField(path) => {
56            remove_at_path(&mut value, path);
57            Ok(value)
58        }
59        JsonRule::SetField { path, value: val } => {
60            set_at_path(&mut value, path, val.clone());
61            Ok(value)
62        }
63        JsonRule::WrapIn(key) => {
64            let mut map = serde_json::Map::new();
65            map.insert(key.clone(), value);
66            Ok(Value::Object(map))
67        }
68        JsonRule::Unwrap(path) => get_at_path(&value, path)
69            .cloned()
70            .ok_or_else(|| ProxyError::Conversion(format!("path not found: {path}"))),
71    }
72}
73
74fn get_at_path<'a>(value: &'a Value, path: &str) -> Option<&'a Value> {
75    let mut current = value;
76    for segment in path.split('.') {
77        current = current.get(segment)?;
78    }
79    Some(current)
80}
81
82fn set_at_path(value: &mut Value, path: &str, new_val: Value) {
83    let segments: Vec<&str> = path.split('.').collect();
84    if segments.is_empty() {
85        return;
86    }
87
88    let mut current = value;
89    for segment in &segments[..segments.len() - 1] {
90        current = match current {
91            Value::Object(map) => map
92                .entry(*segment)
93                .or_insert_with(|| Value::Object(serde_json::Map::new())),
94            _ => return,
95        };
96    }
97
98    if let Value::Object(map) = current {
99        map.insert(segments[segments.len() - 1].to_string(), new_val);
100    }
101}
102
103fn remove_at_path(value: &mut Value, path: &str) {
104    let segments: Vec<&str> = path.split('.').collect();
105    if segments.is_empty() {
106        return;
107    }
108
109    let mut current = value;
110    for segment in &segments[..segments.len() - 1] {
111        current = match current {
112            Value::Object(map) => match map.get_mut(*segment) {
113                Some(v) => v,
114                None => return,
115            },
116            _ => return,
117        };
118    }
119
120    if let Value::Object(map) = current {
121        map.remove(segments[segments.len() - 1]);
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_rename_field() {
131        let input = br#"{"old_name": 42, "keep": true}"#;
132        let t = JsonTransformer::new(vec![JsonRule::RenameField {
133            from: "old_name".into(),
134            to: "new_name".into(),
135        }]);
136        let out = t.transform(input).unwrap();
137        let v: Value = serde_json::from_slice(&out).unwrap();
138        assert_eq!(v["new_name"], 42);
139        assert!(v.get("old_name").is_none());
140    }
141
142    #[test]
143    fn test_wrap_and_unwrap() {
144        let input = br#"{"data": [1,2,3]}"#;
145        let t = JsonTransformer::new(vec![JsonRule::WrapIn("wrapper".into())]);
146        let out = t.transform(input).unwrap();
147        let v: Value = serde_json::from_slice(&out).unwrap();
148        assert!(v["wrapper"]["data"].is_array());
149
150        let t2 = JsonTransformer::new(vec![JsonRule::Unwrap("wrapper".into())]);
151        let out2 = t2.transform(&out).unwrap();
152        let v2: Value = serde_json::from_slice(&out2).unwrap();
153        assert_eq!(v2["data"], serde_json::json!([1, 2, 3]));
154    }
155}