umili/
change.rs

1use serde::Serialize;
2use serde_json::{to_value, Value};
3
4use crate::error::Error;
5
6/// A change in JSON format.
7#[derive(Debug, Clone, PartialEq)]
8pub enum Change {
9    /// SET is the default change for `DerefMut` operations.
10    /// 
11    /// ## Example
12    /// 
13    /// ```ignore
14    /// foo.a.b = 1;        // SET "a/b"
15    /// foo.num *= 2;       // SET "num"
16    /// foo.vec.clear();    // SET "vec"
17    /// ```
18    /// 
19    /// If an operation triggers APPEND, no SET change is emitted.
20    SET { p: String, v: Value },
21
22    /// APPEND represents a `String` or `Vec` append operation.
23    /// 
24    /// ## Example
25    /// 
26    /// ```ignore
27    /// foo.a.b += "text";          // APPEND "a/b"
28    /// foo.a.b.push_str("text");   // APPEND "a/b"
29    /// foo.vec.push(1);            // APPEND "vec"
30    /// foo.vec.extend(iter);       // APPEND "vec"
31    /// ```
32    #[cfg(feature = "append")]
33    APPEND { p: String, v: Value },
34
35    /// BATCH represents a sequence of changes.
36    BATCH { p: String, v: Vec<Self> },
37}
38
39impl Change {
40    /// Construct a SET change.
41    pub fn set<P: Into<String>, V: Serialize>(p: P, v: V) -> Result<Self, serde_json::Error> {
42        Ok(Self::SET { p: p.into(), v: to_value(v)? })
43    }
44
45    /// Construct an APPEND change.
46    #[cfg(feature = "append")]
47    pub fn append<P: Into<String>, V: Serialize>(p: P, v: V) -> Result<Self, serde_json::Error> {
48        Ok(Self::APPEND { p: p.into(), v: to_value(v)? })
49    }
50
51    /// Construct a BATCH change.
52    pub fn batch<P: Into<String>>(p: P, v: Vec<Change>) -> Self {
53        Self::BATCH { p: p.into(), v }
54    }
55
56    /// Get the path of the change.
57    pub fn path(&self) -> &String {
58        match self {
59            Self::BATCH { p, .. } => p,
60            Self::SET { p, .. } => p,
61            #[cfg(feature = "append")]
62            Self::APPEND { p, .. } => p,
63        }
64    }
65
66    /// Get the mutable path of the change.
67    pub fn path_mut(&mut self) -> &mut String {
68        match self {
69            Self::BATCH { p, .. } => p,
70            Self::SET { p, .. } => p,
71            #[cfg(feature = "append")]
72            Self::APPEND { p, .. } => p,
73        }
74    }
75
76    /// Apply the change to a JSON value.
77    pub fn apply(self, mut value: &mut Value, prefix: &str) -> Result<(), Error> {
78        let mut parts = split_path(self.path());
79        let mut prefix = prefix.to_string();
80        while let Some(key) = parts.pop() {
81            prefix += key;
82            prefix += "/";
83            match json_index(value, key, parts.is_empty() && matches!(self, Self::SET { .. })) {
84                Some(v) => value = v,
85                None => {
86                    prefix.pop();
87                    return Err(Error::IndexError { path: prefix })
88                },
89            }
90        }
91        match self {
92            Self::SET { v, .. } => {
93                *value = v;
94            },
95            #[cfg(feature = "append")]
96            Self::APPEND { v, .. } => {
97                if !append(value, v) {
98                    prefix.pop();
99                    return Err(Error::OperationError { path: prefix })
100                }
101            },
102            Self::BATCH { v, .. } => {
103                for delta in v {
104                    delta.apply(value, &prefix)?;
105                }
106            },
107        }
108        Ok(())
109    }
110}
111
112fn json_index<'v>(value: &'v mut Value, key: &str, insert: bool) -> Option<&'v mut Value> {
113    match value {
114        Value::Array(vec) => {
115            key.parse::<usize>().ok().and_then(|index| vec.get_mut(index))
116        },
117        Value::Object(map) => {
118            match insert {
119                true => Some(map.entry(key.to_string()).or_insert(Value::Null)),
120                false => map.get_mut(key),
121            }
122        },
123        _ => None,
124    }
125}
126
127#[cfg(feature = "append")]
128pub(crate) fn append(lhs: &mut Value, rhs: Value) -> bool {
129    match (lhs, rhs) {
130        (Value::String(lhs), Value::String(rhs)) => {
131            *lhs += &rhs;
132        },
133        (Value::Array(lhs), Value::Array(rhs)) => {
134            lhs.extend(rhs);
135        },
136        _ => return false,
137    }
138    true
139}
140
141pub(crate) fn split_path(path: &str) -> Vec<&str> {
142    if path.is_empty() {
143        vec![]
144    } else {
145        path.split('/').rev().collect()
146    }
147}
148
149pub(crate) fn concat_path(key: String, path: &str) -> String {
150    if path.is_empty() {
151        key
152    } else {
153        format!("{}/{}", key, path)
154    }
155}
156
157#[cfg(test)]
158mod test {
159    use serde_json::json;
160
161    use super::*;
162
163    #[test]
164    fn apply_set() {
165        let mut value = json!({"a": 1});
166        Change::set("", json!({})).unwrap().apply(&mut value, "").unwrap();
167        assert_eq!(value, json!({}));
168
169        let mut value = json!({});
170        Change::set("a", 1).unwrap().apply(&mut value, "").unwrap();
171        assert_eq!(value, json!({"a": 1}));
172
173        let mut value = json!({"a": 1});
174        Change::set("a", 2).unwrap().apply(&mut value, "").unwrap();
175        assert_eq!(value, json!({"a": 2}));
176
177        let error = Change::set("a/b", 3).unwrap().apply(&mut json!({}), "").unwrap_err();
178        assert_eq!(error, Error::IndexError { path: "a".to_string() });
179
180        let error = Change::set("a/b", 3).unwrap().apply(&mut json!({"a": 1}), "").unwrap_err();
181        assert_eq!(error, Error::IndexError { path: "a/b".to_string() });
182
183        let error = Change::set("a/b", 3).unwrap().apply(&mut json!({"a": []}), "").unwrap_err();
184        assert_eq!(error, Error::IndexError { path: "a/b".to_string() });
185
186        let mut value = json!({"a": {}});
187        Change::set("a/b", 3).unwrap().apply(&mut value, "").unwrap();
188        assert_eq!(value, json!({"a": {"b": 3}}));
189    }
190
191    #[test]
192    fn apply_append() {
193        let mut value = json!("2");
194        Change::append("", "34").unwrap().apply(&mut value, "").unwrap();
195        assert_eq!(value, json!("234"));
196
197        let mut value = json!([2]);
198        Change::append("", ["3", "4"]).unwrap().apply(&mut value, "").unwrap();
199        assert_eq!(value, json!([2, "3", "4"]));
200
201        let error = Change::append("", 3).unwrap().apply(&mut json!(""), "").unwrap_err();
202        assert_eq!(error, Error::OperationError { path: "".to_string() });
203
204        let error = Change::append("", "3").unwrap().apply(&mut json!({}), "").unwrap_err();
205        assert_eq!(error, Error::OperationError { path: "".to_string() });
206
207        let error = Change::append("", "3").unwrap().apply(&mut json!([]), "").unwrap_err();
208        assert_eq!(error, Error::OperationError { path: "".to_string() });
209
210        let error = Change::append("", [3]).unwrap().apply(&mut json!(""), "").unwrap_err();
211        assert_eq!(error, Error::OperationError { path: "".to_string() });
212    }
213
214    #[test]
215    fn apply_batch() {
216        let mut value = json!({"a": {"b": {"c": {}}}});
217        Change::batch("", vec![]).apply(&mut value, "").unwrap();
218        assert_eq!(value, json!({"a": {"b": {"c": {}}}}));
219
220        let mut value = json!({"a": {"b": {"c": "1"}}});
221        let error = Change::batch("a/d", vec![]).apply(&mut value, "").unwrap_err();
222        assert_eq!(error, Error::IndexError { path: "a/d".to_string() });
223
224        let mut value = json!({"a": {"b": {"c": "1"}}});
225        Change::batch("a", vec![
226            Change::append("b/c", "2").unwrap(),
227            Change::set("d", 3).unwrap(),
228        ]).apply(&mut value, "").unwrap();
229        assert_eq!(value, json!({"a": {"b": {"c": "12"}, "d": 3}}));
230    }
231}