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