Skip to content

Commit 8515a07

Browse files
authored
Merge pull request #27 from stackhpc/better-s3-errors
better s3 errors
2 parents b7130d3 + c4c295e commit 8515a07

File tree

1 file changed

+109
-13
lines changed

1 file changed

+109
-13
lines changed

src/error.rs

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use axum::{
1111
};
1212
use ndarray::ShapeError;
1313
use serde::{Deserialize, Serialize};
14+
use std::error::Error;
1415
use thiserror::Error;
16+
use tracing::{event, Level};
1517

1618
/// Active Storage server error type
1719
///
@@ -141,6 +143,14 @@ impl ErrorResponse {
141143
Self::new(StatusCode::BAD_REQUEST, error)
142144
}
143145

146+
/// Return a 401 unauthorised ErrorResponse
147+
fn unauthorised<E>(error: &E) -> Self
148+
where
149+
E: std::error::Error + Send + Sync,
150+
{
151+
Self::new(StatusCode::UNAUTHORIZED, error)
152+
}
153+
144154
/// Return a 404 not found ErrorResponse
145155
fn not_found<E>(error: &E) -> Self
146156
where
@@ -161,7 +171,7 @@ impl ErrorResponse {
161171
impl From<ActiveStorageError> for ErrorResponse {
162172
/// Convert from an `ActiveStorageError` into an `ErrorResponse`.
163173
fn from(error: ActiveStorageError) -> Self {
164-
match &error {
174+
let response = match &error {
165175
// Bad request
166176
ActiveStorageError::EmptyArray { operation: _ }
167177
| ActiveStorageError::RequestDataJsonRejection(_)
@@ -194,9 +204,22 @@ impl From<ActiveStorageError> for ErrorResponse {
194204
GetObjectErrorKind::InvalidObjectState(_)
195205
| GetObjectErrorKind::NoSuchKey(_) => Self::bad_request(&error),
196206

197-
// FIXME: Quite a lot of error cases end up here - invalid username,
198-
// password, etc.
199-
GetObjectErrorKind::Unhandled(_) => Self::internal_server_error(&error),
207+
// Quite a lot of error cases end up as unhandled. Attempt to determine
208+
// the error from the code.
209+
GetObjectErrorKind::Unhandled(_) => {
210+
match get_obj_error.code() {
211+
// Bad request
212+
Some("NoSuchBucket") => Self::bad_request(&error),
213+
214+
// Unauthorised
215+
Some("InvalidAccessKeyId") | Some("SignatureDoesNotMatch") => {
216+
Self::unauthorised(&error)
217+
}
218+
219+
// Internal server error
220+
_ => Self::internal_server_error(&error),
221+
}
222+
}
200223

201224
// The enum is marked as non-exhaustive
202225
_ => Self::internal_server_error(&error),
@@ -207,7 +230,19 @@ impl From<ActiveStorageError> for ErrorResponse {
207230
_ => Self::internal_server_error(&error),
208231
}
209232
}
233+
};
234+
235+
// Log server errors.
236+
if response.status.is_server_error() {
237+
event!(Level::ERROR, "{}", error.to_string());
238+
let mut current = error.source();
239+
while let Some(source) = current {
240+
event!(Level::ERROR, "Caused by: {}", source.to_string());
241+
current = source.source();
242+
}
210243
}
244+
245+
response
211246
}
212247
}
213248

@@ -301,22 +336,83 @@ mod tests {
301336
test_active_storage_error(error, StatusCode::BAD_REQUEST, message, caused_by).await;
302337
}
303338

339+
// Helper function for S3 GetObjectError errors
340+
async fn test_s3_get_object_error(
341+
sdk_error: SdkError<GetObjectError>,
342+
status: StatusCode,
343+
caused_by: Option<Vec<&'static str>>,
344+
) {
345+
let error = ActiveStorageError::S3GetObject(sdk_error);
346+
let message = "error retrieving object from S3 storage";
347+
test_active_storage_error(error, status, message, caused_by).await;
348+
}
349+
350+
fn get_smithy_response() -> SmithyResponse {
351+
let sdk_body = "body";
352+
let http_response = HttpResponse::new(sdk_body.into());
353+
SmithyResponse::new(http_response)
354+
}
355+
304356
#[tokio::test]
305357
async fn s3_get_object_error() {
306358
// Jump through hoops to create an SdkError.
307359
let smithy_error = SmithyError::builder().message("fake smithy error").build();
308360
let no_such_key_error = GetObjectErrorKind::NoSuchKey(NoSuchKey::builder().build());
309361
let get_object_error = GetObjectError::new(no_such_key_error, smithy_error);
310-
let sdk_body = "body";
311-
let http_response = HttpResponse::new(sdk_body.into());
312-
let smithy_response = SmithyResponse::new(http_response);
313-
let error = ActiveStorageError::S3GetObject(SdkError::service_error(
314-
get_object_error,
315-
smithy_response,
316-
));
317-
let message = "error retrieving object from S3 storage";
362+
let sdk_error = SdkError::service_error(get_object_error, get_smithy_response());
318363
let caused_by = Some(vec!["service error", "NoSuchKey"]);
319-
test_active_storage_error(error, StatusCode::BAD_REQUEST, message, caused_by).await;
364+
test_s3_get_object_error(sdk_error, StatusCode::BAD_REQUEST, caused_by).await;
365+
}
366+
367+
#[tokio::test]
368+
async fn s3_get_object_invalid_access_key_error() {
369+
// Jump through hoops to create an SdkError.
370+
let smithy_error = SmithyError::builder()
371+
.message("fake smithy error")
372+
.code("InvalidAccessKeyId")
373+
.build();
374+
let get_object_error = GetObjectError::generic(smithy_error);
375+
let sdk_error = SdkError::service_error(get_object_error, get_smithy_response());
376+
let caused_by = Some(vec![
377+
"service error",
378+
"unhandled error",
379+
"Error { code: \"InvalidAccessKeyId\", message: \"fake smithy error\" }",
380+
]);
381+
test_s3_get_object_error(sdk_error, StatusCode::UNAUTHORIZED, caused_by).await;
382+
}
383+
384+
#[tokio::test]
385+
async fn s3_get_object_no_such_bucket() {
386+
// Jump through hoops to create an SdkError.
387+
let smithy_error = SmithyError::builder()
388+
.message("fake smithy error")
389+
.code("NoSuchBucket")
390+
.build();
391+
let get_object_error = GetObjectError::generic(smithy_error);
392+
let sdk_error = SdkError::service_error(get_object_error, get_smithy_response());
393+
let caused_by = Some(vec![
394+
"service error",
395+
"unhandled error",
396+
"Error { code: \"NoSuchBucket\", message: \"fake smithy error\" }",
397+
]);
398+
test_s3_get_object_error(sdk_error, StatusCode::BAD_REQUEST, caused_by).await;
399+
}
400+
401+
#[tokio::test]
402+
async fn s3_get_object_sig_does_not_match_error() {
403+
// Jump through hoops to create an SdkError.
404+
let smithy_error = SmithyError::builder()
405+
.message("fake smithy error")
406+
.code("SignatureDoesNotMatch")
407+
.build();
408+
let get_object_error = GetObjectError::generic(smithy_error);
409+
let sdk_error = SdkError::service_error(get_object_error, get_smithy_response());
410+
let caused_by = Some(vec![
411+
"service error",
412+
"unhandled error",
413+
"Error { code: \"SignatureDoesNotMatch\", message: \"fake smithy error\" }",
414+
]);
415+
test_s3_get_object_error(sdk_error, StatusCode::UNAUTHORIZED, caused_by).await;
320416
}
321417

322418
#[tokio::test]

0 commit comments

Comments
 (0)