hit_data/import/
diff.rs

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    // find added items
57    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    // filter data to remove null values
76    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// pub fn export_entries(hit: Hit) -> LinkedHashMap<String, AddedEntry> {
92//     let mut entries = LinkedHashMap::new();
93//     for entry in hit.index.iter() {
94//         let id = entry.0.clone();
95//         let model = hit.get_model(&id).unwrap();
96//         entries.insert(id.clone(), AddedEntry {
97//             id,
98//             data: entry.1.borrow().data.clone(),
99//             parent: entry.1.borrow().get_parent(),
100//             model: model.get_name().clone(),
101//         });
102//     }
103//     entries
104// }
105
106#[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}