aboutsummaryrefslogtreecommitdiff
path: root/src/api/common_error.rs
blob: c47555d405dc5970637115be9ea9c30bdd86037f (plain) (blame)
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
use err_derive::Error;
use hyper::StatusCode;

use garage_util::error::Error as GarageError;

use garage_model::helper::error::Error as HelperError;

/// Errors of this crate
#[derive(Debug, Error)]
pub enum CommonError {
	// ---- INTERNAL ERRORS ----
	/// Error related to deeper parts of Garage
	#[error(display = "Internal error: {}", _0)]
	InternalError(#[error(source)] GarageError),

	/// Error related to Hyper
	#[error(display = "Internal error (Hyper error): {}", _0)]
	Hyper(#[error(source)] hyper::Error),

	/// Error related to HTTP
	#[error(display = "Internal error (HTTP error): {}", _0)]
	Http(#[error(source)] http::Error),

	// ---- GENERIC CLIENT ERRORS ----
	/// Proper authentication was not provided
	#[error(display = "Forbidden: {}", _0)]
	Forbidden(String),

	/// Generic bad request response with custom message
	#[error(display = "Bad request: {}", _0)]
	BadRequest(String),

	/// The client sent a header with invalid value
	#[error(display = "Invalid header value: {}", _0)]
	InvalidHeader(#[error(source)] hyper::header::ToStrError),

	// ---- SPECIFIC ERROR CONDITIONS ----
	// These have to be error codes referenced in the S3 spec here:
	// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList
	/// The bucket requested don't exists
	#[error(display = "Bucket not found: {}", _0)]
	NoSuchBucket(String),

	/// Tried to create a bucket that already exist
	#[error(display = "Bucket already exists")]
	BucketAlreadyExists,

	/// Tried to delete a non-empty bucket
	#[error(display = "Tried to delete a non-empty bucket")]
	BucketNotEmpty,

	// Category: bad request
	/// Bucket name is not valid according to AWS S3 specs
	#[error(display = "Invalid bucket name: {}", _0)]
	InvalidBucketName(String),
}

impl CommonError {
	pub fn http_status_code(&self) -> StatusCode {
		match self {
			CommonError::InternalError(
				GarageError::Timeout | GarageError::RemoteError(_) | GarageError::Quorum(..),
			) => StatusCode::SERVICE_UNAVAILABLE,
			CommonError::InternalError(_) | CommonError::Hyper(_) | CommonError::Http(_) => {
				StatusCode::INTERNAL_SERVER_ERROR
			}
			CommonError::BadRequest(_) => StatusCode::BAD_REQUEST,
			CommonError::Forbidden(_) => StatusCode::FORBIDDEN,
			CommonError::NoSuchBucket(_) => StatusCode::NOT_FOUND,
			CommonError::BucketNotEmpty | CommonError::BucketAlreadyExists => StatusCode::CONFLICT,
			CommonError::InvalidBucketName(_) | CommonError::InvalidHeader(_) => {
				StatusCode::BAD_REQUEST
			}
		}
	}

	pub fn aws_code(&self) -> &'static str {
		match self {
			CommonError::Forbidden(_) => "AccessDenied",
			CommonError::InternalError(
				GarageError::Timeout | GarageError::RemoteError(_) | GarageError::Quorum(..),
			) => "ServiceUnavailable",
			CommonError::InternalError(_) | CommonError::Hyper(_) | CommonError::Http(_) => {
				"InternalError"
			}
			CommonError::BadRequest(_) => "InvalidRequest",
			CommonError::NoSuchBucket(_) => "NoSuchBucket",
			CommonError::BucketAlreadyExists => "BucketAlreadyExists",
			CommonError::BucketNotEmpty => "BucketNotEmpty",
			CommonError::InvalidBucketName(_) => "InvalidBucketName",
			CommonError::InvalidHeader(_) => "InvalidHeaderValue",
		}
	}

	pub fn bad_request<M: ToString>(msg: M) -> Self {
		CommonError::BadRequest(msg.to_string())
	}
}

impl From<HelperError> for CommonError {
	fn from(err: HelperError) -> Self {
		match err {
			HelperError::Internal(i) => Self::InternalError(i),
			HelperError::BadRequest(b) => Self::BadRequest(b),
			HelperError::InvalidBucketName(n) => Self::InvalidBucketName(n),
			HelperError::NoSuchBucket(n) => Self::NoSuchBucket(n),
			e => Self::bad_request(format!("{}", e)),
		}
	}
}

pub trait CommonErrorDerivative: From<CommonError> {
	fn internal_error<M: ToString>(msg: M) -> Self {
		Self::from(CommonError::InternalError(GarageError::Message(
			msg.to_string(),
		)))
	}

	fn bad_request<M: ToString>(msg: M) -> Self {
		Self::from(CommonError::BadRequest(msg.to_string()))
	}

	fn forbidden<M: ToString>(msg: M) -> Self {
		Self::from(CommonError::Forbidden(msg.to_string()))
	}
}

/// Trait to map error to the Bad Request error code
pub trait OkOrBadRequest {
	type S;
	fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<Self::S, CommonError>;
}

impl<T, E> OkOrBadRequest for Result<T, E>
where
	E: std::fmt::Display,
{
	type S = T;
	fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> {
		match self {
			Ok(x) => Ok(x),
			Err(e) => Err(CommonError::BadRequest(format!(
				"{}: {}",
				reason.as_ref(),
				e
			))),
		}
	}
}

impl<T> OkOrBadRequest for Option<T> {
	type S = T;
	fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> {
		match self {
			Some(x) => Ok(x),
			None => Err(CommonError::BadRequest(reason.as_ref().to_string())),
		}
	}
}

/// Trait to map an error to an Internal Error code
pub trait OkOrInternalError {
	type S;
	fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<Self::S, CommonError>;
}

impl<T, E> OkOrInternalError for Result<T, E>
where
	E: std::fmt::Display,
{
	type S = T;
	fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> {
		match self {
			Ok(x) => Ok(x),
			Err(e) => Err(CommonError::InternalError(GarageError::Message(format!(
				"{}: {}",
				reason.as_ref(),
				e
			)))),
		}
	}
}

impl<T> OkOrInternalError for Option<T> {
	type S = T;
	fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> {
		match self {
			Some(x) => Ok(x),
			None => Err(CommonError::InternalError(GarageError::Message(
				reason.as_ref().to_string(),
			))),
		}
	}
}