@@ -5,7 +5,6 @@ use aws_config::BehaviorVersion;
5
5
use aws_sdk_s3:: {
6
6
config:: { retry:: RetryConfig , Region } ,
7
7
error:: { ProvideErrorMetadata , SdkError } ,
8
- operation:: { get_object:: GetObjectError , head_object:: HeadObjectError } ,
9
8
types:: { Delete , ObjectIdentifier , Tag , Tagging } ,
10
9
Client ,
11
10
} ;
@@ -21,16 +20,50 @@ use tracing::{error, warn};
21
20
const PUBLIC_ACCESS_TAG : & str = "static-cloudfront-access" ;
22
21
const PUBLIC_ACCESS_VALUE : & str = "allow" ;
23
22
24
- fn err_is_not_found < E > ( err : & SdkError < E > ) -> bool
23
+ // error codes to check for when trying to determaine if an error is
24
+ // a "NOT FOUND" error.
25
+ // Definition taken from the S3 rust SDK,
26
+ // and validated by manually testing with actual S3.
27
+ static NOT_FOUND_ERROR_CODES : [ & str ; 5 ] = [
28
+ // from sentry errors
29
+ "KeyTooLongError" ,
30
+ // https://github.com/awslabs/aws-sdk-rust/blob/6155192dbd003af7bc5d4c1419bf17794302f8c3/sdk/s3/src/protocol_serde/shape_get_object.rs#L201-L251
31
+ "NoSuchKey" ,
32
+ // https://github.com/awslabs/aws-sdk-rust/blob/6155192dbd003af7bc5d4c1419bf17794302f8c3/sdk/s3/src/protocol_serde/shape_head_object.rs#L1-L39"
33
+ "NotFound" ,
34
+ // https://github.com/awslabs/aws-sdk-rust/blob/6155192dbd003af7bc5d4c1419bf17794302f8c3/sdk/mediastoredata/src/protocol_serde/shape_get_object.rs#L47-L128
35
+ "ObjectNotFoundException" ,
36
+ // from testing long keys with minio
37
+ "XMinioInvalidObjectName" ,
38
+ ] ;
39
+
40
+ trait S3ResultExt < T > {
41
+ fn convert_errors ( self ) -> anyhow:: Result < T > ;
42
+ }
43
+
44
+ impl < T , E > S3ResultExt < T > for Result < T , SdkError < E > >
25
45
where
26
- E : ProvideErrorMetadata ,
46
+ E : ProvideErrorMetadata + std :: error :: Error + Send + Sync + ' static ,
27
47
{
28
- if err. code ( ) == Some ( "KeyTooLongError" ) || err. code ( ) == Some ( "XMinioInvalidObjectName" ) {
29
- true
30
- } else if let SdkError :: ServiceError ( err) = err {
31
- err. raw ( ) . status ( ) . as_u16 ( ) == http:: StatusCode :: NOT_FOUND . as_u16 ( )
32
- } else {
33
- false
48
+ fn convert_errors ( self ) -> anyhow:: Result < T > {
49
+ match self {
50
+ Ok ( result) => Ok ( result) ,
51
+ Err ( err) => {
52
+ if let Some ( err_code) = err. code ( ) {
53
+ if NOT_FOUND_ERROR_CODES . iter ( ) . any ( |& code| err_code == code) {
54
+ return Err ( super :: PathNotFoundError . into ( ) ) ;
55
+ }
56
+ }
57
+
58
+ if let SdkError :: ServiceError ( err) = & err {
59
+ if err. raw ( ) . status ( ) . as_u16 ( ) == http:: StatusCode :: NOT_FOUND . as_u16 ( ) {
60
+ return Err ( super :: PathNotFoundError . into ( ) ) ;
61
+ }
62
+ }
63
+
64
+ Err ( err. into ( ) )
65
+ }
66
+ }
34
67
}
35
68
}
36
69
@@ -88,40 +121,31 @@ impl S3Backend {
88
121
. key ( path)
89
122
. send ( )
90
123
. await
124
+ . convert_errors ( )
91
125
{
92
126
Ok ( _) => Ok ( true ) ,
93
- Err ( SdkError :: ServiceError ( err) )
94
- if matches ! ( err. err( ) , HeadObjectError :: NotFound ( _) ) =>
95
- {
96
- Ok ( false )
97
- }
98
- Err ( err) if err_is_not_found ( & err) => Ok ( false ) ,
99
- Err ( other) => Err ( other. into ( ) ) ,
127
+ Err ( err) if err. is :: < super :: PathNotFoundError > ( ) => Ok ( false ) ,
128
+ Err ( other) => Err ( other) ,
100
129
}
101
130
}
102
131
103
132
pub ( super ) async fn get_public_access ( & self , path : & str ) -> Result < bool , Error > {
104
- match self
133
+ Ok ( self
105
134
. client
106
135
. get_object_tagging ( )
107
136
. bucket ( & self . bucket )
108
137
. key ( path)
109
138
. send ( )
110
139
. await
111
- {
112
- Ok ( tags) => Ok ( tags
113
- . tag_set ( )
114
- . iter ( )
115
- . filter ( |tag| tag. key ( ) == PUBLIC_ACCESS_TAG )
116
- . any ( |tag| tag. value ( ) == PUBLIC_ACCESS_VALUE ) ) ,
117
- Err ( err) if err_is_not_found ( & err) => Err ( super :: PathNotFoundError . into ( ) ) ,
118
- Err ( other) => Err ( other. into ( ) ) ,
119
- }
140
+ . convert_errors ( ) ?
141
+ . tag_set ( )
142
+ . iter ( )
143
+ . filter ( |tag| tag. key ( ) == PUBLIC_ACCESS_TAG )
144
+ . any ( |tag| tag. value ( ) == PUBLIC_ACCESS_VALUE ) )
120
145
}
121
146
122
147
pub ( super ) async fn set_public_access ( & self , path : & str , public : bool ) -> Result < ( ) , Error > {
123
- match self
124
- . client
148
+ self . client
125
149
. put_object_tagging ( )
126
150
. bucket ( & self . bucket )
127
151
. key ( path)
@@ -144,11 +168,8 @@ impl S3Backend {
144
168
} )
145
169
. send ( )
146
170
. await
147
- {
148
- Ok ( _) => Ok ( ( ) ) ,
149
- Err ( err) if err_is_not_found ( & err) => Err ( super :: PathNotFoundError . into ( ) ) ,
150
- Err ( other) => Err ( other. into ( ) ) ,
151
- }
171
+ . convert_errors ( )
172
+ . map ( |_| ( ) )
152
173
}
153
174
154
175
pub ( super ) async fn get (
@@ -165,15 +186,7 @@ impl S3Backend {
165
186
. set_range ( range. map ( |r| format ! ( "bytes={}-{}" , r. start( ) , r. end( ) ) ) )
166
187
. send ( )
167
188
. await
168
- . map_err ( |err| match err {
169
- SdkError :: ServiceError ( err)
170
- if matches ! ( err. err( ) , GetObjectError :: NoSuchKey ( _) ) =>
171
- {
172
- super :: PathNotFoundError . into ( )
173
- }
174
- err if err_is_not_found ( & err) => super :: PathNotFoundError . into ( ) ,
175
- err => Error :: from ( err) ,
176
- } ) ?;
189
+ . convert_errors ( ) ?;
177
190
178
191
let mut content = crate :: utils:: sized_buffer:: SizedBuffer :: new ( max_size) ;
179
192
content. reserve (
0 commit comments