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}