1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::fmt::Debug;
4use std::mem::take;
5
6use crate::{Adapter, Mutation, MutationError, MutationKind};
7
8pub struct Batch<A: Adapter> {
35 operation: Option<MutationKind<A>>,
36 children: BTreeMap<Cow<'static, str>, Self>,
37}
38
39impl<A: Adapter> Default for Batch<A> {
40 fn default() -> Self {
41 Self {
42 operation: None,
43 children: BTreeMap::new(),
44 }
45 }
46}
47
48impl<A: Adapter> Debug for Batch<A>
49where
50 A::Value: Debug,
51{
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("Batch")
54 .field("operation", &self.operation)
55 .field("children", &self.children)
56 .finish()
57 }
58}
59
60impl<A: Adapter> Batch<A> {
61 pub fn new() -> Self {
63 Default::default()
64 }
65
66 pub fn load(&mut self, mutation: Mutation<A>) -> Result<(), MutationError> {
76 self.load_with_stack(mutation, &mut vec![])
77 }
78
79 fn load_with_stack(
80 &mut self,
81 mut mutation: Mutation<A>,
82 path_stack: &mut Vec<Cow<'static, str>>,
83 ) -> Result<(), MutationError> {
84 let mut batch = self;
85 if let Some(MutationKind::Replace(value)) = &mut batch.operation {
86 A::apply_mutation(value, mutation, path_stack)?;
87 return Ok(());
88 }
89 while let Some(key) = mutation.path_rev.pop() {
90 path_stack.push(key.clone());
92 batch = batch.children.entry(key).or_default();
93 if let Some(MutationKind::Replace(value)) = &mut batch.operation {
94 A::apply_mutation(value, mutation, path_stack)?;
95 return Ok(());
96 }
97 }
98
99 match mutation.operation {
100 MutationKind::Replace(_) => {
101 batch.operation = Some(mutation.operation);
102 batch.children.clear();
103 }
104 MutationKind::Append(new_value) => match &mut batch.operation {
105 Some(MutationKind::Append(old_value)) => {
106 A::merge_append(old_value, new_value, path_stack)?;
107 }
108 Some(_) => unreachable!(),
109 None => batch.operation = Some(MutationKind::Append(new_value)),
110 },
111 MutationKind::Batch(mutations) => {
112 let len = path_stack.len();
113 for mutation in mutations {
114 batch.load_with_stack(mutation, path_stack)?;
115 path_stack.truncate(len);
116 }
117 }
118 }
119
120 Ok(())
121 }
122
123 pub fn dump(&mut self) -> Option<Mutation<A>> {
129 let mut mutations = vec![];
130 if let Some(operation) = self.operation.take() {
131 mutations.push(Mutation {
132 path_rev: vec![],
133 operation,
134 });
135 }
136 for (key, mut batch) in take(&mut self.children) {
137 if let Some(mut mutation) = batch.dump() {
138 mutation.path_rev.push(key);
139 mutations.push(mutation);
140 }
141 }
142 Self::build(mutations)
143 }
144
145 #[doc(hidden)]
146 pub fn build(mut mutations: Vec<Mutation<A>>) -> Option<Mutation<A>> {
147 match mutations.len() {
148 0 => None,
149 1 => Some(mutations.swap_remove(0)),
150 _ => Some(Mutation {
151 path_rev: vec![],
152 operation: MutationKind::Batch(mutations),
153 }),
154 }
155 }
156}
157
158#[cfg(test)]
159mod test {
160 use serde_json::json;
161
162 use super::*;
163 use crate::JsonAdapter;
164
165 #[test]
166 fn batch() {
167 let mut batch = Batch::<JsonAdapter>::new();
168 assert_eq!(batch.dump(), None);
169
170 let mut batch = Batch::<JsonAdapter>::new();
171 batch
172 .load(Mutation {
173 path_rev: vec!["bar".into(), "foo".into()],
174 operation: MutationKind::Replace(json!(1)),
175 })
176 .unwrap();
177 assert_eq!(
178 batch.dump(),
179 Some(Mutation {
180 path_rev: vec!["bar".into(), "foo".into()],
181 operation: MutationKind::Replace(json!(1))
182 }),
183 );
184
185 let mut batch = Batch::<JsonAdapter>::new();
186 batch
187 .load(Mutation {
188 path_rev: vec!["bar".into(), "foo".into()],
189 operation: MutationKind::Replace(json!(1)),
190 })
191 .unwrap();
192 batch
193 .load(Mutation {
194 path_rev: vec!["bar".into(), "foo".into()],
195 operation: MutationKind::Replace(json!(2)),
196 })
197 .unwrap();
198 assert_eq!(
199 batch.dump(),
200 Some(Mutation {
201 path_rev: vec!["bar".into(), "foo".into()],
202 operation: MutationKind::Replace(json!(2)),
203 }),
204 );
205
206 let mut batch = Batch::<JsonAdapter>::new();
207 batch
208 .load(Mutation {
209 path_rev: vec!["bar".into(), "foo".into()],
210 operation: MutationKind::Replace(json!({"qux": "1"})),
211 })
212 .unwrap();
213 batch
214 .load(Mutation {
215 path_rev: vec!["qux".into(), "bar".into(), "foo".into()],
216 operation: MutationKind::Append(json!("2")),
217 })
218 .unwrap();
219 assert_eq!(
220 batch.dump(),
221 Some(Mutation {
222 path_rev: vec!["bar".into(), "foo".into()],
223 operation: MutationKind::Replace(json!({"qux": "12"})),
224 }),
225 );
226
227 let mut batch = Batch::<JsonAdapter>::new();
228 batch
229 .load(Mutation {
230 path_rev: vec!["qux".into(), "bar".into(), "foo".into()],
231 operation: MutationKind::Append(json!("2")),
232 })
233 .unwrap();
234 batch
235 .load(Mutation {
236 path_rev: vec!["bar".into(), "foo".into()],
237 operation: MutationKind::Replace(json!({"qux": "1"})),
238 })
239 .unwrap();
240 assert_eq!(
241 batch.dump(),
242 Some(Mutation {
243 path_rev: vec!["bar".into(), "foo".into()],
244 operation: MutationKind::Replace(json!({"qux": "1"})),
245 }),
246 );
247
248 let mut batch = Batch::<JsonAdapter>::new();
249 batch
250 .load(Mutation {
251 path_rev: vec!["foo".into()],
252 operation: MutationKind::Batch(vec![
253 Mutation {
254 path_rev: vec!["bar".into()],
255 operation: MutationKind::Append(json!("1")),
256 },
257 Mutation {
258 path_rev: vec!["bar".into()],
259 operation: MutationKind::Append(json!("2")),
260 },
261 ]),
262 })
263 .unwrap();
264 assert_eq!(
265 batch.dump(),
266 Some(Mutation {
267 path_rev: vec!["bar".into(), "foo".into()],
268 operation: MutationKind::Append(json!("12")),
269 }),
270 );
271
272 let mut batch = Batch::<JsonAdapter>::new();
273 batch
274 .load(Mutation {
275 path_rev: vec!["bar".into()],
276 operation: MutationKind::Append(json!("2")),
277 })
278 .unwrap();
279 batch
280 .load(Mutation {
281 path_rev: vec!["qux".into()],
282 operation: MutationKind::Append(json!("1")),
283 })
284 .unwrap();
285 assert_eq!(
286 batch.dump(),
287 Some(Mutation {
288 path_rev: vec![],
289 operation: MutationKind::Batch(vec![
290 Mutation {
291 path_rev: vec!["bar".into()],
292 operation: MutationKind::Append(json!("2")),
293 },
294 Mutation {
295 path_rev: vec!["qux".into()],
296 operation: MutationKind::Append(json!("1")),
297 },
298 ]),
299 }),
300 );
301
302 let mut batch = Batch::<JsonAdapter>::new();
303 batch
304 .load(Mutation {
305 path_rev: vec!["bar".into(), "foo".into()],
306 operation: MutationKind::Append(json!("2")),
307 })
308 .unwrap();
309 batch
310 .load(Mutation {
311 path_rev: vec!["qux".into(), "foo".into()],
312 operation: MutationKind::Append(json!("1")),
313 })
314 .unwrap();
315 assert_eq!(
316 batch.dump(),
317 Some(Mutation {
318 path_rev: vec!["foo".into()],
319 operation: MutationKind::Batch(vec![
320 Mutation {
321 path_rev: vec!["bar".into()],
322 operation: MutationKind::Append(json!("2")),
323 },
324 Mutation {
325 path_rev: vec!["qux".into()],
326 operation: MutationKind::Append(json!("1")),
327 },
328 ]),
329 }),
330 );
331 }
332}