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