Skip to main content

uni_sdk/
error.rs

1use reqwest::StatusCode;
2
3/// Result type returned by the SDK.
4pub type Result<T> = std::result::Result<T, UniError>;
5
6/// Errors produced while configuring the client or calling the API.
7#[derive(Debug, thiserror::Error)]
8pub enum UniError {
9    /// An API response contained a non-zero business error code.
10    #[error("[{code}] {message}{request_suffix}", request_suffix = format_request_id(.request_id.as_deref()))]
11    Api {
12        /// Business error code returned by the API.
13        code: String,
14        /// Human-readable error message returned by the API.
15        message: String,
16        /// HTTP status of the response.
17        status: StatusCode,
18        /// Unimatrix request ID used for support and diagnostics.
19        request_id: Option<String>,
20        /// Parsed response body, when one was available.
21        body: Option<serde_json::Value>,
22    },
23
24    /// The server returned a non-successful HTTP status without an API error.
25    #[error("HTTP {status}: {message}{request_suffix}", request_suffix = format_request_id(.request_id.as_deref()))]
26    Http {
27        /// Non-successful HTTP status.
28        status: StatusCode,
29        /// HTTP or response message describing the failure.
30        message: String,
31        /// Unimatrix request ID used for support and diagnostics.
32        request_id: Option<String>,
33        /// Parsed response body, or the raw body represented as a JSON string.
34        body: Option<serde_json::Value>,
35    },
36
37    /// A transport-level error occurred.
38    #[error("request failed: {0}")]
39    Transport(#[source] reqwest::Error),
40
41    /// The endpoint URL or client configuration was invalid.
42    #[error("invalid configuration: {0}")]
43    Configuration(String),
44
45    /// A response could not be decoded according to the API schema.
46    #[error("invalid API response (HTTP {status}){request_suffix}: {message}", request_suffix = format_request_id(.request_id.as_deref()))]
47    InvalidResponse {
48        /// HTTP status of the response.
49        status: StatusCode,
50        /// Unimatrix request ID used for support and diagnostics.
51        request_id: Option<String>,
52        /// Description of the response decoding failure.
53        message: String,
54        /// Raw response body.
55        body: String,
56    },
57}
58
59impl From<reqwest::Error> for UniError {
60    fn from(error: reqwest::Error) -> Self {
61        // Signed request URLs contain the access key ID, timestamp, nonce, and
62        // signature. Never retain those values in an error that callers may log.
63        Self::Transport(error.without_url())
64    }
65}
66
67impl UniError {
68    /// API error code, when the server supplied one.
69    pub fn code(&self) -> Option<&str> {
70        match self {
71            Self::Api { code, .. } => Some(code),
72            _ => None,
73        }
74    }
75
76    /// HTTP status associated with the error, when available.
77    pub fn status(&self) -> Option<StatusCode> {
78        match self {
79            Self::Api { status, .. }
80            | Self::Http { status, .. }
81            | Self::InvalidResponse { status, .. } => Some(*status),
82            _ => None,
83        }
84    }
85
86    /// Request ID that can be provided to Unimatrix support.
87    pub fn request_id(&self) -> Option<&str> {
88        match self {
89            Self::Api { request_id, .. }
90            | Self::Http { request_id, .. }
91            | Self::InvalidResponse { request_id, .. } => request_id.as_deref(),
92            _ => None,
93        }
94    }
95}
96
97fn format_request_id(request_id: Option<&str>) -> String {
98    request_id
99        .map(|id| format!(", RequestId: {id}"))
100        .unwrap_or_default()
101}