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 =
49 self.client.issues(&self.owner, &self.repo).get_label(name).await.map_err(|e| {
50 MiyabiError::GitHub(format!(
51 "Failed to get label '{}' from {}/{}: {}",
52 name, self.owner, self.repo, e
53 ))
54 })?;
55
56 Ok(Label {
57 name: label.name,
58 color: label.color,
59 description: label.description,
60 })
61 }
62
63 pub async fn create_label(
70 &self,
71 name: &str,
72 color: &str,
73 description: Option<&str>,
74 ) -> Result<Label> {
75 let label = self
76 .client
77 .issues(&self.owner, &self.repo)
78 .create_label(name, color, description.unwrap_or(""))
79 .await
80 .map_err(|e| {
81 MiyabiError::GitHub(format!(
82 "Failed to create label '{}' in {}/{}: {}",
83 name, self.owner, self.repo, e
84 ))
85 })?;
86
87 Ok(Label {
88 name: label.name,
89 color: label.color,
90 description: label.description,
91 })
92 }
93
94 pub async fn update_label(
102 &self,
103 name: &str,
104 new_name: Option<&str>,
105 color: Option<&str>,
106 description: Option<&str>,
107 ) -> Result<Label> {
108 let current = self.get_label(name).await?;
111
112 let final_name = new_name.unwrap_or(¤t.name);
113 let final_color = color.unwrap_or(¤t.color);
114 let final_desc = description.or(current.description.as_deref());
115
116 if new_name.is_some() && new_name.unwrap() != name {
118 self.delete_label(name).await?;
119 }
120
121 self.create_label(final_name, final_color, final_desc).await
123 }
124
125 pub async fn delete_label(&self, name: &str) -> Result<()> {
127 self.client
128 .issues(&self.owner, &self.repo)
129 .delete_label(name)
130 .await
131 .map_err(|e| {
132 MiyabiError::GitHub(format!(
133 "Failed to delete label '{}' from {}/{}: {}",
134 name, self.owner, self.repo, e
135 ))
136 })
137 }
138
139 pub async fn bulk_create_labels(&self, labels: Vec<Label>) -> Result<Vec<Label>> {
147 let mut created = Vec::new();
148
149 for label in labels {
150 match self.create_label(&label.name, &label.color, label.description.as_deref()).await {
151 Ok(l) => created.push(l),
152 Err(e) => {
153 eprintln!("Warning: Failed to create label '{}': {}", label.name, e);
154 },
156 }
157 }
158
159 Ok(created)
160 }
161
162 pub async fn label_exists(&self, name: &str) -> Result<bool> {
164 match self.get_label(name).await {
165 Ok(_) => Ok(true),
166 Err(MiyabiError::GitHub(ref msg)) if msg.contains("404") => Ok(false),
167 Err(e) => Err(e),
168 }
169 }
170
171 pub async fn sync_labels(&self, labels: Vec<Label>) -> Result<usize> {
180 let mut synced = 0;
181
182 for label in labels {
183 match self.label_exists(&label.name).await? {
184 true => {
185 self.update_label(
187 &label.name,
188 None,
189 Some(&label.color),
190 label.description.as_deref(),
191 )
192 .await?;
193 synced += 1;
194 },
195 false => {
196 self.create_label(&label.name, &label.color, label.description.as_deref())
198 .await?;
199 synced += 1;
200 },
201 }
202 }
203
204 Ok(synced)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_label_struct() {
214 let label = Label {
215 name: "bug".to_string(),
216 color: "d73a4a".to_string(),
217 description: Some("Something isn't working".to_string()),
218 };
219
220 assert_eq!(label.name, "bug");
221 assert_eq!(label.color, "d73a4a");
222 assert!(label.description.is_some());
223 }
224
225 #[test]
226 fn test_label_serialization() {
227 let label = Label {
228 name: "enhancement".to_string(),
229 color: "a2eeef".to_string(),
230 description: Some("New feature or request".to_string()),
231 };
232
233 let json = serde_json::to_string(&label).unwrap();
234 let deserialized: Label = serde_json::from_str(&json).unwrap();
235
236 assert_eq!(label.name, deserialized.name);
237 assert_eq!(label.color, deserialized.color);
238 }
239
240 }