1mod soap;
42pub use soap::{BugLog, BugReport};
43
44const DEFAULT_URL: &str = "https://bugs.debian.org/cgi-bin/soap.cgi";
45
46#[derive(Debug)]
48pub enum Error {
49 SoapError(String),
54
55 XmlError(String),
60
61 ReqwestError(reqwest::Error),
66
67 Fault(soap::Fault),
73}
74
75impl From<reqwest::Error> for Error {
76 fn from(err: reqwest::Error) -> Self {
77 Error::ReqwestError(err)
78 }
79}
80
81#[derive(Debug, PartialEq, Eq, Clone, Copy)]
83pub enum BugStatus {
84 Done,
86 Forwarded,
88 Open,
90}
91
92impl std::str::FromStr for BugStatus {
93 type Err = Error;
94
95 fn from_str(s: &str) -> Result<Self, Self::Err> {
96 match s {
97 "done" => Ok(BugStatus::Done),
98 "forwarded" => Ok(BugStatus::Forwarded),
99 "open" => Ok(BugStatus::Open),
100 _ => Err(Error::SoapError(format!("Unknown status: {}", s))),
101 }
102 }
103}
104
105impl std::fmt::Display for BugStatus {
106 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
107 match self {
108 BugStatus::Done => f.write_str("done"),
109 BugStatus::Forwarded => f.write_str("forwarded"),
110 BugStatus::Open => f.write_str("open"),
111 }
112 }
113}
114
115#[derive(Debug, PartialEq, Eq, Clone, Copy)]
117pub enum Pending {
118 Pending,
120 PendingFixed,
122 Fixed,
124 Done,
126 Forwarded,
128}
129
130impl std::str::FromStr for Pending {
131 type Err = Error;
132
133 fn from_str(s: &str) -> Result<Self, Self::Err> {
134 match s {
135 "pending" => Ok(Pending::Pending),
136 "pending-fixed" => Ok(Pending::PendingFixed),
137 "fixed" => Ok(Pending::Fixed),
138 "done" => Ok(Pending::Done),
139 "forwarded" => Ok(Pending::Forwarded),
140 _ => Err(Error::SoapError(format!("Unknown pending: {}", s))),
141 }
142 }
143}
144
145impl std::fmt::Display for Pending {
146 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
147 match self {
148 Pending::Pending => f.write_str("pending"),
149 Pending::PendingFixed => f.write_str("pending-fixed"),
150 Pending::Done => f.write_str("done"),
151 Pending::Forwarded => f.write_str("forwarded"),
152 Pending::Fixed => f.write_str("fixed"),
153 }
154 }
155}
156
157#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
159pub enum Archived {
160 Archived,
162 #[default]
164 NotArchived,
165 Both,
167}
168
169impl std::str::FromStr for Archived {
170 type Err = Error;
171
172 fn from_str(s: &str) -> Result<Self, Self::Err> {
173 match s {
174 "1" | "archived" => Ok(Archived::Archived),
175 "0" | "unarchived" => Ok(Archived::NotArchived),
176 "both" => Ok(Archived::Both),
177 _ => Err(Error::SoapError(format!("Unknown archived: {}", s))),
178 }
179 }
180}
181
182impl std::fmt::Display for Archived {
183 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
184 match self {
185 Archived::Archived => f.write_str("archived"),
186 Archived::NotArchived => f.write_str("unarchived"),
187 Archived::Both => f.write_str("both"),
188 }
189 }
190}
191
192impl std::fmt::Display for Error {
193 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
194 match &self {
195 Error::SoapError(err) => write!(f, "SOAP Error: {}", err),
196 Error::XmlError(err) => write!(f, "XML Error: {}", err),
197 Error::ReqwestError(err) => write!(f, "Reqwest Error: {}", err),
198 Error::Fault(err) => write!(f, "Fault: {}", err),
199 }
200 }
201}
202
203impl std::error::Error for Error {}
204
205pub type SoapResponse = Result<(reqwest::StatusCode, String), Error>;
206
207pub type BugId = i32;
209
210pub use soap::SearchQuery;
211
212#[cfg(feature = "blocking")]
213pub mod blocking;
214
215mod r#async;
216
217pub use r#async::Debbugs;
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use std::str::FromStr;
223
224 #[test]
225 fn test_bug_status_from_str() {
226 assert_eq!(BugStatus::from_str("done").unwrap(), BugStatus::Done);
227 assert_eq!(
228 BugStatus::from_str("forwarded").unwrap(),
229 BugStatus::Forwarded
230 );
231 assert_eq!(BugStatus::from_str("open").unwrap(), BugStatus::Open);
232 }
233
234 #[test]
235 fn test_bug_status_from_str_invalid() {
236 assert!(BugStatus::from_str("invalid").is_err());
237 assert!(BugStatus::from_str("").is_err());
238 assert!(BugStatus::from_str("DONE").is_err());
239 assert!(BugStatus::from_str("Done").is_err());
240 }
241
242 #[test]
243 fn test_bug_status_display() {
244 assert_eq!(BugStatus::Done.to_string(), "done");
245 assert_eq!(BugStatus::Forwarded.to_string(), "forwarded");
246 assert_eq!(BugStatus::Open.to_string(), "open");
247 }
248
249 #[test]
250 fn test_bug_status_roundtrip() {
251 let statuses = vec![BugStatus::Done, BugStatus::Forwarded, BugStatus::Open];
252 for status in statuses {
253 let s = status.to_string();
254 let parsed = BugStatus::from_str(&s).unwrap();
255 assert_eq!(status, parsed);
256 }
257 }
258
259 #[test]
260 fn test_pending_from_str() {
261 assert_eq!(Pending::from_str("pending").unwrap(), Pending::Pending);
262 assert_eq!(
263 Pending::from_str("pending-fixed").unwrap(),
264 Pending::PendingFixed
265 );
266 assert_eq!(Pending::from_str("fixed").unwrap(), Pending::Fixed);
267 assert_eq!(Pending::from_str("done").unwrap(), Pending::Done);
268 assert_eq!(Pending::from_str("forwarded").unwrap(), Pending::Forwarded);
269 }
270
271 #[test]
272 fn test_pending_from_str_invalid() {
273 assert!(Pending::from_str("invalid").is_err());
274 assert!(Pending::from_str("").is_err());
275 assert!(Pending::from_str("PENDING").is_err());
276 assert!(Pending::from_str("pending_fixed").is_err());
277 }
278
279 #[test]
280 fn test_pending_display() {
281 assert_eq!(Pending::Pending.to_string(), "pending");
282 assert_eq!(Pending::PendingFixed.to_string(), "pending-fixed");
283 assert_eq!(Pending::Fixed.to_string(), "fixed");
284 assert_eq!(Pending::Done.to_string(), "done");
285 assert_eq!(Pending::Forwarded.to_string(), "forwarded");
286 }
287
288 #[test]
289 fn test_pending_roundtrip() {
290 let pendings = vec![
291 Pending::Pending,
292 Pending::PendingFixed,
293 Pending::Fixed,
294 Pending::Done,
295 Pending::Forwarded,
296 ];
297 for pending in pendings {
298 let s = pending.to_string();
299 let parsed = Pending::from_str(&s).unwrap();
300 assert_eq!(pending, parsed);
301 }
302 }
303
304 #[test]
305 fn test_archived_from_str() {
306 assert_eq!(Archived::from_str("1").unwrap(), Archived::Archived);
307 assert_eq!(Archived::from_str("archived").unwrap(), Archived::Archived);
308 assert_eq!(Archived::from_str("0").unwrap(), Archived::NotArchived);
309 assert_eq!(
310 Archived::from_str("unarchived").unwrap(),
311 Archived::NotArchived
312 );
313 assert_eq!(Archived::from_str("both").unwrap(), Archived::Both);
314 }
315
316 #[test]
317 fn test_archived_from_str_invalid() {
318 assert!(Archived::from_str("invalid").is_err());
319 assert!(Archived::from_str("").is_err());
320 assert!(Archived::from_str("2").is_err());
321 assert!(Archived::from_str("ARCHIVED").is_err());
322 assert!(Archived::from_str("not-archived").is_err());
323 }
324
325 #[test]
326 fn test_archived_display() {
327 assert_eq!(Archived::Archived.to_string(), "archived");
328 assert_eq!(Archived::NotArchived.to_string(), "unarchived");
329 assert_eq!(Archived::Both.to_string(), "both");
330 }
331
332 #[test]
333 fn test_archived_roundtrip() {
334 let archiveds = vec![Archived::Archived, Archived::NotArchived, Archived::Both];
335 for archived in archiveds {
336 let s = archived.to_string();
337 let parsed = Archived::from_str(&s).unwrap();
339 assert_eq!(archived, parsed);
340 }
341 }
342
343 #[test]
344 fn test_archived_default() {
345 assert_eq!(Archived::default(), Archived::NotArchived);
346 }
347
348 #[test]
349 fn test_error_display() {
350 let soap_err = Error::SoapError("test error".to_string());
351 assert_eq!(soap_err.to_string(), "SOAP Error: test error");
352
353 let xml_err = Error::XmlError("xml parse failed".to_string());
354 assert_eq!(xml_err.to_string(), "XML Error: xml parse failed");
355
356 let fault = soap::Fault {
360 faultcode: "Client".to_string(),
361 faultstring: "Invalid request".to_string(),
362 faultactor: None,
363 detail: Some("Missing required parameter".to_string()),
364 };
365 let fault_err = Error::Fault(fault);
366 assert_eq!(fault_err.to_string(), "Fault: { faultcode: Client, faultstring: Invalid request, faultactor: None, detail: Some(\"Missing required parameter\") }");
367 }
368
369 #[test]
370 fn test_error_conversions() {
371 }
374}