1use crate::client::GitHubClient;
6use miyabi_types::error::{MiyabiError, Result};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Label {
12 pub name: String,
13 pub color: String,
14 pub description: Option<String>,
15}
16
17impl GitHubClient {
18 pub async fn list_labels(&self) -> Result<Vec<Label>> {
20 let page = self
21 .client
22 .issues(&self.owner, &self.repo)
23 .list_labels_for_repo()
24 .send()
25 .await
26 .map_err(|e| {
27 MiyabiError::GitHub(format!(
28 "Failed to list labels for {}/{}: {}",
29 self.owner, self.repo, e
30 ))
31 })?;
32
33 Ok(page
34 .items
35 .into_iter()
36 .map(|l| Label {
37 name: l.name,
38 color: l.color,
39 description: l.description,
40 })
41 .collect())
42 }
43
44 pub async fn get_label(&self, name: &str) -> Result<Label> {
46 let label = self
47 .client
48 .issues(&self.owner, &self.repo)
49 .get_label(name)
50 .await
51 .map_err(|e| {
52 MiyabiError::GitHub(format!(
53 "Failed to get label '{}' from {}/{}: {}",
54 name, self.owner, self.repo, e
55 ))
56 })?;
57
58 Ok(Label {
59 name: label.name,
60 color: label.color,
61 description: label.description,
62 })
63 }
64
65 pub async fn create_label(
72 &self,
73 name: &str,
74 color: &str,
75 description: Option<&str>,
76 ) -> Result<Label> {
77 let label = self
78 .client
79 .issues(&self.owner, &self.repo)
80 .create_label(name, color, description.unwrap_or(""))
81 .await
82 .map_err(|e| {
83 MiyabiError::GitHub(format!(
84 "Failed to create label '{}' in {}/{}: {}",
85 name, self.owner, self.repo, e
86 ))
87 })?;
88
89 Ok(Label {
90 name: label.name,
91 color: label.color,
92 description: label.description,
93 })
94 }
95
96 pub async fn update_label(
104 &self,
105 name: &str,
106 new_name: Option<&str>,
107 color: Option<&str>,
108 description: Option<&str>,
109 ) -> Result<Label> {
110 let current = self.get_label(name).await?;
113
114 let final_name = new_name.unwrap_or(¤t.name);
115 let final_color = color.unwrap_or(¤t.color);
116 let final_desc = description.or(current.description.as_deref());
117
118 if new_name.is_some() && new_name.unwrap() != name {
120 self.delete_label(name).await?;
121 }
122
123 self.create_label(final_name, final_color, final_desc).await
125 }
126
127 pub async fn delete_label(&self, name: &str) -> Result<()> {
129 self.client
130 .issues(&self.owner, &self.repo)
131 .delete_label(name)
132 .await
133 .map_err(|e| {
134 MiyabiError::GitHub(format!(
135 "Failed to delete label '{}' from {}/{}: {}",
136 name, self.owner, self.repo, e
137 ))
138 })
139 }
140
141 pub async fn bulk_create_labels(&self, labels: Vec<Label>) -> Result<Vec<Label>> {
149 let mut created = Vec::new();
150
151 for label in labels {
152 match self
153 .create_label(&label.name, &label.color, label.description.as_deref())
154 .await
155 {
156 Ok(l) => created.push(l),
157 Err(e) => {
158 eprintln!("Warning: Failed to create label '{}': {}", label.name, e);
159 }
161 }
162 }
163
164 Ok(created)
165 }
166
167 pub async fn label_exists(&self, name: &str) -> Result<bool> {
169 match self.get_label(name).await {
170 Ok(_) => Ok(true),
171 Err(MiyabiError::GitHub(ref msg)) if msg.contains("404") => Ok(false),
172 Err(e) => Err(e),
173 }
174 }
175
176 pub async fn sync_labels(&self, labels: Vec<Label>) -> Result<usize> {
185 let mut synced = 0;
186
187 for label in labels {
188 match self.label_exists(&label.name).await? {
189 true => {
190 self.update_label(
192 &label.name,
193 None,
194 Some(&label.color),
195 label.description.as_deref(),
196 )
197 .await?;
198 synced += 1;
199 }
200 false => {
201 self.create_label(&label.name, &label.color, label.description.as_deref())
203 .await?;
204 synced += 1;
205 }
206 }
207 }
208
209 Ok(synced)
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_label_struct() {
219 let label = Label {
220 name: "bug".to_string(),
221 color: "d73a4a".to_string(),
222 description: Some("Something isn't working".to_string()),
223 };
224
225 assert_eq!(label.name, "bug");
226 assert_eq!(label.color, "d73a4a");
227 assert!(label.description.is_some());
228 }
229
230 #[test]
231 fn test_label_serialization() {
232 let label = Label {
233 name: "enhancement".to_string(),
234 color: "a2eeef".to_string(),
235 description: Some("New feature or request".to_string()),
236 };
237
238 let json = serde_json::to_string(&label).unwrap();
239 let deserialized: Label = serde_json::from_str(&json).unwrap();
240
241 assert_eq!(label.name, deserialized.name);
242 assert_eq!(label.color, deserialized.color);
243 }
244
245 }