garage_api_common/
common_error.rs

1use std::convert::TryFrom;
2
3use err_derive::Error;
4use hyper::StatusCode;
5
6use garage_util::error::Error as GarageError;
7
8use garage_model::helper::error::Error as HelperError;
9
10/// Errors of this crate
11#[derive(Debug, Error)]
12pub enum CommonError {
13	// ---- INTERNAL ERRORS ----
14	/// Error related to deeper parts of Garage
15	#[error(display = "Internal error: {}", _0)]
16	InternalError(#[error(source)] GarageError),
17
18	/// Error related to Hyper
19	#[error(display = "Internal error (Hyper error): {}", _0)]
20	Hyper(#[error(source)] hyper::Error),
21
22	/// Error related to HTTP
23	#[error(display = "Internal error (HTTP error): {}", _0)]
24	Http(#[error(source)] http::Error),
25
26	// ---- GENERIC CLIENT ERRORS ----
27	/// Proper authentication was not provided
28	#[error(display = "Forbidden: {}", _0)]
29	Forbidden(String),
30
31	/// Generic bad request response with custom message
32	#[error(display = "Bad request: {}", _0)]
33	BadRequest(String),
34
35	/// The client sent a header with invalid value
36	#[error(display = "Invalid header value: {}", _0)]
37	InvalidHeader(#[error(source)] hyper::header::ToStrError),
38
39	// ---- SPECIFIC ERROR CONDITIONS ----
40	// These have to be error codes referenced in the S3 spec here:
41	// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList
42	/// The bucket requested don't exists
43	#[error(display = "Bucket not found: {}", _0)]
44	NoSuchBucket(String),
45
46	/// Tried to create a bucket that already exist
47	#[error(display = "Bucket already exists")]
48	BucketAlreadyExists,
49
50	/// Tried to delete a non-empty bucket
51	#[error(display = "Tried to delete a non-empty bucket")]
52	BucketNotEmpty,
53
54	// Category: bad request
55	/// Bucket name is not valid according to AWS S3 specs
56	#[error(display = "Invalid bucket name: {}", _0)]
57	InvalidBucketName(String),
58}
59
60#[macro_export]
61macro_rules! commonErrorDerivative {
62	( $error_struct: ident ) => {
63		impl From<garage_util::error::Error> for $error_struct {
64			fn from(err: garage_util::error::Error) -> Self {
65				Self::Common(CommonError::InternalError(err))
66			}
67		}
68		impl From<http::Error> for $error_struct {
69			fn from(err: http::Error) -> Self {
70				Self::Common(CommonError::Http(err))
71			}
72		}
73		impl From<hyper::Error> for $error_struct {
74			fn from(err: hyper::Error) -> Self {
75				Self::Common(CommonError::Hyper(err))
76			}
77		}
78		impl From<hyper::header::ToStrError> for $error_struct {
79			fn from(err: hyper::header::ToStrError) -> Self {
80				Self::Common(CommonError::InvalidHeader(err))
81			}
82		}
83		impl CommonErrorDerivative for $error_struct {}
84	};
85}
86
87pub use commonErrorDerivative;
88
89impl CommonError {
90	pub fn http_status_code(&self) -> StatusCode {
91		match self {
92			CommonError::InternalError(
93				GarageError::Timeout | GarageError::RemoteError(_) | GarageError::Quorum(..),
94			) => StatusCode::SERVICE_UNAVAILABLE,
95			CommonError::InternalError(_) | CommonError::Hyper(_) | CommonError::Http(_) => {
96				StatusCode::INTERNAL_SERVER_ERROR
97			}
98			CommonError::BadRequest(_) => StatusCode::BAD_REQUEST,
99			CommonError::Forbidden(_) => StatusCode::FORBIDDEN,
100			CommonError::NoSuchBucket(_) => StatusCode::NOT_FOUND,
101			CommonError::BucketNotEmpty | CommonError::BucketAlreadyExists => StatusCode::CONFLICT,
102			CommonError::InvalidBucketName(_) | CommonError::InvalidHeader(_) => {
103				StatusCode::BAD_REQUEST
104			}
105		}
106	}
107
108	pub fn aws_code(&self) -> &'static str {
109		match self {
110			CommonError::Forbidden(_) => "AccessDenied",
111			CommonError::InternalError(
112				GarageError::Timeout | GarageError::RemoteError(_) | GarageError::Quorum(..),
113			) => "ServiceUnavailable",
114			CommonError::InternalError(_) | CommonError::Hyper(_) | CommonError::Http(_) => {
115				"InternalError"
116			}
117			CommonError::BadRequest(_) => "InvalidRequest",
118			CommonError::NoSuchBucket(_) => "NoSuchBucket",
119			CommonError::BucketAlreadyExists => "BucketAlreadyExists",
120			CommonError::BucketNotEmpty => "BucketNotEmpty",
121			CommonError::InvalidBucketName(_) => "InvalidBucketName",
122			CommonError::InvalidHeader(_) => "InvalidHeaderValue",
123		}
124	}
125
126	pub fn bad_request<M: ToString>(msg: M) -> Self {
127		CommonError::BadRequest(msg.to_string())
128	}
129}
130
131impl TryFrom<HelperError> for CommonError {
132	type Error = HelperError;
133
134	fn try_from(err: HelperError) -> Result<Self, HelperError> {
135		match err {
136			HelperError::Internal(i) => Ok(Self::InternalError(i)),
137			HelperError::BadRequest(b) => Ok(Self::BadRequest(b)),
138			HelperError::InvalidBucketName(n) => Ok(Self::InvalidBucketName(n)),
139			HelperError::NoSuchBucket(n) => Ok(Self::NoSuchBucket(n)),
140			e => Err(e),
141		}
142	}
143}
144
145/// This function converts HelperErrors into CommonErrors,
146/// for variants that exist in CommonError.
147/// This is used for helper functions that might return InvalidBucketName
148/// or NoSuchBucket for instance, and we want to pass that error
149/// up to our caller.
150pub fn pass_helper_error(err: HelperError) -> CommonError {
151	match CommonError::try_from(err) {
152		Ok(e) => e,
153		Err(e) => panic!("Helper error `{}` should hot have happenned here", e),
154	}
155}
156
157pub fn helper_error_as_internal(err: HelperError) -> CommonError {
158	match err {
159		HelperError::Internal(e) => CommonError::InternalError(e),
160		e => CommonError::InternalError(GarageError::Message(e.to_string())),
161	}
162}
163
164pub trait CommonErrorDerivative: From<CommonError> {
165	fn internal_error<M: ToString>(msg: M) -> Self {
166		Self::from(CommonError::InternalError(GarageError::Message(
167			msg.to_string(),
168		)))
169	}
170
171	fn bad_request<M: ToString>(msg: M) -> Self {
172		Self::from(CommonError::BadRequest(msg.to_string()))
173	}
174
175	fn forbidden<M: ToString>(msg: M) -> Self {
176		Self::from(CommonError::Forbidden(msg.to_string()))
177	}
178}
179
180/// Trait to map error to the Bad Request error code
181pub trait OkOrBadRequest {
182	type S;
183	fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<Self::S, CommonError>;
184}
185
186impl<T, E> OkOrBadRequest for Result<T, E>
187where
188	E: std::fmt::Display,
189{
190	type S = T;
191	fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> {
192		match self {
193			Ok(x) => Ok(x),
194			Err(e) => Err(CommonError::BadRequest(format!(
195				"{}: {}",
196				reason.as_ref(),
197				e
198			))),
199		}
200	}
201}
202
203impl<T> OkOrBadRequest for Option<T> {
204	type S = T;
205	fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> {
206		match self {
207			Some(x) => Ok(x),
208			None => Err(CommonError::BadRequest(reason.as_ref().to_string())),
209		}
210	}
211}
212
213/// Trait to map an error to an Internal Error code
214pub trait OkOrInternalError {
215	type S;
216	fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<Self::S, CommonError>;
217}
218
219impl<T, E> OkOrInternalError for Result<T, E>
220where
221	E: std::fmt::Display,
222{
223	type S = T;
224	fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> {
225		match self {
226			Ok(x) => Ok(x),
227			Err(e) => Err(CommonError::InternalError(GarageError::Message(format!(
228				"{}: {}",
229				reason.as_ref(),
230				e
231			)))),
232		}
233	}
234}
235
236impl<T> OkOrInternalError for Option<T> {
237	type S = T;
238	fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> {
239		match self {
240			Some(x) => Ok(x),
241			None => Err(CommonError::InternalError(GarageError::Message(
242				reason.as_ref().to_string(),
243			))),
244		}
245	}
246}