1use std::fmt::{Display, Formatter};
2
3impl std::error::Error for Error {}
4
5impl Display for Error {
6 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
7 match self {
8 Error::Connection(msg) => write!(f, "Connection Error: {msg}"),
9 Error::Serialization(msg) => write!(f, "Serialization Error: {msg}"),
10 Error::AlreadyInitialized => write!(f, "Client already initialized"),
11 Error::NotInitialized => write!(f, "Client not initialized"),
12 Error::InvalidTimestamp(msg) => write!(f, "Invalid Timestamp: {msg}"),
13 Error::InconclusiveMatch(msg) => write!(f, "Inconclusive Match: {msg}"),
14 Error::RateLimit => write!(f, "Rate limited"),
15 Error::BadRequest(msg) => write!(f, "Bad Request: {msg}"),
16 Error::ServerError { status, message } => {
17 write!(f, "Server Error (HTTP {status}): {message}")
18 }
19 }
20 }
21}
22
23#[derive(Debug)]
25#[non_exhaustive]
26pub enum Error {
27 Connection(String),
29 Serialization(String),
31 AlreadyInitialized,
33 NotInitialized,
35 InvalidTimestamp(String),
37 InconclusiveMatch(String),
39 RateLimit,
41 BadRequest(String),
43 ServerError { status: u16, message: String },
45}
46
47impl Error {
48 pub(crate) fn from_http_response(status: u16, body: String) -> Option<Self> {
51 match status {
52 200..=299 => None,
53 429 => Some(Error::RateLimit),
54 400 | 413 => Some(Error::BadRequest(body)),
55 500..=599 => Some(Error::ServerError {
56 status,
57 message: body,
58 }),
59 _ => Some(Error::Connection(format!(
60 "Unexpected HTTP status {status}: {body}"
61 ))),
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn success_returns_none() {
72 assert!(Error::from_http_response(200, String::new()).is_none());
73 assert!(Error::from_http_response(201, String::new()).is_none());
74 assert!(Error::from_http_response(299, String::new()).is_none());
75 }
76
77 #[test]
78 fn rate_limit() {
79 let err = Error::from_http_response(429, String::new()).unwrap();
80 assert!(matches!(err, Error::RateLimit));
81 }
82
83 #[test]
84 fn bad_request_preserves_body() {
85 let err = Error::from_http_response(400, "invalid payload".to_string()).unwrap();
86 match err {
87 Error::BadRequest(msg) => assert_eq!(msg, "invalid payload"),
88 _ => panic!("expected BadRequest"),
89 }
90 }
91
92 #[test]
93 fn payload_too_large() {
94 let err = Error::from_http_response(413, "too large".to_string()).unwrap();
95 match err {
96 Error::BadRequest(msg) => assert_eq!(msg, "too large"),
97 _ => panic!("expected BadRequest for 413"),
98 }
99 }
100
101 #[test]
102 fn server_error_preserves_status_and_body() {
103 let err = Error::from_http_response(503, "unavailable".to_string()).unwrap();
104 match err {
105 Error::ServerError { status, message } => {
106 assert_eq!(status, 503);
107 assert_eq!(message, "unavailable");
108 }
109 _ => panic!("expected ServerError"),
110 }
111 }
112
113 #[test]
114 fn unexpected_status_becomes_connection_error() {
115 let err = Error::from_http_response(302, "redirect".to_string()).unwrap();
116 match err {
117 Error::Connection(msg) => {
118 assert!(msg.contains("302"));
119 assert!(msg.contains("redirect"));
120 }
121 _ => panic!("expected Connection"),
122 }
123 }
124}