sms_client/http/
error.rs

1//! HTTP interface related errors.
2
3/// An error originating from the SMS `HttpClient`.
4#[derive(thiserror::Error, Debug)]
5pub enum HttpError {
6    /// Network request failed (connection issues, timeouts, etc.)
7    #[error("Reqwest failure: {0}")]
8    RequestError(#[from] reqwest::Error),
9
10    /// Failed to parse the provided URL.
11    #[error("Invalid URL: {0}")]
12    UrlParseError(#[from] url::ParseError),
13
14    /// Failed to parse JSON response from the API.
15    #[error("JSON parsing failed: {0}")]
16    JsonError(#[from] serde_json::Error),
17
18    /// System IO error
19    #[error("IO error: {0}")]
20    IOError(#[from] std::io::Error),
21
22    /// HTTP request returned a non-success status code.
23    #[error("{}", HttpError::format_http_error(status, message))]
24    HttpStatus {
25        /// HTTP status returned in response.
26        status: u16,
27        /// Full response body as text.
28        message: String,
29    },
30
31    /// API returned success=false with an error message.
32    #[error("API responded with success=false: {message}")]
33    ApiError {
34        /// The `error_message` key from response.
35        message: String,
36    },
37
38    /// TLS configuration error
39    #[error("TLS error: {0}")]
40    TLSError(String),
41
42    /// API response missing the expected 'response' field.
43    #[error("Missing 'response' field in API response")]
44    MissingResponseField,
45
46    /// Modem response missing the expected 'type' field.
47    #[error("Missing 'type' field in API response")]
48    MissingTypeField,
49
50    /// Modem response missing the expected 'data' field.
51    #[error("Missing 'data' field in API response")]
52    MissingDataField,
53
54    /// Modem response type doesn't match what was expected.
55    #[error("Type mismatch: expected '{expected}', got '{actual}'")]
56    ResponseTypeMismatch {
57        /// The expected response data-type.
58        expected: String,
59        /// The actual response data-type.
60        actual: String,
61    },
62}
63impl HttpError {
64    fn status_text(status: u16) -> &'static str {
65        match status {
66            200 => "OK",
67            400 => "Bad Request",
68            401 => "Unauthorized",
69            403 => "Forbidden",
70            404 => "Not Found",
71            405 => "Method Not Allowed",
72            408 => "Not Acceptable",
73            429 => "Too Many Requests",
74            500 => "Internal Server Error",
75            503 => "Service Unavailable",
76            504 => "Gateway Timeout",
77            _ => "Unknown",
78        }
79    }
80
81    // Ignore Clippy hint since thiserror always provides it by reference.
82    #[allow(clippy::trivially_copy_pass_by_ref)]
83    fn format_http_error(status: &u16, message: &str) -> String {
84        if message.trim().is_empty() {
85            format!("HTTP {status} {}", Self::status_text(*status))
86        } else {
87            format!("HTTP {status}: {message}")
88        }
89    }
90}
91
92/// Result type alias for HTTP operations.
93pub type HttpResult<T> = Result<T, HttpError>;