cratestack_client_rust/
error.rs1use cratestack_core::{CratestackError, CratestackErrorResponse};
2
3use crate::envelope_error::EnvelopeError;
4use crate::remote_refusal::Challenges;
5use reqwest::StatusCode;
6
7pub type HeaderPair<'a> = (&'a str, &'a str);
8pub type QueryPair<'a> = (&'a str, &'a str);
9
10#[derive(Debug)]
13pub struct TransportError {
14 inner: Box<reqwest::Error>,
15}
16
17impl TransportError {
18 pub fn reqwest_error(&self) -> &reqwest::Error {
25 &self.inner
26 }
27
28 pub fn into_source(self) -> reqwest::Error {
30 *self.inner
31 }
32}
33
34impl std::fmt::Display for TransportError {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(f, "{}", self.inner)
37 }
38}
39
40impl std::error::Error for TransportError {
41 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
42 Some(self.inner.as_ref())
43 }
44}
45
46impl From<reqwest::Error> for TransportError {
47 fn from(e: reqwest::Error) -> Self {
48 TransportError { inner: Box::new(e) }
49 }
50}
51
52#[derive(Debug, thiserror::Error)]
53#[non_exhaustive]
54pub enum ClientError {
55 #[error("transport error: {0}")]
56 Transport(#[source] TransportError),
57 #[cfg(feature = "middleware")]
63 #[error("middleware error: {0}")]
64 Middleware(#[source] crate::middleware::MiddlewareError),
65 #[error("codec error: {0}")]
66 Codec(#[from] CratestackError),
67 #[error("state error: {0}")]
68 State(String),
69 #[error("invalid response: {0}")]
70 InvalidResponse(String),
71 #[error("bad input: {0}")]
72 BadInput(String),
73 #[error("signed transport: {0}")]
75 Envelope(#[from] EnvelopeError),
76 #[error("remote call failed with status {status}: {message}")]
77 Remote {
78 status: StatusCode,
79 error: Option<Box<CratestackErrorResponse>>,
83 message: String,
84 challenges: Challenges,
88 },
89}
90
91impl From<reqwest::Error> for ClientError {
92 fn from(e: reqwest::Error) -> Self {
93 ClientError::Transport(TransportError::from(e))
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 async fn synthesize_reqwest_error() -> reqwest::Error {
107 let _ = rustls::crypto::ring::default_provider().install_default();
111
112 reqwest::Client::new()
113 .get("http://127.0.0.1:1")
114 .send()
115 .await
116 .expect_err("connecting to a closed loopback port should fail")
117 }
118
119 #[tokio::test]
120 async fn transport_error_display_and_accessor_forward_to_reqwest() {
121 let reqwest_err = synthesize_reqwest_error().await;
122 let expected_message = reqwest_err.to_string();
123
124 let transport = TransportError::from(reqwest_err);
125
126 assert_eq!(transport.to_string(), expected_message);
127 assert_eq!(transport.reqwest_error().to_string(), expected_message);
128 }
129
130 #[tokio::test]
131 async fn transport_error_into_source_round_trips_the_reqwest_error() {
132 let reqwest_err = synthesize_reqwest_error().await;
133 let expected_message = reqwest_err.to_string();
134
135 let recovered = TransportError::from(reqwest_err).into_source();
136
137 assert_eq!(recovered.to_string(), expected_message);
138 }
139
140 #[tokio::test]
141 async fn client_error_transport_chains_via_std_error_source() {
142 let reqwest_err = synthesize_reqwest_error().await;
143 let expected_message = reqwest_err.to_string();
144
145 let client_err = ClientError::from(reqwest_err);
146
147 assert!(matches!(client_err, ClientError::Transport(_)));
149 assert!(client_err.to_string().contains(&expected_message));
151
152 let source = std::error::Error::source(&client_err)
158 .expect("ClientError::Transport should chain to the reqwest::Error via source()");
159 assert_eq!(source.to_string(), expected_message);
160 }
161
162 #[test]
163 fn client_error_variants() {
164 let transport_err = ClientError::State("state error".to_string());
165 assert!(matches!(transport_err, ClientError::State(_)));
166
167 let invalid_resp = ClientError::InvalidResponse("invalid".to_string());
168 assert!(matches!(invalid_resp, ClientError::InvalidResponse(_)));
169
170 let bad_input = ClientError::BadInput("bad".to_string());
171 assert!(matches!(bad_input, ClientError::BadInput(_)));
172 }
173
174 #[test]
175 fn client_error_displays_correctly() {
176 let err = ClientError::State("test error".to_string());
177 assert_eq!(err.to_string(), "state error: test error");
178
179 let err = ClientError::InvalidResponse("invalid".to_string());
180 assert_eq!(err.to_string(), "invalid response: invalid");
181 }
182}