Skip to main content

singularity_cli/models/
tag.rs

1use serde::{Deserialize, Serialize};
2
3fn display_opt(o: &Option<String>) -> String {
4    o.as_deref().unwrap_or("-").to_string()
5}
6
7#[derive(Debug, Deserialize)]
8pub struct TagListResponse {
9    pub tags: Vec<Tag>,
10}
11
12#[derive(Debug, Deserialize)]
13pub struct Tag {
14    pub id: String,
15    pub title: String,
16    pub parent: Option<String>,
17    pub order: Option<f64>,
18    #[serde(rename = "modificatedDate")]
19    #[allow(dead_code)]
20    pub modificated_date: Option<String>,
21}
22
23impl Tag {
24    pub fn display_detail(&self) -> String {
25        let mut lines = vec![
26            format!("**ID:** {}", self.id),
27            format!("**Title:** {}", self.title),
28        ];
29        if let Some(ref v) = self.parent {
30            lines.push(format!("**Parent:** {}", v));
31        }
32        if let Some(v) = self.order {
33            lines.push(format!("**Order:** {}", v));
34        }
35        lines.join("\n")
36    }
37
38    pub fn display_list_item(&self) -> String {
39        format!(
40            "- ID: {}\n  Tag: {}\n  Parent: {}",
41            self.id,
42            self.title,
43            display_opt(&self.parent)
44        )
45    }
46}
47
48#[derive(Debug, Serialize)]
49pub struct TagCreate {
50    pub title: String,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub parent: Option<String>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub order: Option<f64>,
55}
56
57#[derive(Debug, Serialize, Default)]
58pub struct TagUpdate {
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub title: Option<String>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub parent: Option<String>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub order: Option<f64>,
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn deserialize_tag_list() {
73        let json = r#"{"tags": [{"id": "tag-1", "title": "urgent", "order": 1.0}]}"#;
74        let resp: TagListResponse = serde_json::from_str(json).unwrap();
75        assert_eq!(resp.tags.len(), 1);
76        assert_eq!(resp.tags[0].id, "tag-1");
77        assert_eq!(resp.tags[0].order, Some(1.0));
78    }
79
80    #[test]
81    fn deserialize_tag_minimal() {
82        let json = r#"{"id": "tag-2", "title": "low"}"#;
83        let t: Tag = serde_json::from_str(json).unwrap();
84        assert_eq!(t.id, "tag-2");
85        assert!(t.parent.is_none());
86        assert!(t.order.is_none());
87    }
88
89    #[test]
90    fn serialize_create_skips_none() {
91        let data = TagCreate {
92            title: "bug".to_string(),
93            parent: None,
94            order: None,
95        };
96        let json = serde_json::to_value(&data).unwrap();
97        assert_eq!(json, serde_json::json!({"title": "bug"}));
98    }
99
100    #[test]
101    fn serialize_update_partial() {
102        let data = TagUpdate {
103            title: Some("renamed".to_string()),
104            ..Default::default()
105        };
106        let json = serde_json::to_value(&data).unwrap();
107        assert_eq!(json, serde_json::json!({"title": "renamed"}));
108    }
109}