1use linked_hash_map::LinkedHashMap;
2use serde::{Serialize, Deserialize};
3
4use crate::{Hit, ObjectValue, Id, ObjectValues, IndexEntryProperty};
5
6#[derive(Clone, Debug, Serialize, Deserialize)]
7pub struct PatchPropertyDifference {
8 pub id: String,
9 pub property: String,
10 pub old_value: ObjectValue,
11 pub new_value: ObjectValue,
12}
13
14#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
15pub struct AddedEntry {
16 pub id: Id,
17 pub data: ObjectValues,
18 pub parent: Option<IndexEntryProperty>,
19 pub model: String,
20}
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct Patch {
23 pub differences: Vec<PatchPropertyDifference>,
24 pub deleted: Vec<String>,
25 pub added: Vec<AddedEntry>,
26}
27
28pub fn create_patch(old: Hit, new: &Hit) -> Patch {
29 let mut deleted = Vec::new();
30 let mut added = Vec::new();
31 let mut differences = Vec::new();
32 for entry in old.index.iter() {
33 let id = entry.0.clone();
34 let old_entry = entry.1;
35 if !new.index.contains(&id) {
36 deleted.push(id);
37 continue;
38 }
39 let old_entry = old_entry.borrow();
40 let old_model = old.get_model(&id.clone()).unwrap();
41
42 for field in old_model.fields.iter() {
43 let old_value = old_entry.get(&field.0);
44 let new_value = new.get_value(&id.clone(), field.0).unwrap();
45 if &new_value != old_value {
46 differences.push(PatchPropertyDifference {
47 id: id.clone(),
48 property: field.0.clone(),
49 old_value: old_value.clone(),
50 new_value,
51 });
52 }
53 }
54 }
55
56 for entry in new.index.iter() {
58 let id = entry.0.clone();
59 if !old.index.contains(&id) {
60 add_entry(entry, &new, id, &mut added);
61 }
62 }
63 return Patch {
64 differences,
65 deleted,
66 added,
67 };
68}
69
70pub(super) fn add_entry(entry: (&String, &std::rc::Rc<std::cell::RefCell<crate::index::IndexEntry>>), new: &Hit, id: String, added: &mut Vec<AddedEntry>) {
71 let index_entry = entry.1;
72 let index_entry = index_entry.borrow();
73 let model = new.get_model(&id).unwrap();
74
75 let mut data = LinkedHashMap::new();
77 for (key, value) in index_entry.data.iter() {
78 if value != &ObjectValue::Null {
79 data.insert(String::from(key), value.clone());
80 }
81 }
82
83 added.push(AddedEntry {
84 data,
85 id: index_entry.get_id().clone(),
86 parent: index_entry.get_parent().clone(),
87 model: model.get_name().clone(),
88 });
89}
90
91#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::{Hit, IndexEntryProperty, LinkedHashMap, Reference, test_kernel::create_test_kernel};
110
111 use std::rc::Rc;
112
113 #[test]
114 fn test_create_patch() {
115 let kernel = create_test_kernel();
116 let kernel = Rc::new(kernel);
117 let mut old = Hit::new("main", "test/test", kernel.clone()).unwrap();
118 let mut fields = LinkedHashMap::new();
119 fields.insert(
120 "name".to_string(),
121 ObjectValue::String("model 1".to_string()),
122 );
123 old.insert(
124 "test/test",
125 "id",
126 fields,
127 IndexEntryProperty {
128 id: "main".to_string(),
129 property: "sub_items".to_string(),
130 },
131 None,
132 )
133 .unwrap();
134 let mut fields = LinkedHashMap::new();
135 fields.insert(
136 "deleted_folder".to_string(),
137 ObjectValue::String("model 1".to_string()),
138 );
139 old.insert(
140 "test/test",
141 "deleted_folder",
142 fields,
143 IndexEntryProperty {
144 id: "main".to_string(),
145 property: "sub_items".to_string(),
146 },
147 None,
148 )
149 .unwrap();
150 let mut new = Hit::new("main", "test/test", kernel).unwrap();
151 let mut fields = LinkedHashMap::new();
152 fields.insert(
153 "name".to_string(),
154 ObjectValue::String("other_name".to_string()),
155 );
156 new.insert(
157 "test/test",
158 "id",
159 fields,
160 IndexEntryProperty {
161 id: "main".to_string(),
162 property: "sub_items".to_string(),
163 },
164 None,
165 )
166 .unwrap();
167 let mut fields = LinkedHashMap::new();
168 fields.insert(
169 "name".to_string(),
170 ObjectValue::String("added_folder".to_string()),
171 );
172 new.insert(
173 "test/test",
174 "added_folder",
175 fields.clone(),
176 IndexEntryProperty {
177 id: "main".to_string(),
178 property: "sub_items".to_string(),
179 },
180 None,
181 )
182 .unwrap();
183
184 let patch = create_patch(old, &new);
185 assert_eq!(patch.added, vec![AddedEntry {
186 id: "added_folder".to_string(),
187 data: fields.clone(),
188 parent: Some(IndexEntryProperty {
189 id: "main".to_string(),
190 property: "sub_items".to_string(),
191 }),
192 model: "test/test".to_string(),
193 }]);
194 assert_eq!(patch.deleted, vec!["deleted_folder"]);
195
196 assert_eq!(patch.differences.len(), 2);
197 assert_eq!(patch.differences[0].id, "id");
198 assert_eq!(patch.differences[0].property, "name");
199 assert_eq!(patch.differences[0].old_value, ObjectValue::String("model 1".to_string()));
200 assert_eq!(patch.differences[0].new_value, ObjectValue::String("other_name".to_string()));
201
202
203 assert_eq!(patch.differences[1].id, "main");
204 assert_eq!(patch.differences[1].property, "sub_items");
205 assert_eq!(patch.differences[1].old_value, ObjectValue::VecSubObjects(vec![
206 Reference {
207 id: "id".to_string(),
208 },
209 Reference {
210 id: "deleted_folder".to_string(),
211 }
212 ]));
213 assert_eq!(patch.differences[1].new_value, ObjectValue::VecSubObjects(vec![
214 Reference {
215 id: "id".to_string(),
216 },
217 Reference {
218 id: "added_folder".to_string(),
219 }
220 ]));
221 }
222}