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