1use serde::{Deserialize, Serialize};
2use tabled::Tabled;
3
4#[derive(Debug, Deserialize)]
5pub struct TaskListResponse {
6 pub tasks: Vec<Task>,
7}
8
9#[derive(Debug, Deserialize, Tabled)]
10#[tabled(rename_all = "UPPERCASE")]
11pub struct Task {
12 pub id: String,
13 pub title: String,
14 #[tabled(display_with = "display_priority")]
15 pub priority: Option<i32>,
16 #[tabled(display_with = "display_checked")]
17 pub checked: Option<i32>,
18 #[tabled(skip)]
19 pub note: Option<String>,
20 #[tabled(skip)]
21 #[serde(rename = "projectId")]
22 pub project_id: Option<String>,
23 #[tabled(skip)]
24 pub parent: Option<String>,
25 #[tabled(skip)]
26 pub group: Option<String>,
27 #[tabled(skip)]
28 pub start: Option<String>,
29 #[tabled(skip)]
30 pub deadline: Option<String>,
31 #[tabled(skip)]
32 pub tags: Option<Vec<String>>,
33 #[tabled(skip)]
34 #[serde(rename = "showInBasket")]
35 #[allow(dead_code)]
36 pub show_in_basket: Option<bool>,
37 #[tabled(skip)]
38 #[serde(rename = "modificatedDate")]
39 #[allow(dead_code)]
40 pub modificated_date: Option<String>,
41 #[tabled(skip)]
42 #[serde(rename = "isNote")]
43 #[allow(dead_code)]
44 pub is_note: Option<bool>,
45}
46
47fn display_priority(p: &Option<i32>) -> String {
48 match p {
49 Some(0) => "high".to_string(),
50 Some(1) => "normal".to_string(),
51 Some(2) => "low".to_string(),
52 _ => "-".to_string(),
53 }
54}
55
56fn display_checked(c: &Option<i32>) -> String {
57 match c {
58 Some(0) => "empty".to_string(),
59 Some(1) => "checked".to_string(),
60 Some(2) => "cancelled".to_string(),
61 _ => "-".to_string(),
62 }
63}
64
65impl Task {
66 pub fn display_detail(&self) {
67 println!("ID: {}", self.id);
68 println!("Title: {}", self.title);
69 println!("Priority: {}", display_priority(&self.priority));
70 println!("Checked: {}", display_checked(&self.checked));
71 if let Some(ref v) = self.note {
72 println!("Note: {}", v);
73 }
74 if let Some(ref v) = self.project_id {
75 println!("Project: {}", v);
76 }
77 if let Some(ref v) = self.parent {
78 println!("Parent: {}", v);
79 }
80 if let Some(ref v) = self.group {
81 println!("Group: {}", v);
82 }
83 if let Some(ref v) = self.start {
84 println!("Start: {}", v);
85 }
86 if let Some(ref v) = self.deadline {
87 println!("Deadline: {}", v);
88 }
89 if let Some(ref v) = self.tags
90 && !v.is_empty()
91 {
92 println!("Tags: {}", v.join(", "));
93 }
94 }
95}
96
97#[derive(Debug, Serialize, Default)]
98pub struct TaskCreate {
99 pub title: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub note: Option<String>,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub priority: Option<i32>,
104 #[serde(skip_serializing_if = "Option::is_none", rename = "projectId")]
105 pub project_id: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub parent: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub group: Option<String>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub deadline: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub start: Option<String>,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub tags: Option<Vec<String>>,
116 #[serde(skip_serializing_if = "Option::is_none", rename = "isNote")]
117 pub is_note: Option<bool>,
118}
119
120#[derive(Debug, Serialize, Default)]
121pub struct TaskUpdate {
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub title: Option<String>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub note: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub priority: Option<i32>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub checked: Option<i32>,
130 #[serde(skip_serializing_if = "Option::is_none", rename = "projectId")]
131 pub project_id: Option<String>,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub parent: Option<String>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub group: Option<String>,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 pub deadline: Option<String>,
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub start: Option<String>,
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub tags: Option<Vec<String>>,
142 #[serde(skip_serializing_if = "Option::is_none", rename = "isNote")]
143 pub is_note: Option<bool>,
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn deserialize_task_full() {
152 let json = r#"{
153 "id": "T-abc",
154 "title": "Do stuff",
155 "priority": 0,
156 "checked": 1,
157 "projectId": "P-123",
158 "tags": ["t1"],
159 "showInBasket": false,
160 "modificatedDate": "2025-01-01T00:00:00Z",
161 "isNote": false
162 }"#;
163 let t: Task = serde_json::from_str(json).unwrap();
164 assert_eq!(t.id, "T-abc");
165 assert_eq!(t.priority, Some(0));
166 assert_eq!(t.checked, Some(1));
167 assert_eq!(t.project_id.as_deref(), Some("P-123"));
168 }
169
170 #[test]
171 fn deserialize_task_minimal() {
172 let json = r#"{"id": "T-min", "title": "Minimal"}"#;
173 let t: Task = serde_json::from_str(json).unwrap();
174 assert_eq!(t.id, "T-min");
175 assert!(t.priority.is_none());
176 assert!(t.project_id.is_none());
177 }
178
179 #[test]
180 fn serialize_create_skips_none() {
181 let data = TaskCreate {
182 title: "Test task".to_string(),
183 ..Default::default()
184 };
185 let json = serde_json::to_value(&data).unwrap();
186 assert_eq!(json, serde_json::json!({"title": "Test task"}));
187 }
188
189 #[test]
190 fn serialize_create_camel_case_rename() {
191 let data = TaskCreate {
192 title: "T".to_string(),
193 project_id: Some("P-1".to_string()),
194 is_note: Some(true),
195 ..Default::default()
196 };
197 let json = serde_json::to_value(&data).unwrap();
198 assert_eq!(json["projectId"], "P-1");
199 assert_eq!(json["isNote"], true);
200 assert!(json.get("project_id").is_none());
201 }
202
203 #[test]
204 fn serialize_update_partial() {
205 let data = TaskUpdate {
206 checked: Some(1),
207 ..Default::default()
208 };
209 let json = serde_json::to_value(&data).unwrap();
210 assert_eq!(json, serde_json::json!({"checked": 1}));
211 }
212
213 #[test]
214 fn display_priority_values() {
215 assert_eq!(display_priority(&Some(0)), "high");
216 assert_eq!(display_priority(&Some(1)), "normal");
217 assert_eq!(display_priority(&Some(2)), "low");
218 assert_eq!(display_priority(&None), "-");
219 assert_eq!(display_priority(&Some(99)), "-");
220 }
221
222 #[test]
223 fn display_checked_values() {
224 assert_eq!(display_checked(&Some(0)), "empty");
225 assert_eq!(display_checked(&Some(1)), "checked");
226 assert_eq!(display_checked(&Some(2)), "cancelled");
227 assert_eq!(display_checked(&None), "-");
228 }
229}