Skip to main content

boarddown_schema/
metadata.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use ts_rs::TS;
4
5#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
6#[ts(export)]
7pub struct Metadata {
8    #[serde(skip_serializing_if = "Option::is_none")]
9    pub assign: Option<String>,
10    #[serde(skip_serializing_if = "Vec::is_empty", default)]
11    pub tags: Vec<String>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub priority: Option<Priority>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub estimate: Option<u32>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub due: Option<chrono::DateTime<chrono::Utc>>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub started: Option<chrono::DateTime<chrono::Utc>>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub closed: Option<chrono::DateTime<chrono::Utc>>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub epic: Option<String>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub branch: Option<String>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub pr: Option<String>,
28    #[serde(default)]
29    #[ts(type = "Record<string, unknown>")]
30    pub custom: HashMap<String, serde_json::Value>,
31}
32
33impl Metadata {
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    pub fn with(mut self, key: &str, value: impl Into<serde_json::Value>) -> Self {
39        match key {
40            "assign" => self.assign = value.into().as_str().map(String::from),
41            "tags" => {
42                if let serde_json::Value::Array(arr) = value.into() {
43                    self.tags = arr
44                        .into_iter()
45                        .filter_map(|v| v.as_str().map(String::from))
46                        .collect();
47                }
48            }
49            "priority" => {
50                self.priority = serde_json::from_value(value.into()).ok();
51            }
52            "estimate" => {
53                self.estimate = value.into().as_u64().map(|v| v as u32);
54            }
55            other => {
56                self.custom.insert(other.to_string(), value.into());
57            }
58        }
59        self
60    }
61
62    pub fn get(&self, key: &str) -> Option<serde_json::Value> {
63        match key {
64            "assign" => self.assign.as_ref().map(|s| serde_json::Value::String(s.clone())),
65            "tags" => Some(serde_json::Value::Array(
66                self.tags.iter().map(|s| serde_json::Value::String(s.clone())).collect(),
67            )),
68            "priority" => self.priority.map(|p| serde_json::to_value(p).unwrap()),
69            "estimate" => self.estimate.map(|e| serde_json::Value::Number(e.into())),
70            other => self.custom.get(other).cloned(),
71        }
72    }
73
74    pub fn remove(&mut self, key: &str) {
75        match key {
76            "assign" => self.assign = None,
77            "tags" => self.tags.clear(),
78            "priority" => self.priority = None,
79            "estimate" => self.estimate = None,
80            "due" => self.due = None,
81            "started" => self.started = None,
82            "closed" => self.closed = None,
83            "epic" => self.epic = None,
84            "branch" => self.branch = None,
85            "pr" => self.pr = None,
86            other => { self.custom.remove(other); }
87        }
88    }
89
90    pub fn set(&mut self, key: &str, value: serde_json::Value) {
91        match key {
92            "assign" => self.assign = value.as_str().map(String::from),
93            "tags" => {
94                if let serde_json::Value::Array(arr) = value {
95                    self.tags = arr
96                        .into_iter()
97                        .filter_map(|v| v.as_str().map(String::from))
98                        .collect();
99                }
100            }
101            "priority" => {
102                self.priority = serde_json::from_value(value).ok();
103            }
104            "estimate" => {
105                self.estimate = value.as_u64().map(|v| v as u32);
106            }
107            "due" => {
108                self.due = serde_json::from_value(value).ok();
109            }
110            "started" => {
111                self.started = serde_json::from_value(value).ok();
112            }
113            "closed" => {
114                self.closed = serde_json::from_value(value).ok();
115            }
116            "epic" => {
117                self.epic = value.as_str().map(String::from);
118            }
119            "branch" => {
120                self.branch = value.as_str().map(String::from);
121            }
122            "pr" => {
123                self.pr = value.as_str().map(String::from);
124            }
125            other => {
126                self.custom.insert(other.to_string(), value);
127            }
128        }
129    }
130
131    pub fn merge(&mut self, other: &Metadata) {
132        if other.assign.is_some() {
133            self.assign = other.assign.clone();
134        }
135        if !other.tags.is_empty() {
136            for tag in &other.tags {
137                if !self.tags.contains(tag) {
138                    self.tags.push(tag.clone());
139                }
140            }
141        }
142        if other.priority.is_some() {
143            self.priority = other.priority;
144        }
145        if other.estimate.is_some() {
146            self.estimate = other.estimate;
147        }
148        if other.due.is_some() {
149            self.due = other.due;
150        }
151        if other.started.is_some() {
152            self.started = other.started;
153        }
154        if other.closed.is_some() {
155            self.closed = other.closed;
156        }
157        if other.epic.is_some() {
158            self.epic = other.epic.clone();
159        }
160        if other.branch.is_some() {
161            self.branch = other.branch.clone();
162        }
163        if other.pr.is_some() {
164            self.pr = other.pr.clone();
165        }
166        for (k, v) in &other.custom {
167            self.custom.insert(k.clone(), v.clone());
168        }
169    }
170
171    pub fn iter(&self) -> impl Iterator<Item = (String, serde_json::Value)> + '_ {
172        let standard: Vec<(String, serde_json::Value)> = [
173            self.assign.as_ref().map(|s| ("assign".to_string(), serde_json::Value::String(s.clone()))),
174            if !self.tags.is_empty() {
175                Some(("tags".to_string(), serde_json::Value::Array(
176                    self.tags.iter().map(|s| serde_json::Value::String(s.clone())).collect(),
177                )))
178            } else { None },
179            self.priority.map(|p| ("priority".to_string(), serde_json::to_value(p).unwrap())),
180            self.estimate.map(|e| ("estimate".to_string(), serde_json::Value::Number(e.into()))),
181            self.due.map(|d| ("due".to_string(), serde_json::to_value(d).unwrap())),
182        ].into_iter().flatten().collect();
183        
184        standard.into_iter().chain(
185            self.custom.iter().map(|(k, v)| (k.clone(), v.clone()))
186        )
187    }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
191#[ts(export)]
192#[serde(rename_all = "lowercase")]
193pub enum Priority {
194    Low,
195    Medium,
196    High,
197    Critical,
198}
199
200impl Default for Priority {
201    fn default() -> Self {
202        Self::Medium
203    }
204}
205
206impl std::str::FromStr for Priority {
207    type Err = String;
208    
209    fn from_str(s: &str) -> Result<Self, Self::Err> {
210        match s.to_lowercase().as_str() {
211            "low" => Ok(Self::Low),
212            "medium" => Ok(Self::Medium),
213            "high" => Ok(Self::High),
214            "critical" => Ok(Self::Critical),
215            _ => Err(format!("Invalid priority: {}", s)),
216        }
217    }
218}