boarddown_schema/
metadata.rs1use 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}