1use std::marker::PhantomData;
10use std::result::Result as RResult;
11
12use chrono::Utc;
13use serde::{de, Deserialize, Deserializer};
14use serde::{Serialize, Serializer};
15use uuid::Uuid;
16
17use crate::annotation::Annotation;
18use crate::date::Date;
19use crate::priority::TaskPriority;
20use crate::project::Project;
21use crate::status::TaskStatus;
22use crate::tag::Tag;
23use crate::uda::UDA;
24use crate::urgency::Urgency;
25
26#[derive(Debug, Clone)]
29pub struct TW26;
30
31#[derive(Debug, Clone)]
34pub struct TW25;
35
36mod private {
38 pub trait Sealed {}
39 impl Sealed for super::TW26 {}
40 impl Sealed for super::TW25 {}
41}
42
43pub trait TaskWarriorVersion: private::Sealed {}
45impl TaskWarriorVersion for TW26 {}
46impl TaskWarriorVersion for TW25 {}
47
48#[derive(Debug, Clone, PartialEq, derive_builder::Builder, Serialize, Deserialize)]
70#[builder(setter(into))]
71pub struct Task<Version: TaskWarriorVersion + 'static = TW26> {
72 #[builder(default)]
74 #[serde(skip_serializing_if = "Option::is_none")]
75 id: Option<u64>,
76
77 #[builder(default = "TaskStatus::Pending")]
79 status: TaskStatus,
80 #[builder(default = "Uuid::new_v4()")]
82 uuid: Uuid,
83 #[builder(default = "Date::from(Utc::now().naive_utc())")]
85 entry: Date,
86 description: String,
89 #[builder(default)]
91 #[serde(skip_serializing_if = "Option::is_none")]
92 annotations: Option<Vec<Annotation>>,
93 #[builder(default)]
95 #[serde(skip_serializing_if = "Option::is_none")]
96 #[serde(serialize_with = "serialize_depends::<_, Version>")]
97 #[serde(deserialize_with = "deserialize_depends::<_, Version>", default)]
98 depends: Option<Vec<Uuid>>,
99 #[builder(default)]
101 #[serde(skip_serializing_if = "Option::is_none")]
102 due: Option<Date>,
103 #[builder(default)]
105 #[serde(skip_serializing_if = "Option::is_none")]
106 end: Option<Date>,
107 #[builder(default)]
109 #[serde(skip_serializing_if = "Option::is_none")]
110 imask: Option<f64>,
111 #[builder(default)]
113 #[serde(skip_serializing_if = "Option::is_none")]
114 mask: Option<String>,
115 #[builder(default)]
117 #[serde(skip_serializing_if = "Option::is_none")]
118 modified: Option<Date>,
119 #[builder(default)]
121 #[serde(skip_serializing_if = "Option::is_none")]
122 parent: Option<Uuid>,
123 #[builder(default)]
125 #[serde(skip_serializing_if = "Option::is_none")]
126 priority: Option<TaskPriority>,
127 #[builder(default)]
129 #[serde(skip_serializing_if = "Option::is_none")]
130 project: Option<Project>,
131 #[builder(default)]
133 #[serde(skip_serializing_if = "Option::is_none")]
134 recur: Option<String>,
135 #[builder(default)]
137 #[serde(skip_serializing_if = "Option::is_none")]
138 scheduled: Option<Date>,
139 #[builder(default)]
141 #[serde(skip_serializing_if = "Option::is_none")]
142 start: Option<Date>,
143 #[builder(default)]
145 #[serde(skip_serializing_if = "Option::is_none")]
146 tags: Option<Vec<Tag>>,
147 #[builder(default)]
149 #[serde(skip_serializing_if = "Option::is_none")]
150 until: Option<Date>,
151 #[builder(default)]
153 #[serde(skip_serializing_if = "Option::is_none")]
154 wait: Option<Date>,
155 #[builder(default)]
157 #[serde(skip_serializing_if = "Option::is_none")]
158 urgency: Option<Urgency>,
159
160 #[builder(default)]
162 #[serde(default)]
163 #[serde(skip_serializing_if = "UDA::is_empty")]
164 #[serde(flatten)]
165 uda: UDA,
166
167 #[doc(hidden)]
168 #[builder(setter(skip))]
169 #[serde(skip)]
170 _version: PhantomData<Version>,
171}
172
173impl<Version: TaskWarriorVersion> Task<Version> {
178 #[allow(clippy::too_many_arguments)]
180 pub fn new(
181 id: Option<u64>,
182
183 status: TaskStatus,
184 uuid: Uuid,
185 entry: Date,
186 description: String,
187
188 annotations: Option<Vec<Annotation>>,
189 depends: Option<Vec<Uuid>>,
190 due: Option<Date>,
191 end: Option<Date>,
192 imask: Option<f64>,
193 mask: Option<String>,
194 modified: Option<Date>,
195 parent: Option<Uuid>,
196 priority: Option<TaskPriority>,
197 project: Option<Project>,
198 recur: Option<String>,
199 scheduled: Option<Date>,
200 start: Option<Date>,
201 tags: Option<Vec<Tag>>,
202 until: Option<Date>,
203 wait: Option<Date>,
204 urgency: Option<Urgency>,
205 uda: UDA,
206 ) -> Task<Version> {
207 Task {
208 id,
209 status,
210 uuid,
211 entry,
212 description,
213
214 annotations,
215 depends,
216 due,
217 end,
218 imask,
219 mask,
220 modified,
221 parent,
222 priority,
223 project,
224 recur,
225 scheduled,
226 start,
227 tags,
228 until,
229 wait,
230 urgency,
231 uda,
232 _version: PhantomData,
233 }
234 }
235
236 pub fn id(&self) -> Option<u64> {
238 self.id
239 }
240
241 pub fn status(&self) -> &TaskStatus {
243 &self.status
244 }
245
246 pub fn status_mut(&mut self) -> &mut TaskStatus {
248 &mut self.status
249 }
250
251 pub fn uuid(&self) -> &Uuid {
253 &self.uuid
254 }
255
256 pub fn uuid_mut(&mut self) -> &mut Uuid {
258 &mut self.uuid
259 }
260
261 pub fn entry(&self) -> &Date {
263 &self.entry
264 }
265
266 pub fn entry_mut(&mut self) -> &mut Date {
268 &mut self.entry
269 }
270
271 pub fn description(&self) -> &String {
273 &self.description
274 }
275
276 pub fn description_mut(&mut self) -> &mut String {
278 &mut self.description
279 }
280
281 pub fn annotations(&self) -> Option<&Vec<Annotation>> {
283 self.annotations.as_ref()
284 }
285
286 pub fn annotations_mut(&mut self) -> Option<&mut Vec<Annotation>> {
288 self.annotations.as_mut()
289 }
290
291 pub fn set_annotations<T, A>(&mut self, new: Option<T>)
293 where
294 T: IntoIterator,
295 T::Item: Into<Annotation>,
296 {
297 self.annotations = new.map(|x| x.into_iter().map(Into::into).collect());
298 }
299
300 pub fn depends(&self) -> Option<&Vec<Uuid>> {
302 self.depends.as_ref()
303 }
304
305 pub fn depends_mut(&mut self) -> Option<&mut Vec<Uuid>> {
307 self.depends.as_mut()
308 }
309
310 pub fn set_depends<T, U>(&mut self, new: Option<T>)
312 where
313 T: IntoIterator,
314 T::Item: Into<Uuid>,
315 {
316 self.depends = new.map(|x| x.into_iter().map(Into::into).collect());
317 }
318
319 pub fn due(&self) -> Option<&Date> {
321 self.due.as_ref()
322 }
323
324 pub fn due_mut(&mut self) -> Option<&mut Date> {
326 self.due.as_mut()
327 }
328
329 pub fn set_due<T>(&mut self, new: Option<T>)
331 where
332 T: Into<Date>,
333 {
334 self.due = new.map(Into::into)
335 }
336
337 pub fn end(&self) -> Option<&Date> {
339 self.end.as_ref()
340 }
341
342 pub fn end_mut(&mut self) -> Option<&mut Date> {
344 self.end.as_mut()
345 }
346
347 pub fn set_end<T>(&mut self, new: Option<T>)
349 where
350 T: Into<Date>,
351 {
352 self.end = new.map(Into::into)
353 }
354
355 pub fn imask(&self) -> Option<&f64> {
357 self.imask.as_ref()
358 }
359
360 pub fn imask_mut(&mut self) -> Option<&mut f64> {
362 self.imask.as_mut()
363 }
364
365 pub fn set_imask<T>(&mut self, new: Option<T>)
367 where
368 T: Into<f64>,
369 {
370 self.imask = new.map(Into::into)
371 }
372
373 pub fn mask(&self) -> Option<&String> {
375 self.mask.as_ref()
376 }
377
378 pub fn mask_mut(&mut self) -> Option<&mut String> {
380 self.mask.as_mut()
381 }
382
383 pub fn set_mask<T>(&mut self, new: Option<T>)
385 where
386 T: Into<String>,
387 {
388 self.mask = new.map(Into::into)
389 }
390
391 pub fn modified(&self) -> Option<&Date> {
393 self.modified.as_ref()
394 }
395
396 pub fn modified_mut(&mut self) -> Option<&mut Date> {
398 self.modified.as_mut()
399 }
400
401 pub fn set_modified<T>(&mut self, new: Option<T>)
403 where
404 T: Into<Date>,
405 {
406 self.modified = new.map(Into::into)
407 }
408
409 pub fn parent(&self) -> Option<&Uuid> {
411 self.parent.as_ref()
412 }
413
414 pub fn parent_mut(&mut self) -> Option<&mut Uuid> {
416 self.parent.as_mut()
417 }
418
419 pub fn set_parent<T>(&mut self, new: Option<T>)
421 where
422 T: Into<Uuid>,
423 {
424 self.parent = new.map(Into::into)
425 }
426
427 pub fn priority(&self) -> Option<&TaskPriority> {
429 self.priority.as_ref()
430 }
431
432 pub fn priority_mut(&mut self) -> Option<&mut TaskPriority> {
434 self.priority.as_mut()
435 }
436
437 pub fn set_priority<T>(&mut self, new: Option<T>)
439 where
440 T: Into<TaskPriority>,
441 {
442 self.priority = new.map(Into::into)
443 }
444
445 pub fn project(&self) -> Option<&Project> {
447 self.project.as_ref()
448 }
449
450 pub fn project_mut(&mut self) -> Option<&mut Project> {
452 self.project.as_mut()
453 }
454
455 pub fn set_project<T>(&mut self, new: Option<T>)
457 where
458 T: Into<Project>,
459 {
460 self.project = new.map(Into::into)
461 }
462
463 pub fn recur(&self) -> Option<&String> {
467 self.recur.as_ref()
468 }
469
470 pub fn recur_mut(&mut self) -> Option<&mut String> {
473 self.recur.as_mut()
474 }
475
476 pub fn set_recur<T>(&mut self, new: Option<T>)
478 where
479 T: Into<String>,
480 {
481 self.recur = new.map(Into::into)
482 }
483
484 pub fn scheduled(&self) -> Option<&Date> {
486 self.scheduled.as_ref()
487 }
488
489 pub fn scheduled_mut(&mut self) -> Option<&mut Date> {
491 self.scheduled.as_mut()
492 }
493
494 pub fn set_scheduled<T>(&mut self, new: Option<T>)
496 where
497 T: Into<Date>,
498 {
499 self.scheduled = new.map(Into::into)
500 }
501
502 pub fn start(&self) -> Option<&Date> {
504 self.start.as_ref()
505 }
506
507 pub fn start_mut(&mut self) -> Option<&mut Date> {
509 self.start.as_mut()
510 }
511
512 pub fn set_start<T>(&mut self, new: Option<T>)
514 where
515 T: Into<Date>,
516 {
517 self.start = new.map(Into::into)
518 }
519
520 pub fn tags(&self) -> Option<&Vec<Tag>> {
522 self.tags.as_ref()
523 }
524
525 pub fn tags_mut(&mut self) -> Option<&mut Vec<Tag>> {
527 self.tags.as_mut()
528 }
529
530 pub fn set_tags<T>(&mut self, new: Option<T>)
532 where
533 T: IntoIterator,
534 T::Item: Into<Tag>,
535 {
536 self.tags = new.map(|x| x.into_iter().map(Into::into).collect());
537 }
538
539 pub fn until(&self) -> Option<&Date> {
541 self.until.as_ref()
542 }
543
544 pub fn until_mut(&mut self) -> Option<&mut Date> {
546 self.until.as_mut()
547 }
548
549 pub fn set_until<T>(&mut self, new: Option<T>)
551 where
552 T: Into<Date>,
553 {
554 self.until = new.map(Into::into);
555 }
556
557 pub fn urgency(&self) -> Option<&Urgency> {
559 self.urgency.as_ref()
560 }
561
562 pub fn urgency_mut(&mut self) -> Option<&mut Urgency> {
564 self.urgency.as_mut()
565 }
566
567 pub fn set_urgency<T>(&mut self, new: Option<T>)
569 where
570 T: Into<Urgency>,
571 {
572 self.urgency = new.map(Into::into);
573 }
574
575 pub fn wait(&self) -> Option<&Date> {
577 self.wait.as_ref()
578 }
579
580 pub fn wait_mut(&mut self) -> Option<&mut Date> {
582 self.wait.as_mut()
583 }
584
585 pub fn set_wait<T>(&mut self, new: Option<T>)
587 where
588 T: Into<Date>,
589 {
590 self.wait = new.map(Into::into);
591 }
592
593 pub fn uda(&self) -> &UDA {
595 &self.uda
596 }
597 pub fn uda_mut(&mut self) -> &mut UDA {
599 &mut self.uda
600 }
601}
602
603fn serialize_depends<S, T: 'static>(
604 field: &Option<Vec<Uuid>>,
605 serializer: S,
606) -> RResult<S::Ok, S::Error>
607where
608 S: Serializer,
609{
610 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<TW25>() {
611 let value = field.as_ref().unwrap();
612 let v: Vec<String> = value.iter().map(Uuid::to_string).collect();
613 serializer.serialize_str(&v.join(","))
614 } else {
615 field.serialize(serializer)
616 }
617}
618
619fn deserialize_depends<'de, D, T: 'static>(deserializer: D) -> RResult<Option<Vec<Uuid>>, D::Error>
620where
621 D: Deserializer<'de>,
622{
623 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<TW25>() {
624 let raw: String = String::deserialize(deserializer)?;
625 let mut uuids = vec![];
626 for uuid in raw.split(',') {
627 uuids.push(Uuid::parse_str(uuid).map_err(de::Error::custom)?);
628 }
629 Ok(Some(uuids))
630 } else {
631 let value: Option<Vec<Uuid>> = Option::deserialize(deserializer)?;
632 Ok(value)
633 }
634}
635
636#[cfg(test)]
637mod test {
638 use crate::annotation::Annotation;
639 use crate::date::Date;
640 use crate::date::TASKWARRIOR_DATETIME_TEMPLATE;
641 use crate::status::TaskStatus;
642 use crate::task::{Task, TW25, TW26};
643 use crate::uda::UDAValue;
644
645 use chrono::NaiveDateTime;
646 use serde_json;
647 use uuid::{uuid, Uuid};
648
649 fn mklogger() {
650 env_logger::init();
651 log::debug!("Env-logger enabled");
652 }
653
654 fn mkdate(s: &str) -> Date {
655 let n = NaiveDateTime::parse_from_str(s, TASKWARRIOR_DATETIME_TEMPLATE);
656 Date::from(n.unwrap())
657 }
658
659 #[test]
660 fn test_deser() {
661 let s = r#"{
662"id": 1,
663"description": "test",
664"entry": "20150619T165438Z",
665"status": "waiting",
666"uuid": "8ca953d5-18b4-4eb9-bd56-18f2e5b752f0",
667"urgency": 5.3
668}"#;
669
670 println!("{}", s);
671
672 let task = serde_json::from_str(s);
673 println!("{:?}", task);
674 assert!(task.is_ok());
675 let task: Task = task.unwrap();
676
677 assert_eq!(*task.status(), TaskStatus::Waiting);
678 assert_eq!(task.description(), "test");
679 assert_eq!(*task.entry(), mkdate("20150619T165438Z"));
680 assert_eq!(
681 *task.uuid(),
682 Uuid::parse_str("8ca953d5-18b4-4eb9-bd56-18f2e5b752f0").unwrap()
683 );
684 assert_eq!(task.urgency(), Some(&5.3));
685
686 let back = serde_json::to_string(&task).unwrap();
687
688 assert!(back.contains("description"));
689 assert!(back.contains("test"));
690 assert!(back.contains("entry"));
691 assert!(back.contains("20150619T165438Z"));
692 assert!(back.contains("status"));
693 assert!(back.contains("waiting"));
694 assert!(back.contains("uuid"));
695 assert!(back.contains("urgency"));
696 assert!(back.contains("8ca953d5-18b4-4eb9-bd56-18f2e5b752f0"));
697 }
698
699 #[test]
700 fn test_deser_more_tw26() {
701 let s = r#"{
702"id": 1,
703"description": "some description",
704"entry": "20150619T165438Z",
705"modified": "20160327T164007Z",
706"project": "someproject",
707"status": "waiting",
708"tags": ["some", "tags", "are", "here"],
709"uuid": "8ca953d5-18b4-4eb9-bd56-18f2e5b752f0",
710"depends": ["8ca953d5-18b4-4eb9-bd56-18f2e5b752f0","5a04bb1e-3f4b-49fb-b9ba-44407ca223b5"],
711"wait": "20160508T164007Z",
712"urgency": 0.583562
713}"#;
714 let task = serde_json::from_str(s);
715 assert!(task.is_ok());
716 let task: Task = task.unwrap();
717
718 assert_eq!(*task.status(), TaskStatus::Waiting);
719 assert_eq!(task.description(), "some description");
720 assert_eq!(*task.entry(), mkdate("20150619T165438Z"));
721 assert_eq!(
722 *task.uuid(),
723 Uuid::parse_str("8ca953d5-18b4-4eb9-bd56-18f2e5b752f0").unwrap()
724 );
725 assert_eq!(task.urgency(), Some(&0.583562));
726 assert_eq!(task.modified(), Some(&mkdate("20160327T164007Z")));
727 assert_eq!(task.project(), Some(&String::from("someproject")));
728
729 if let Some(tags) = task.tags() {
730 for tag in tags {
731 let any_tag = ["some", "tags", "are", "here"].iter().any(|t| tag == *t);
732 assert!(any_tag, "Tag {} missing", tag);
733 }
734 } else {
735 panic!("Tags completely missing");
736 }
737
738 assert_eq!(task.wait(), Some(&mkdate("20160508T164007Z")));
739
740 if let Some(depends) = task.depends() {
741 assert_eq!(depends.len(), 2);
742 assert!(depends.contains(&uuid!("8ca953d5-18b4-4eb9-bd56-18f2e5b752f0")));
743 assert!(depends.contains(&uuid!("5a04bb1e-3f4b-49fb-b9ba-44407ca223b5")));
744 } else {
745 panic!("Depends completely missing");
746 }
747
748 assert_eq!(task.wait(), Some(&mkdate("20160508T164007Z")));
749
750 let back = serde_json::to_string(&task).unwrap();
751
752 assert!(back.contains("description"));
753 assert!(back.contains("some description"));
754 assert!(back.contains("entry"));
755 assert!(back.contains("20150619T165438Z"));
756 assert!(back.contains("project"));
757 assert!(back.contains("someproject"));
758 assert!(back.contains("status"));
759 assert!(back.contains("waiting"));
760 assert!(back.contains("tags"));
761 assert!(back.contains("some"));
762 assert!(back.contains("tags"));
763 assert!(back.contains("are"));
764 assert!(back.contains("here"));
765 assert!(back.contains("uuid"));
766 assert!(back.contains("8ca953d5-18b4-4eb9-bd56-18f2e5b752f0"));
767 assert!(back.contains(
768 r#"["8ca953d5-18b4-4eb9-bd56-18f2e5b752f0","5a04bb1e-3f4b-49fb-b9ba-44407ca223b5"]"#,
769 ));
770 }
771
772 #[test]
773 fn test_deser_more_tw25() {
774 mklogger();
775 let s = r#"{
776"id": 1,
777"description": "some description",
778"entry": "20150619T165438Z",
779"modified": "20160327T164007Z",
780"project": "someproject",
781"status": "waiting",
782"tags": ["some", "tags", "are", "here"],
783"uuid": "8ca953d5-18b4-4eb9-bd56-18f2e5b752f0",
784"depends": "8ca953d5-18b4-4eb9-bd56-18f2e5b752f0,5a04bb1e-3f4b-49fb-b9ba-44407ca223b5",
785"wait": "20160508T164007Z",
786"urgency": 0.583562
787}"#;
788
789 println!("{}", s);
790
791 let task = serde_json::from_str(s);
792 println!("{:?}", task);
793 assert!(task.is_ok());
794 let task: Task<TW25> = task.unwrap();
795
796 assert_eq!(*task.status(), TaskStatus::Waiting);
797 assert_eq!(task.description(), "some description");
798 assert_eq!(*task.entry(), mkdate("20150619T165438Z"));
799 assert_eq!(
800 *task.uuid(),
801 Uuid::parse_str("8ca953d5-18b4-4eb9-bd56-18f2e5b752f0").unwrap()
802 );
803 assert_eq!(task.urgency(), Some(&0.583562));
804 assert_eq!(task.modified(), Some(&mkdate("20160327T164007Z")));
805 assert_eq!(task.project(), Some(&String::from("someproject")));
806
807 if let Some(tags) = task.tags() {
808 for tag in tags {
809 let any_tag = ["some", "tags", "are", "here"].iter().any(|t| tag == *t);
810 assert!(any_tag, "Tag {} missing", tag);
811 }
812 } else {
813 panic!("Tags completely missing");
814 }
815
816 if let Some(depends) = task.depends() {
817 assert_eq!(depends.len(), 2);
818 assert!(depends.contains(&uuid!("8ca953d5-18b4-4eb9-bd56-18f2e5b752f0")));
819 assert!(depends.contains(&uuid!("5a04bb1e-3f4b-49fb-b9ba-44407ca223b5")));
820 } else {
821 panic!("Depends completely missing");
822 }
823
824 assert_eq!(task.wait(), Some(&mkdate("20160508T164007Z")));
825
826 let back = serde_json::to_string(&task).unwrap();
827
828 assert!(back.contains("description"));
829 assert!(back.contains("some description"));
830 assert!(back.contains("entry"));
831 assert!(back.contains("20150619T165438Z"));
832 assert!(back.contains("project"));
833 assert!(back.contains("someproject"));
834 assert!(back.contains("status"));
835 assert!(back.contains("waiting"));
836 assert!(back.contains("tags"));
837 assert!(back.contains("some"));
838 assert!(back.contains("tags"));
839 assert!(back.contains("are"));
840 assert!(back.contains("here"));
841 assert!(back.contains("uuid"));
842 assert!(back.contains("8ca953d5-18b4-4eb9-bd56-18f2e5b752f0"));
843 assert!(
844 back.contains(
845 "8ca953d5-18b4-4eb9-bd56-18f2e5b752f0,5a04bb1e-3f4b-49fb-b9ba-44407ca223b5",
846 )
847 );
848 }
849
850 #[test]
851 fn test_deser_annotation() {
852 let s = r#"{
853"id":192,
854"description":"Some long description for a task",
855"entry":"20160423T125820Z",
856"modified":"20160423T125942Z",
857"project":"project",
858"status":"pending",
859"tags":["search","things"],
860"uuid":"5a04bb1e-3f4b-49fb-b9ba-44407ca223b5",
861"annotations":[{"entry":"20160423T125911Z","description":"An Annotation"},
862 {"entry":"20160423T125926Z","description":"Another Annotation"},
863 {"entry":"20160422T125942Z","description":"A Third Anno"}
864 ],
865"urgency": -5
866}"#;
867
868 println!("{}", s);
869
870 let task = serde_json::from_str(s);
871 println!("{:?}", task);
872 assert!(task.is_ok());
873 let task: Task = task.unwrap();
874
875 assert_eq!(task.urgency(), Some(&-5.0));
876
877 let all_annotations = vec![
878 Annotation::new(mkdate("20160423T125911Z"), String::from("An Annotation")),
879 Annotation::new(
880 mkdate("20160423T125926Z"),
881 String::from("Another Annotation"),
882 ),
883 Annotation::new(mkdate("20160422T125942Z"), String::from("A Third Anno")),
884 ];
885
886 if let Some(annotations) = task.annotations() {
887 for annotation in annotations {
888 let r = all_annotations.iter().any(|ann| {
889 let descr = ann.description() == annotation.description();
890 let entry = ann.entry() == annotation.entry();
891
892 descr && entry
893 });
894 assert!(r, "Annotation {:?} missing or buggy", annotation);
895 }
896 } else {
897 panic!("Annotations missing");
898 }
899 }
900 #[test]
901 fn test_uda() {
902 let s = r#"{
903"description":"Some long description for a task",
904"entry":"20160423T125820Z",
905"modified":"20160423T125942Z",
906"project":"project",
907"status":"pending",
908"uuid":"5a04bb1e-3f4b-49fb-b9ba-44407ca223b5",
909"test_str_uda":"test_str_uda_value",
910"test_float_uda":-17.1234,
911"test_int_uda":1234
912}"#;
913
914 println!("{}", s);
915
916 let task = serde_json::from_str(s);
917 println!("{:?}", task);
918 assert!(task.is_ok());
919 let task: Task<TW25> = task.unwrap();
920
921 let str_uda = task.uda().get(&"test_str_uda".to_owned());
922 assert!(str_uda.is_some());
923 let str_uda = str_uda.unwrap();
924 assert_eq!(str_uda, &UDAValue::Str("test_str_uda_value".to_owned()));
925
926 let float_uda = task.uda().get(&"test_float_uda".to_owned());
927 assert!(float_uda.is_some());
928 let float_uda = float_uda.unwrap();
929 assert_eq!(float_uda, &UDAValue::F64(-17.1234));
930
931 let int_uda = task.uda().get(&"test_int_uda".to_owned());
932 assert!(int_uda.is_some());
933 let int_uda = int_uda.unwrap();
934 assert_eq!(int_uda, &UDAValue::U64(1234));
935
936 let back = serde_json::to_string_pretty(&task);
937 assert!(back.is_ok());
938 let back = back.unwrap();
939 println!("{}", back);
940 assert!(back.contains("description"));
941 assert!(back.contains("Some long description for a task"));
942 assert!(back.contains("entry"));
943 assert!(back.contains("20160423T125820Z"));
944 assert!(back.contains("project"));
945 assert!(back.contains("status"));
946 assert!(back.contains("pending"));
947 assert!(back.contains("uuid"));
948 assert!(back.contains("5a04bb1e-3f4b-49fb-b9ba-44407ca223b5"));
949 assert!(back.contains("test_str_uda"));
950 assert!(back.contains("test_str_uda_value"));
951 assert!(back.contains("test_float_uda"));
952 assert!(back.contains("-17.1234"));
953 assert!(back.contains("test_int_uda"));
954 assert!(back.contains("1234"));
955 }
956 #[test]
957 fn test_priority() {
958 let s = r#"{
959"id":9,
960"description":"Some long description for a task",
961"entry":"20201021T065503Z",
962"estimate":"30",
963"modified":"20210213T233603Z",
964"priority":"U",
965"status":"pending",
966"uuid":"6c4c9ee8-d6c4-4d64-a84d-bf9cb710684e",
967"urgency":23
968}"#;
969
970 println!("{}", s);
971
972 let task = serde_json::from_str(s);
973 println!("{:?}", task);
974 assert!(task.is_ok());
975 let task: Task = task.unwrap();
976
977 if let Some(priority) = task.priority() {
978 assert_eq!(*priority, "U".to_string());
979 } else {
980 panic!("Priority completely missing");
981 }
982
983 let back = serde_json::to_string_pretty(&task);
984 assert!(back.is_ok());
985 let back = back.unwrap();
986 println!("{}", back);
987 assert!(back.contains("description"));
988 assert!(back.contains("Some long description for a task"));
989 assert!(back.contains("entry"));
990 assert!(back.contains("20201021T065503Z"));
991 assert!(back.contains("priority"));
992 assert!(back.contains("status"));
993 assert!(back.contains("pending"));
994 assert!(back.contains("uuid"));
995 assert!(back.contains("6c4c9ee8-d6c4-4d64-a84d-bf9cb710684e"));
996 }
997
998 #[test]
999 fn test_builder_simple() {
1000 use crate::task::TaskBuilder;
1001
1002 let t = TaskBuilder::<TW25>::default()
1003 .description("test")
1004 .entry(mkdate("20150619T165438Z"))
1005 .build();
1006 println!("{:?}", t);
1007 assert!(t.is_ok());
1008 let t = t.unwrap();
1009
1010 assert_eq!(t.status(), &TaskStatus::Pending);
1011 assert_eq!(t.description(), "test");
1012 assert_eq!(t.entry(), &mkdate("20150619T165438Z"));
1013 }
1014 #[test]
1015 fn test_builder_extensive() {
1016 use crate::task::TaskBuilder;
1017 use crate::task::TW25;
1018 use crate::uda::{UDAValue, UDA};
1019 let mut uda = UDA::new();
1020 uda.insert(
1021 "test_str_uda".into(),
1022 UDAValue::Str("test_str_uda_value".into()),
1023 );
1024 uda.insert("test_int_uda".into(), UDAValue::U64(1234));
1025 uda.insert("test_float_uda".into(), UDAValue::F64(-17.1234));
1026 let t = TaskBuilder::<TW25>::default()
1027 .description("test")
1028 .entry(mkdate("20150619T165438Z"))
1029 .id(192)
1030 .modified(mkdate("20160423T125942Z"))
1031 .project("project".to_owned())
1032 .tags(vec!["search".to_owned(), "things".to_owned()])
1033 .uda(uda)
1034 .build();
1035 println!("{:?}", t);
1036 assert!(t.is_ok());
1037 let t = t.unwrap();
1038
1039 assert!(t.id().is_some());
1040 assert_eq!(t.id().unwrap(), 192);
1041 assert_eq!(t.status(), &TaskStatus::Pending);
1042 assert_eq!(t.description(), "test");
1043 assert_eq!(t.entry(), &mkdate("20150619T165438Z"));
1044 assert!(t.modified().is_some());
1045 assert_eq!(t.modified().unwrap(), &mkdate("20160423T125942Z"));
1046 assert!(t.project().is_some());
1047 assert_eq!(t.project().unwrap(), "project");
1048 assert!(t.tags().is_some());
1049 assert!(t.urgency().is_none());
1050 assert_eq!(
1051 t.tags().unwrap(),
1052 &vec!["search".to_owned(), "things".to_owned()]
1053 );
1054 }
1055 #[test]
1056 fn test_builder_defaults() {
1057 use crate::task::TaskBuilder;
1058 assert!(TaskBuilder::<TW25>::default()
1059 .description("Nice Task")
1060 .build()
1061 .is_ok());
1062 }
1063
1064 #[test]
1065 fn test_builder_fail() {
1066 use crate::task::TaskBuilder;
1067 assert!(TaskBuilder::<TW25>::default().build().is_err());
1068 }
1069
1070 const FIELD_NAMES_TO_NOT_SERIALIZE: [&str; 20] = [
1071 r#""id":"#,
1072 r#"""annotations:""#,
1073 r#""depends:""#,
1074 r#""due:""#,
1075 r#""end:""#,
1076 r#""imask:""#,
1077 r#""mask:""#,
1078 r#""modified:""#,
1079 r#""parent:""#,
1080 r#""priority:""#,
1081 r#""project:""#,
1082 r#""recur:""#,
1083 r#""scheduled:""#,
1084 r#""start:""#,
1085 r#""tags:""#,
1086 r#""until:""#,
1087 r#""wait:""#,
1088 r#""urgency:""#,
1089 r#""uda:""#,
1090 r#""_version:""#,
1091 ];
1092
1093 #[test]
1094 fn test_null_fields_not_serialized_tw25() {
1095 use crate::task::TaskBuilder;
1096
1097 let task = TaskBuilder::<TW25>::default()
1098 .description("Test Task")
1099 .build()
1100 .expect("Task to be built");
1101
1102 let task_as_str = serde_json::to_string_pretty(&task).expect("Task serialized as string");
1103
1104 for field_name in FIELD_NAMES_TO_NOT_SERIALIZE {
1105 assert!(
1106 !task_as_str.contains(field_name),
1107 "'{}' should not have been in {}",
1108 field_name,
1109 task_as_str
1110 );
1111 }
1112 }
1113
1114 #[test]
1115 fn test_null_fields_not_serialized_tw26() {
1116 use crate::task::TaskBuilder;
1117
1118 let task = TaskBuilder::<TW26>::default()
1119 .description("Test Task")
1120 .build()
1121 .expect("Task to be built");
1122
1123 let task_as_str = serde_json::to_string_pretty(&task).expect("Task serialized as string");
1124
1125 for field_name in FIELD_NAMES_TO_NOT_SERIALIZE {
1126 assert!(
1127 !task_as_str.contains(field_name),
1128 "'{}' should not have been in {}",
1129 field_name,
1130 task_as_str
1131 );
1132 }
1133 }
1134}