augr_core/store/
patch.rs

1use crate::Tag;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeSet;
5use uuid::Uuid;
6
7pub type PatchRef = Uuid;
8type EventRef = String;
9type Set<T> = std::collections::HashSet<T>;
10
11#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "kebab-case")]
13pub struct Patch {
14    pub id: Uuid,
15
16    #[serde(default, skip_serializing_if = "Set::is_empty")]
17    pub add_start: Set<AddStart>,
18
19    #[serde(default, skip_serializing_if = "Set::is_empty")]
20    pub remove_start: Set<RemoveStart>,
21
22    #[serde(default, skip_serializing_if = "Set::is_empty")]
23    pub add_tag: Set<AddTag>,
24
25    #[serde(default, skip_serializing_if = "Set::is_empty")]
26    pub remove_tag: Set<RemoveTag>,
27
28    #[serde(default, skip_serializing_if = "Set::is_empty")]
29    pub create_event: Set<CreateEvent>,
30}
31
32#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
33#[serde(rename_all = "kebab-case")]
34pub struct AddStart {
35    #[serde(default)]
36    pub parents: BTreeSet<PatchRef>,
37    pub event: EventRef,
38    pub time: DateTime<Utc>,
39}
40
41#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "kebab-case")]
43pub struct RemoveStart {
44    #[serde(default)]
45    pub parents: Option<BTreeSet<PatchRef>>,
46    pub patch: PatchRef,
47    pub event: EventRef,
48    pub time: DateTime<Utc>,
49}
50
51#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
52#[serde(rename_all = "kebab-case")]
53pub struct AddTag {
54    #[serde(default)]
55    pub parents: BTreeSet<PatchRef>,
56    pub event: EventRef,
57    pub tag: Tag,
58}
59
60#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "kebab-case")]
62pub struct RemoveTag {
63    #[serde(default)]
64    pub parents: Option<BTreeSet<PatchRef>>,
65    pub patch: PatchRef,
66    pub event: EventRef,
67    pub tag: Tag,
68}
69
70#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
71#[serde(rename_all = "kebab-case")]
72pub struct CreateEvent {
73    pub event: EventRef,
74    pub start: DateTime<Utc>,
75    pub tags: Vec<Tag>,
76}
77
78impl Patch {
79    pub fn new() -> Self {
80        Self {
81            id: Uuid::new_v4(),
82            add_start: Set::new(),
83            remove_start: Set::new(),
84            add_tag: Set::new(),
85            remove_tag: Set::new(),
86            create_event: Set::new(),
87        }
88    }
89
90    pub fn with_id(id: PatchRef) -> Self {
91        Self {
92            id,
93            add_start: Set::new(),
94            remove_start: Set::new(),
95            add_tag: Set::new(),
96            remove_tag: Set::new(),
97            create_event: Set::new(),
98        }
99    }
100
101    pub fn patch_ref(&self) -> &PatchRef {
102        &self.id
103    }
104
105    pub fn parents(&self) -> Set<PatchRef> {
106        let add_start_parents = self.add_start.iter().flat_map(|x| x.parents.iter());
107        let remove_start_parents = self.remove_start.iter().map(|x| &x.patch).chain(
108            self.remove_start
109                .iter()
110                .flat_map(|x| x.parents.iter().flat_map(|s| s.iter())),
111        );
112        let remove_tag_parents = self.remove_tag.iter().map(|x| &x.patch).chain(
113            self.remove_tag
114                .iter()
115                .flat_map(|x| x.parents.iter().flat_map(|s| s.iter())),
116        );
117        let add_tag_parents = self.add_tag.iter().flat_map(|x| x.parents.iter());
118        add_start_parents
119            .chain(remove_start_parents)
120            .chain(remove_tag_parents)
121            .chain(add_tag_parents)
122            .cloned()
123            .collect()
124    }
125
126    pub fn add_start(mut self, parent: PatchRef, event: EventRef, time: DateTime<Utc>) -> Self {
127        self.add_start.insert(AddStart {
128            parents: {
129                let mut s = BTreeSet::new();
130                s.insert(parent);
131                s
132            },
133            event,
134            time,
135        });
136        self
137    }
138
139    pub fn remove_start(mut self, patch: PatchRef, event: EventRef, time: DateTime<Utc>) -> Self {
140        self.remove_start.insert(RemoveStart {
141            parents: None,
142            patch,
143            event,
144            time,
145        });
146        self
147    }
148
149    pub fn add_tag(mut self, parent: PatchRef, event: EventRef, tag: String) -> Self {
150        self.add_tag.insert(AddTag {
151            parents: {
152                let mut s = BTreeSet::new();
153                s.insert(parent);
154                s
155            },
156            event,
157            tag,
158        });
159        self
160    }
161
162    pub fn remove_tag(mut self, patch: PatchRef, event: EventRef, tag: String) -> Self {
163        self.remove_tag.insert(RemoveTag {
164            parents: None,
165            patch,
166            event,
167            tag,
168        });
169        self
170    }
171
172    pub fn create_event(
173        mut self,
174        event: EventRef,
175        start: DateTime<Utc>,
176        tags: Vec<String>,
177    ) -> Self {
178        self.create_event.insert(CreateEvent { event, start, tags });
179        self
180    }
181
182    pub fn insert_add_start(&mut self, add_start: AddStart) {
183        self.add_start.insert(add_start);
184    }
185
186    pub fn insert_remove_start(&mut self, remove_start: RemoveStart) {
187        self.remove_start.insert(remove_start);
188    }
189
190    pub fn insert_add_tag(&mut self, add_tag: AddTag) {
191        self.add_tag.insert(add_tag);
192    }
193
194    pub fn insert_remove_tag(&mut self, remove_tag: RemoveTag) {
195        self.remove_tag.insert(remove_tag);
196    }
197
198    pub fn insert_create_event(&mut self, create_event: CreateEvent) {
199        self.create_event.insert(create_event);
200    }
201}
202
203impl Default for Patch {
204    fn default() -> Self {
205        Patch::new()
206    }
207}
208
209impl AddStart {
210    pub fn parents(&self) -> impl Iterator<Item = &PatchRef> {
211        self.parents.iter()
212    }
213}
214impl RemoveStart {
215    pub fn parents(&self) -> impl Iterator<Item = &PatchRef> {
216        self.parents.iter().flat_map(|s| s.iter())
217    }
218}
219impl AddTag {
220    pub fn parents(&self) -> impl Iterator<Item = &PatchRef> {
221        self.parents.iter()
222    }
223}
224impl RemoveTag {
225    pub fn parents(&self) -> impl Iterator<Item = &PatchRef> {
226        self.parents.iter().flat_map(|s| s.iter())
227    }
228}
229
230#[cfg(test)]
231mod test {
232    use super::*;
233    use chrono::offset::{TimeZone, Utc};
234
235    macro_rules! s (
236        { $stuff:expr } => {
237            {
238                $stuff.to_string()
239            }
240         };
241    );
242
243    #[test]
244    fn read_patch_with_create_event_toml() {
245        let id = Uuid::parse_str("e39076fe-6b5a-4a7f-b927-7fc1df5ba275").unwrap();
246        let expected = Patch::with_id(id).create_event(
247            s!("a"),
248            Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
249            vec![s!("work"), s!("coding")],
250        );
251
252        let toml_str = r#"
253            id = "e39076fe-6b5a-4a7f-b927-7fc1df5ba275"
254
255            [[create-event]]
256            event = "a"
257            start = "2019-07-24T14:00:00+00:00"
258            tags = ["work", "coding"]
259        "#;
260        assert_eq!(toml::de::from_str(toml_str), Ok(expected));
261    }
262
263    #[test]
264    fn serialize_patch_with_add_tag_toml() {
265        let id = Uuid::parse_str("e39076fe-6b5a-4a7f-b927-7fc1df5ba275").unwrap();
266        let patch0 = Uuid::parse_str("fa5de1d9-aa11-49fa-b064-8128281a7d91").unwrap();
267        let event0 = Uuid::parse_str("0c435b19-4504-440c-abc7-f4e4d6a7d25f").unwrap();
268
269        let patch = Patch::with_id(id).add_start(
270            patch0.clone(),
271            event0.to_string(),
272            Utc.ymd(2019, 07, 24).and_hms(14, 0, 0),
273        );
274
275        let toml_str = "id = \"e39076fe-6b5a-4a7f-b927-7fc1df5ba275\"\n\n[[add-start]]\nparents = [\"fa5de1d9-aa11-49fa-b064-8128281a7d91\"]\nevent = \"0c435b19-4504-440c-abc7-f4e4d6a7d25f\"\ntime = \"2019-07-24T14:00:00Z\"\n".to_string();
276        let serialized = toml::ser::to_string(&patch).unwrap();
277        println!("{}", serialized);
278        assert_eq!(toml_str, serialized);
279    }
280
281    #[test]
282    fn read_patch_with_parents() {
283        let id = Uuid::parse_str("e39076fe-6b5a-4a7f-b927-7fc1df5ba275").unwrap();
284        let patch0 = Uuid::parse_str("fa5de1d9-aa11-49fa-b064-8128281a7d91").unwrap();
285        let patch1 = Uuid::parse_str("0c435b19-4504-440c-abc7-f4e4d6a7d25f").unwrap();
286
287        let mut expected = Patch::with_id(id);
288
289        let remove_start = RemoveStart {
290            parents: {
291                let mut s = BTreeSet::new();
292                s.insert(patch0.clone());
293                s.insert(patch1.clone());
294                Some(s)
295            },
296            patch: patch0.clone(),
297            event: s!("a"),
298            time: Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
299        };
300        expected.insert_remove_start(remove_start);
301
302        let toml_str = r#"
303            id = "e39076fe-6b5a-4a7f-b927-7fc1df5ba275"
304
305            [[remove-start]]
306            parents = ["fa5de1d9-aa11-49fa-b064-8128281a7d91", "0c435b19-4504-440c-abc7-f4e4d6a7d25f"]
307            patch = "fa5de1d9-aa11-49fa-b064-8128281a7d91"
308            event = "a"
309            time = "2019-07-24T14:00:00+00:00"
310        "#;
311        assert_eq!(toml::de::from_str(toml_str), Ok(expected));
312    }
313
314    #[test]
315    fn read_patch_with_all_fields_toml() {
316        let patch0 = Uuid::parse_str("fa5de1d9-aa11-49fa-b064-8128281a7d91").unwrap();
317
318        let expected =
319            Patch::with_id(Uuid::parse_str("2a226f4d-60f2-493d-9e9a-d6c71d98b515").unwrap())
320                .add_start(
321                    patch0.clone(),
322                    s!("a"),
323                    Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
324                )
325                .remove_start(
326                    patch0.clone(),
327                    s!("a"),
328                    Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
329                )
330                .add_tag(patch0.clone(), s!("a"), s!("work"))
331                .remove_tag(patch0.clone(), s!("a"), s!("coding"))
332                .create_event(
333                    s!("a"),
334                    Utc.ymd(2019, 7, 24).and_hms(14, 0, 0),
335                    vec![s!("work"), s!("coding")],
336                );
337
338        let toml_str = r#"
339            id = "2a226f4d-60f2-493d-9e9a-d6c71d98b515"
340
341            [[add-start]]
342            parents = ["fa5de1d9-aa11-49fa-b064-8128281a7d91"]
343            event = "a"
344            time = "2019-07-24T14:00:00+00:00"
345
346            [[remove-start]]
347            patch = "fa5de1d9-aa11-49fa-b064-8128281a7d91"
348            event = "a"
349            time = "2019-07-24T14:00:00+00:00"
350
351            [[add-tag]]
352            parents = ["fa5de1d9-aa11-49fa-b064-8128281a7d91"]
353            event = "a"
354            tag = "work"
355
356            [[remove-tag]]
357            patch = "fa5de1d9-aa11-49fa-b064-8128281a7d91"
358            event = "a"
359            tag = "coding"
360
361            [[create-event]]
362            event = "a"
363            start = "2019-07-24T14:00:00+00:00"
364            tags = ["work", "coding"]
365        "#;
366        assert_eq!(toml::de::from_str(toml_str), Ok(expected));
367    }
368
369}