atomcode_core/atomgit/
models.rs1use serde::{Deserialize, Deserializer};
5
6pub(crate) fn deserialize_u64_from_string_or_int<'de, D>(deserializer: D) -> Result<u64, D::Error>
9where
10 D: Deserializer<'de>,
11{
12 #[derive(Deserialize)]
13 #[serde(untagged)]
14 enum StringOrInt {
15 Int(u64),
16 Str(String),
17 }
18 match StringOrInt::deserialize(deserializer)? {
19 StringOrInt::Int(n) => Ok(n),
20 StringOrInt::Str(s) => s
21 .parse::<u64>()
22 .map_err(|_| serde::de::Error::custom(format!("not a u64: {:?}", s))),
23 }
24}
25
26#[derive(Debug, Deserialize, Clone)]
30pub struct RepoLabel {
31 #[serde(deserialize_with = "deserialize_u64_from_string_or_int")]
32 pub id: u64,
33 pub name: String,
34}
35
36#[derive(Debug, Deserialize, Clone)]
37pub struct User {
38 pub login: String,
39}
40
41#[derive(Debug, Deserialize, Clone)]
42pub struct Label {
43 pub name: String,
44}
45
46#[derive(Debug, Deserialize)]
47pub struct Issue {
48 #[serde(deserialize_with = "deserialize_u64_from_string_or_int")]
49 pub number: u64,
50 pub title: String,
51 #[serde(default)]
52 pub body: Option<String>,
53 pub state: String,
54 #[serde(default)]
55 pub html_url: Option<String>,
56 #[serde(default)]
57 pub user: Option<User>,
58 #[serde(default)]
60 pub assignee: Option<User>,
61 #[serde(default)]
64 pub assignees: Vec<User>,
65 #[serde(default)]
66 pub labels: Vec<Label>,
67}
68
69impl Issue {
70 pub fn is_assigned_to(&self, username: &str) -> bool {
72 if let Some(a) = &self.assignee {
73 if a.login == username {
74 return true;
75 }
76 }
77 self.assignees.iter().any(|a| a.login == username)
78 }
79
80 pub fn assignee_list(&self) -> String {
82 let mut names: Vec<&str> = Vec::new();
83 if let Some(a) = &self.assignee {
84 names.push(&a.login);
85 }
86 for a in &self.assignees {
87 if !names.iter().any(|n| *n == a.login) {
88 names.push(&a.login);
89 }
90 }
91 if names.is_empty() {
92 "(unassigned)".to_string()
93 } else {
94 names.join(", ")
95 }
96 }
97}
98
99#[derive(Debug, Deserialize)]
100pub struct Comment {
101 #[serde(default)]
102 pub user: Option<User>,
103 #[serde(default)]
104 pub body: Option<String>,
105}
106
107#[derive(Debug, Deserialize)]
111pub struct CreatedIssue {
112 #[serde(deserialize_with = "deserialize_u64_from_string_or_int")]
113 pub number: u64,
114 #[serde(default)]
115 pub title: String,
116 #[serde(default)]
117 pub html_url: Option<String>,
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn parses_number_as_string() {
126 let raw = r#"{"number":"140","title":"t","state":"open"}"#;
128 let issue: Issue = serde_json::from_str(raw).unwrap();
129 assert_eq!(issue.number, 140);
130 }
131
132 #[test]
133 fn parses_number_as_int() {
134 let raw = r#"{"number":42,"title":"t","state":"open"}"#;
136 let issue: Issue = serde_json::from_str(raw).unwrap();
137 assert_eq!(issue.number, 42);
138 }
139
140 #[test]
141 fn is_assigned_to_checks_both_fields() {
142 let raw = r#"{"number":1,"title":"t","state":"open","assignee":{"login":"alice"},"assignees":[{"login":"bob"}]}"#;
143 let issue: Issue = serde_json::from_str(raw).unwrap();
144 assert!(issue.is_assigned_to("alice"));
145 assert!(issue.is_assigned_to("bob"));
146 assert!(!issue.is_assigned_to("carol"));
147 }
148}