1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Issue {
10 pub id: String,
12 pub identifier: String,
14 pub title: String,
15 pub description: Option<String>,
16 pub priority: Option<i32>,
18 pub state: String,
20 pub branch_name: Option<String>,
22 pub url: Option<String>,
23 pub labels: Vec<String>,
25 pub blocked_by: Vec<BlockerRef>,
26 pub created_at: Option<DateTime<Utc>>,
27 pub updated_at: Option<DateTime<Utc>>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct BlockerRef {
33 pub id: Option<String>,
34 pub identifier: Option<String>,
35 pub state: Option<String>,
36}
37
38impl Issue {
39 pub fn workspace_key(&self) -> String {
42 self.identifier
43 .chars()
44 .map(|c| {
45 if c.is_ascii_alphanumeric() || c == '.' || c == '_' || c == '-' {
46 c
47 } else {
48 '_'
49 }
50 })
51 .collect()
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn workspace_key_sanitizes_identifier() {
61 let issue = Issue {
62 id: "id1".into(),
63 identifier: "ABC-123".into(),
64 title: "Test".into(),
65 description: None,
66 priority: Some(1),
67 state: "Todo".into(),
68 branch_name: None,
69 url: None,
70 labels: vec![],
71 blocked_by: vec![],
72 created_at: None,
73 updated_at: None,
74 };
75 assert_eq!(issue.workspace_key(), "ABC-123");
76 }
77
78 #[test]
79 fn workspace_key_replaces_special_chars() {
80 let issue = Issue {
81 id: "id2".into(),
82 identifier: "PROJ/feat#42".into(),
83 title: "Test".into(),
84 description: None,
85 priority: None,
86 state: "Todo".into(),
87 branch_name: None,
88 url: None,
89 labels: vec![],
90 blocked_by: vec![],
91 created_at: None,
92 updated_at: None,
93 };
94 assert_eq!(issue.workspace_key(), "PROJ_feat_42");
95 }
96}