fluvio_jolt/
lib.rs

1mod spec;
2mod shift;
3mod default;
4mod remove;
5mod pointer;
6mod transform;
7mod error;
8#[cfg(not(feature = "fuzz"))]
9mod dsl;
10#[cfg(feature = "fuzz")]
11pub mod dsl;
12
13use serde_json::{Map, Value};
14use serde_json::map::Entry;
15use transform::Transform;
16
17use crate::default::default;
18use crate::remove::remove;
19use crate::spec::SpecEntry;
20
21pub use spec::TransformSpec;
22use crate::pointer::JsonPointer;
23
24pub use error::{Error, Result};
25
26/// Perform JSON to JSON transformation where the "specification" is a JSON.
27///
28/// Inspired by Java library [Jolt](https://github.com/bazaarvoice/jolt).
29///
30/// The transformation can compose of many operations that are chained together.
31///
32/// ### Operations
33/// 1. [`shift`](TransformSpec#shift-operation): copy data from the input tree and put it the output tree
34/// 2. [`default`](TransformSpec#default-operation): apply default values to the tree
35/// 3. [`remove`](TransformSpec#remove-operation): remove data from the tree
36///
37/// For example, if you want to repack your JSON record, you can do the following:
38/// ```
39/// use serde_json::{json, Value};
40/// use fluvio_jolt::{transform, TransformSpec};
41///
42/// let input: Value = serde_json::from_str(r#"
43///     {
44///         "id": 1,
45///         "name": "John Smith",
46///         "account": {
47///             "id": 1000,
48///             "type": "Checking"
49///         }
50///     }
51/// "#).unwrap();
52///
53/// let spec: TransformSpec =
54/// serde_json::from_str(r#"[
55///     {
56///       "operation": "shift",
57///       "spec": {
58///         "name": "data.name",
59///         "account": "data.account"
60///       }
61///     }
62///   ]"#).unwrap();
63///
64/// let output = transform(input, &spec).unwrap();
65///
66/// assert_eq!(output, json!({
67///     "data" : {
68///       "name": "John Smith",
69///       "account": {
70///         "id": 1000,
71///         "type": "Checking"
72///       }
73///     }
74/// }));
75/// ```
76///
77/// Checkout supported operations in [TransformSpec] docs.
78pub fn transform(input: Value, spec: &TransformSpec) -> Result<Value> {
79    let mut result = input;
80    for entry in spec.entries() {
81        match entry {
82            SpecEntry::Shift(shift) => result = shift.apply(&result)?,
83            SpecEntry::Default(spec) => result = default(result, spec),
84            SpecEntry::Remove(spec) => result = remove(result, spec),
85        }
86    }
87    Ok(result)
88}
89
90pub(crate) fn insert(dest: &mut Value, position: JsonPointer, val: Value) {
91    let elements = position.iter();
92    let folded = elements
93        .skip(1)
94        .try_fold(dest, |target, token| match target {
95            Value::Object(map) => {
96                if let Entry::Vacant(entry) = map.entry(token) {
97                    entry.insert(Value::Object(Map::new()));
98                }
99                map.get_mut(token)
100            }
101            _ => None,
102        });
103    if let Some(pointer_mut) = folded {
104        merge(pointer_mut, val);
105    }
106}
107
108/// Merge one `Value` node into another if they are both `Value::Object`, otherwise overwrite.
109fn merge(dest: &mut Value, new_value: Value) {
110    match (dest, new_value) {
111        (Value::Object(dest), Value::Object(new_value)) => {
112            for (key, value) in new_value.into_iter() {
113                dest.insert(key, value);
114            }
115        }
116        (dest, new_value) => *dest = new_value,
117    };
118}
119
120pub(crate) fn delete(dest: &mut Value, position: &JsonPointer) -> Option<()> {
121    if let Some(Value::Object(map)) = dest.pointer_mut(position.parent().join_rfc6901().as_str()) {
122        map.remove(position.leaf_name());
123    }
124    Some(())
125}
126
127#[cfg(test)]
128mod test {
129
130    use serde_json::json;
131    use super::*;
132
133    #[test]
134    fn test_transform() {
135        let spec: TransformSpec = serde_json::from_value(json!(
136            [
137                {
138                  "operation": "shift",
139                  "spec": {
140                    "a": "a_new",
141                    "c": "c_new"
142                  }
143                }
144            ]
145        ))
146        .expect("parsed spec");
147
148        let source = json!({
149            "a": "b",
150            "c": "d"
151        });
152        let result = transform(source, &spec).unwrap();
153
154        assert_eq!(
155            result,
156            json!({
157                "a_new": "b",
158                "c_new": "d"
159            })
160        );
161    }
162
163    #[test]
164    fn test_insert_object_to_empty() {
165        //given
166        let mut empty_dest = Value::Object(Map::new());
167        let value = json!({
168            "a": "b",
169        });
170
171        insert(
172            &mut empty_dest,
173            JsonPointer::from_dot_notation("new"),
174            value,
175        );
176
177        assert_eq!(
178            empty_dest,
179            json!({
180                "new": {
181                    "a": "b"
182                }
183            })
184        );
185    }
186
187    #[test]
188    fn test_insert_object_to_non_empty() {
189        //given
190        let mut dest = json!({
191            "b": "bb",
192            "c": "cc",
193        });
194        let value = json!({
195            "a": "b",
196        });
197
198        insert(&mut dest, JsonPointer::from_dot_notation("new"), value);
199
200        assert_eq!(
201            dest,
202            json!({
203                "b": "bb",
204                "c": "cc",
205                "new": {
206                    "a": "b"
207                }
208            })
209        );
210    }
211
212    #[test]
213    fn test_insert_object_merged() {
214        //given
215        let mut dest = json!({
216            "some": {
217                "b": "bb",
218                "c": "cc",
219            }
220        });
221        let value = json!({
222            "a": "b",
223        });
224
225        insert(&mut dest, JsonPointer::from_dot_notation("some"), value);
226
227        assert_eq!(
228            dest,
229            json!({
230                "some": {
231                    "a": "b",
232                    "b": "bb",
233                    "c": "cc",
234                }
235            })
236        );
237    }
238
239    #[test]
240    fn test_insert_object_to_empty_non_root() {
241        //given
242        let mut empty_dest = Value::Object(Map::new());
243        let value = json!({
244            "a": "b",
245        });
246
247        insert(
248            &mut empty_dest,
249            JsonPointer::from_dot_notation("level1.level2.new"),
250            value,
251        );
252
253        assert_eq!(
254            empty_dest,
255            json!({
256                "level1": {
257                    "level2": {
258                        "new": {
259                            "a": "b"
260                        }
261                    }
262                }
263            })
264        );
265    }
266
267    #[test]
268    fn test_delete_empty_pointer() {
269        //given
270        let mut input = json!({
271            "a": "b",
272        });
273
274        //when
275        let _ = delete(&mut input, &JsonPointer::from_dot_notation(""));
276
277        //then
278        assert_eq!(
279            input,
280            json!({
281                "a": "b",
282            })
283        );
284    }
285
286    #[test]
287    fn test_delete_not_existing() {
288        //given
289        let mut input = json!({
290            "a": "b",
291        });
292
293        //when
294        let _ = delete(&mut input, &JsonPointer::from_dot_notation(".b"));
295
296        //then
297        assert_eq!(
298            input,
299            json!({
300                "a": "b",
301            })
302        );
303    }
304
305    #[test]
306    fn test_delete() {
307        //given
308        let mut input1 = json!({
309            "a": "b",
310        });
311        let mut input2 = json!({
312            "a": "b",
313            "b": "c",
314        });
315        //when
316        let _ = delete(&mut input1, &JsonPointer::from_dot_notation(".a"));
317        let _ = delete(&mut input2, &JsonPointer::from_dot_notation("b"));
318
319        //then
320        assert_eq!(input1, json!({}));
321        assert_eq!(
322            input2,
323            json!({
324                "a": "b",
325            })
326        );
327    }
328}