Skip to main content

singularity_cli/models/
tag.rs

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