@@ -11,7 +11,9 @@ use axum::{
1111} ;
1212use ndarray:: ShapeError ;
1313use serde:: { Deserialize , Serialize } ;
14+ use std:: error:: Error ;
1415use 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 {
161171impl 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