4
4
*/
5
5
6
6
use aws_smithy_http:: http:: HttpHeaders ;
7
- use aws_smithy_http:: result:: SdkError ;
8
- use aws_smithy_runtime_api:: client:: retries:: RetryReason ;
7
+ use aws_smithy_runtime_api:: client:: interceptors:: InterceptorContext ;
8
+ use aws_smithy_runtime_api:: client:: orchestrator:: OrchestratorError ;
9
+ use aws_smithy_runtime_api:: client:: retries:: { ClassifyRetry , RetryReason } ;
9
10
use aws_smithy_types:: error:: metadata:: ProvideErrorMetadata ;
10
11
use aws_smithy_types:: retry:: ErrorKind ;
12
+ use std:: error:: Error as StdError ;
13
+ use std:: marker:: PhantomData ;
11
14
12
15
/// AWS error codes that represent throttling errors.
13
16
pub const THROTTLING_ERRORS : & [ & str ] = & [
@@ -31,16 +34,31 @@ pub const THROTTLING_ERRORS: &[&str] = &[
31
34
pub const TRANSIENT_ERRORS : & [ & str ] = & [ "RequestTimeout" , "RequestTimeoutException" ] ;
32
35
33
36
/// A retry classifier for determining if the response sent by an AWS service requires a retry.
34
- #[ derive( Debug ) ]
35
- pub struct AwsErrorCodeClassifier ;
36
-
37
- impl AwsErrorCodeClassifier {
38
- /// Classify an error code to check if represents a retryable error. The codes of retryable
39
- /// errors are defined [here](THROTTLING_ERRORS) and [here](TRANSIENT_ERRORS).
40
- pub fn classify_error < E : ProvideErrorMetadata , R > (
41
- & self ,
42
- error : & SdkError < E , R > ,
43
- ) -> Option < RetryReason > {
37
+ #[ derive( Debug , Default ) ]
38
+ pub struct AwsErrorCodeClassifier < E > {
39
+ _inner : PhantomData < E > ,
40
+ }
41
+
42
+ impl < E > AwsErrorCodeClassifier < E > {
43
+ /// Create a new AwsErrorCodeClassifier
44
+ pub fn new ( ) -> Self {
45
+ Self {
46
+ _inner : PhantomData ,
47
+ }
48
+ }
49
+ }
50
+
51
+ impl < E > ClassifyRetry for AwsErrorCodeClassifier < E >
52
+ where
53
+ E : StdError + ProvideErrorMetadata + Send + Sync + ' static ,
54
+ {
55
+ fn classify_retry ( & self , ctx : & InterceptorContext ) -> Option < RetryReason > {
56
+ let error = ctx
57
+ . output_or_error ( ) ?
58
+ . err ( )
59
+ . and_then ( OrchestratorError :: as_operation_error) ?
60
+ . downcast_ref :: < E > ( ) ?;
61
+
44
62
if let Some ( error_code) = error. code ( ) {
45
63
if THROTTLING_ERRORS . contains ( & error_code) {
46
64
return Some ( RetryReason :: Error ( ErrorKind :: ThrottlingError ) ) ;
@@ -51,37 +69,51 @@ impl AwsErrorCodeClassifier {
51
69
52
70
None
53
71
}
72
+
73
+ fn name ( & self ) -> & ' static str {
74
+ "AWS Error Code"
75
+ }
54
76
}
55
77
56
78
/// A retry classifier that checks for `x-amz-retry-after` headers. If one is found, a
57
79
/// [`RetryReason::Explicit`] is returned containing the duration to wait before retrying.
58
- #[ derive( Debug ) ]
80
+ #[ derive( Debug , Default ) ]
59
81
pub struct AmzRetryAfterHeaderClassifier ;
60
82
61
83
impl AmzRetryAfterHeaderClassifier {
62
- /// Classify an AWS responses error code to determine how (and if) it should be retried.
63
- pub fn classify_error < E > ( & self , error : & SdkError < E > ) -> Option < RetryReason > {
64
- error
65
- . raw_response ( )
84
+ /// Create a new `AmzRetryAfterHeaderClassifier`.
85
+ pub fn new ( ) -> Self {
86
+ Self
87
+ }
88
+ }
89
+
90
+ impl ClassifyRetry for AmzRetryAfterHeaderClassifier {
91
+ fn classify_retry ( & self , ctx : & InterceptorContext ) -> Option < RetryReason > {
92
+ ctx. response ( )
66
93
. and_then ( |res| res. http_headers ( ) . get ( "x-amz-retry-after" ) )
67
94
. and_then ( |header| header. to_str ( ) . ok ( ) )
68
95
. and_then ( |header| header. parse :: < u64 > ( ) . ok ( ) )
69
96
. map ( |retry_after_delay| {
70
97
RetryReason :: Explicit ( std:: time:: Duration :: from_millis ( retry_after_delay) )
71
98
} )
72
99
}
100
+
101
+ fn name ( & self ) -> & ' static str {
102
+ "'Retry After' Header"
103
+ }
73
104
}
74
105
75
106
#[ cfg( test) ]
76
107
mod test {
77
108
use super :: { AmzRetryAfterHeaderClassifier , AwsErrorCodeClassifier } ;
78
109
use aws_smithy_http:: body:: SdkBody ;
79
- use aws_smithy_http :: operation ;
80
- use aws_smithy_http :: result :: SdkError ;
81
- use aws_smithy_runtime_api:: client:: retries:: RetryReason ;
110
+ use aws_smithy_runtime_api :: client :: interceptors :: InterceptorContext ;
111
+ use aws_smithy_runtime_api :: client :: orchestrator :: OrchestratorError ;
112
+ use aws_smithy_runtime_api:: client:: retries:: { ClassifyRetry , RetryReason } ;
82
113
use aws_smithy_types:: error:: metadata:: ProvideErrorMetadata ;
83
114
use aws_smithy_types:: error:: ErrorMetadata ;
84
115
use aws_smithy_types:: retry:: { ErrorKind , ProvideErrorKind } ;
116
+ use aws_smithy_types:: type_erasure:: { TypeErasedBox , TypeErasedError } ;
85
117
use std:: fmt;
86
118
use std:: time:: Duration ;
87
119
@@ -96,6 +128,17 @@ mod test {
96
128
97
129
impl std:: error:: Error for UnmodeledError { }
98
130
131
+ impl ProvideErrorKind for UnmodeledError {
132
+ fn retryable_error_kind ( & self ) -> Option < ErrorKind > {
133
+ None
134
+ }
135
+
136
+ fn code ( & self ) -> Option < & str > {
137
+ None
138
+ }
139
+ }
140
+
141
+ #[ derive( Debug ) ]
99
142
struct CodedError {
100
143
metadata : ErrorMetadata ,
101
144
}
@@ -108,16 +151,14 @@ mod test {
108
151
}
109
152
}
110
153
111
- impl ProvideErrorKind for UnmodeledError {
112
- fn retryable_error_kind ( & self ) -> Option < ErrorKind > {
113
- None
114
- }
115
-
116
- fn code ( & self ) -> Option < & str > {
117
- None
154
+ impl fmt:: Display for CodedError {
155
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
156
+ write ! ( f, "Coded Error" )
118
157
}
119
158
}
120
159
160
+ impl std:: error:: Error for CodedError { }
161
+
121
162
impl ProvideErrorMetadata for CodedError {
122
163
fn meta ( & self ) -> & ErrorMetadata {
123
164
& self . metadata
@@ -126,31 +167,39 @@ mod test {
126
167
127
168
#[ test]
128
169
fn classify_by_error_code ( ) {
129
- let policy = AwsErrorCodeClassifier ;
130
- let res = http:: Response :: new ( "OK" ) ;
131
- let err = SdkError :: service_error ( CodedError :: new ( "Throttling" ) , res) ;
170
+ let policy = AwsErrorCodeClassifier :: < CodedError > :: new ( ) ;
171
+ let mut ctx = InterceptorContext :: new ( TypeErasedBox :: new ( "doesntmatter" ) ) ;
172
+ ctx. set_output_or_error ( Err ( OrchestratorError :: operation ( TypeErasedError :: new (
173
+ CodedError :: new ( "Throttling" ) ,
174
+ ) ) ) ) ;
132
175
133
176
assert_eq ! (
134
- policy. classify_error ( & err ) ,
177
+ policy. classify_retry ( & ctx ) ,
135
178
Some ( RetryReason :: Error ( ErrorKind :: ThrottlingError ) )
136
179
) ;
137
180
138
- let res = http:: Response :: new ( "OK" ) ;
139
- let err = SdkError :: service_error ( CodedError :: new ( "RequestTimeout" ) , res) ;
181
+ let mut ctx = InterceptorContext :: new ( TypeErasedBox :: new ( "doesntmatter" ) ) ;
182
+ ctx. set_output_or_error ( Err ( OrchestratorError :: operation ( TypeErasedError :: new (
183
+ CodedError :: new ( "RequestTimeout" ) ,
184
+ ) ) ) ) ;
140
185
assert_eq ! (
141
- policy. classify_error ( & err ) ,
186
+ policy. classify_retry ( & ctx ) ,
142
187
Some ( RetryReason :: Error ( ErrorKind :: TransientError ) )
143
188
)
144
189
}
145
190
146
191
#[ test]
147
192
fn classify_generic ( ) {
148
- let policy = AwsErrorCodeClassifier ;
149
- let res = http:: Response :: new ( "OK" ) ;
193
+ let policy = AwsErrorCodeClassifier :: < ErrorMetadata > :: new ( ) ;
150
194
let err = aws_smithy_types:: Error :: builder ( ) . code ( "SlowDown" ) . build ( ) ;
151
- let err = SdkError :: service_error ( err, res) ;
195
+ let test_response = http:: Response :: new ( "OK" ) . map ( SdkBody :: from) ;
196
+
197
+ let mut ctx = InterceptorContext :: new ( TypeErasedBox :: new ( "doesntmatter" ) ) ;
198
+ ctx. set_response ( test_response) ;
199
+ ctx. set_output_or_error ( Err ( OrchestratorError :: operation ( TypeErasedError :: new ( err) ) ) ) ;
200
+
152
201
assert_eq ! (
153
- policy. classify_error ( & err ) ,
202
+ policy. classify_retry ( & ctx ) ,
154
203
Some ( RetryReason :: Error ( ErrorKind :: ThrottlingError ) )
155
204
) ;
156
205
}
@@ -163,11 +212,14 @@ mod test {
163
212
. body ( "retry later" )
164
213
. unwrap ( )
165
214
. map ( SdkBody :: from) ;
166
- let res = operation:: Response :: new ( res) ;
167
- let err = SdkError :: service_error ( UnmodeledError , res) ;
215
+ let mut ctx = InterceptorContext :: new ( TypeErasedBox :: new ( "doesntmatter" ) ) ;
216
+ ctx. set_response ( res) ;
217
+ ctx. set_output_or_error ( Err ( OrchestratorError :: operation ( TypeErasedError :: new (
218
+ UnmodeledError ,
219
+ ) ) ) ) ;
168
220
169
221
assert_eq ! (
170
- policy. classify_error ( & err ) ,
222
+ policy. classify_retry ( & ctx ) ,
171
223
Some ( RetryReason :: Explicit ( Duration :: from_millis( 5000 ) ) ) ,
172
224
) ;
173
225
}
0 commit comments