Skip to main content

bes_canopy_api/
error.rs

1//! Errors this client produces.
2
3use bytes::Bytes;
4
5/// Result alias used throughout the crate.
6pub type Result<T, E = Error> = std::result::Result<T, E>;
7
8/// Anything that can go wrong calling canopy.
9#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum Error {
12	/// Canopy answered with a status outside the success range.
13	#[error(transparent)]
14	Http(#[from] CanopyHttpError),
15
16	/// The response body was not the JSON the endpoint declares.
17	#[error("decoding the response of {path}: {source}")]
18	Decode {
19		/// Path that was called.
20		path: String,
21		/// Underlying serde error.
22		source: serde_json::Error,
23	},
24
25	/// The request body could not be serialised.
26	#[error("encoding the request body for {path}: {source}")]
27	Encode {
28		/// Path that was called.
29		path: String,
30		/// Underlying serde error.
31		source: serde_json::Error,
32	},
33
34	/// Building the HTTP request failed.
35	#[error("building the request for {path}: {source}")]
36	Request {
37		/// Path that was called.
38		path: String,
39		/// Underlying `http` error.
40		source: http::Error,
41	},
42
43	/// Compressing the request body failed.
44	#[error("compressing the request body for {path}: {source}")]
45	Compress {
46		/// Path that was called.
47		path: String,
48		/// Underlying IO error.
49		source: std::io::Error,
50	},
51
52	/// The transport could not obtain any response.
53	///
54	/// Distinct from [`Error::Http`], which is a response that reports failure.
55	#[error("reaching canopy: {0}")]
56	Transport(#[source] Box<dyn std::error::Error + Send + Sync>),
57}
58
59impl Error {
60	/// Wrap a transport-side failure to obtain a response.
61	pub fn transport<E: std::error::Error + Send + Sync + 'static>(err: E) -> Self {
62		Self::Transport(Box::new(err))
63	}
64
65	/// The [`CanopyHttpError`] this error carries, if it is one.
66	///
67	/// Endpoints give particular statuses meaning, so a caller branching on a
68	/// documented status reads it through here.
69	pub fn http(&self) -> Option<&CanopyHttpError> {
70		match self {
71			Self::Http(err) => Some(err),
72			_ => None,
73		}
74	}
75
76	/// The status canopy answered with, if this is an unsuccessful response.
77	pub fn status(&self) -> Option<http::StatusCode> {
78		self.http().map(|err| err.status)
79	}
80}
81
82/// A non-2xx response from a canopy endpoint.
83///
84/// Endpoints give meaning to specific codes, so this carries the status and the
85/// body rather than flattening them into a message.
86#[derive(Debug, thiserror::Error)]
87#[error("canopy returned {status} for {path}")]
88pub struct CanopyHttpError {
89	/// HTTP status returned by canopy.
90	pub status: http::StatusCode,
91	/// The endpoint path that was called.
92	pub path: String,
93	/// Response body, as returned.
94	pub body: Bytes,
95}
96
97impl CanopyHttpError {
98	/// The response body as UTF-8 text, lossily.
99	pub fn body_text(&self) -> std::borrow::Cow<'_, str> {
100		String::from_utf8_lossy(&self.body)
101	}
102}