Skip to main content

rustkanban_shared/
lib.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct SyncPayload {
6    pub tasks: Vec<SyncTask>,
7    pub tags: Vec<SyncTag>,
8    pub last_synced_at: Option<String>,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct SyncTask {
13    pub uuid: String,
14    pub title: String,
15    #[serde(default)]
16    pub description: String,
17    #[serde(default = "default_priority")]
18    pub priority: String,
19    #[serde(default = "default_column")]
20    pub column: String,
21    #[serde(default)]
22    pub due_date: Option<String>,
23    #[serde(default)]
24    pub tags: Vec<String>,
25    pub created_at: String,
26    pub updated_at: String,
27    #[serde(default)]
28    pub deleted: bool,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct SyncTag {
33    pub uuid: String,
34    pub name: String,
35    pub updated_at: String,
36    #[serde(default)]
37    pub deleted: bool,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct SyncResponse {
42    pub tasks: Vec<SyncTask>,
43    pub tags: Vec<SyncTag>,
44    #[serde(default)]
45    pub tag_uuid_mappings: HashMap<String, String>,
46    pub synced_at: String,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ApiError {
51    pub error: String,
52    pub message: String,
53}
54
55fn default_priority() -> String {
56    "Medium".to_string()
57}
58
59fn default_column() -> String {
60    "todo".to_string()
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_sync_task_defaults() {
69        let json = r#"{"uuid":"abc","title":"t","created_at":"2026-01-01T00:00:00","updated_at":"2026-01-01T00:00:00"}"#;
70        let task: SyncTask = serde_json::from_str(json).unwrap();
71        assert_eq!(task.priority, "Medium");
72        assert_eq!(task.column, "todo");
73        assert_eq!(task.description, "");
74        assert!(!task.deleted);
75        assert!(task.tags.is_empty());
76    }
77
78    #[test]
79    fn test_sync_response_defaults() {
80        let json = r#"{"tasks":[],"tags":[],"synced_at":"2026-01-01T00:00:00"}"#;
81        let resp: SyncResponse = serde_json::from_str(json).unwrap();
82        assert!(resp.tag_uuid_mappings.is_empty());
83    }
84
85    #[test]
86    fn test_roundtrip_payload() {
87        let payload = SyncPayload {
88            tasks: vec![SyncTask {
89                uuid: "uuid-1".into(),
90                title: "Test".into(),
91                description: "desc".into(),
92                priority: "High".into(),
93                column: "done".into(),
94                due_date: Some("2026-06-15".into()),
95                tags: vec!["tag-uuid-1".into()],
96                created_at: "2026-01-01T00:00:00".into(),
97                updated_at: "2026-01-01T00:00:00".into(),
98                deleted: false,
99            }],
100            tags: vec![SyncTag {
101                uuid: "tag-uuid-1".into(),
102                name: "bug".into(),
103                updated_at: "2026-01-01T00:00:00".into(),
104                deleted: false,
105            }],
106            last_synced_at: Some("2026-01-01T00:00:00".into()),
107        };
108        let json = serde_json::to_string(&payload).unwrap();
109        let roundtrip: SyncPayload = serde_json::from_str(&json).unwrap();
110        assert_eq!(roundtrip.tasks.len(), 1);
111        assert_eq!(roundtrip.tags.len(), 1);
112    }
113}