Skip to content

Commit 0cba3d8

Browse files
author
Zelda Hessler
authored
Update retry classifiers to act on InterceptorContext instead of OrchestratorError (#2737)
## Description <!--- Describe your changes in detail --> - update retry classifiers to act directly on the `InterceptorContext` - add codegen method for inserting retry classifiers into the config bag - update `InterceptorContext` to return options instead of panicking ## Testing <!--- Please describe in detail how you tested your changes --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> Tests were updated as needed. ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent 40612a9 commit 0cba3d8

File tree

19 files changed

+427
-286
lines changed

19 files changed

+427
-286
lines changed

aws/rust-runtime/aws-runtime/src/invocation_id.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ mod tests {
8080
use http::HeaderValue;
8181

8282
fn expect_header<'a>(context: &'a InterceptorContext, header_name: &str) -> &'a HeaderValue {
83-
context.request().headers().get(header_name).unwrap()
83+
context
84+
.request()
85+
.expect("request is set")
86+
.headers()
87+
.get(header_name)
88+
.unwrap()
8489
}
8590

8691
#[test]

aws/rust-runtime/aws-runtime/src/recursion_detection.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ mod tests {
159159
RecursionDetectionInterceptor { env }
160160
.modify_before_signing(&mut ctx, &mut config)
161161
.expect("interceptor must succeed");
162-
let mutated_request = context.request();
162+
let mutated_request = context.request().expect("request is set");
163163
for name in mutated_request.headers().keys() {
164164
assert_eq!(
165165
mutated_request.headers().get_all(name).iter().count(),

aws/rust-runtime/aws-runtime/src/request_info.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ mod tests {
168168
fn expect_header<'a>(context: &'a InterceptorContext, header_name: &str) -> &'a str {
169169
context
170170
.request()
171+
.expect("request is set")
171172
.headers()
172173
.get(header_name)
173174
.unwrap()

aws/rust-runtime/aws-runtime/src/retries/classifier.rs

Lines changed: 93 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
*/
55

66
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};
910
use aws_smithy_types::error::metadata::ProvideErrorMetadata;
1011
use aws_smithy_types::retry::ErrorKind;
12+
use std::error::Error as StdError;
13+
use std::marker::PhantomData;
1114

1215
/// AWS error codes that represent throttling errors.
1316
pub const THROTTLING_ERRORS: &[&str] = &[
@@ -31,16 +34,31 @@ pub const THROTTLING_ERRORS: &[&str] = &[
3134
pub const TRANSIENT_ERRORS: &[&str] = &["RequestTimeout", "RequestTimeoutException"];
3235

3336
/// 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+
4462
if let Some(error_code) = error.code() {
4563
if THROTTLING_ERRORS.contains(&error_code) {
4664
return Some(RetryReason::Error(ErrorKind::ThrottlingError));
@@ -51,37 +69,51 @@ impl AwsErrorCodeClassifier {
5169

5270
None
5371
}
72+
73+
fn name(&self) -> &'static str {
74+
"AWS Error Code"
75+
}
5476
}
5577

5678
/// A retry classifier that checks for `x-amz-retry-after` headers. If one is found, a
5779
/// [`RetryReason::Explicit`] is returned containing the duration to wait before retrying.
58-
#[derive(Debug)]
80+
#[derive(Debug, Default)]
5981
pub struct AmzRetryAfterHeaderClassifier;
6082

6183
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()
6693
.and_then(|res| res.http_headers().get("x-amz-retry-after"))
6794
.and_then(|header| header.to_str().ok())
6895
.and_then(|header| header.parse::<u64>().ok())
6996
.map(|retry_after_delay| {
7097
RetryReason::Explicit(std::time::Duration::from_millis(retry_after_delay))
7198
})
7299
}
100+
101+
fn name(&self) -> &'static str {
102+
"'Retry After' Header"
103+
}
73104
}
74105

75106
#[cfg(test)]
76107
mod test {
77108
use super::{AmzRetryAfterHeaderClassifier, AwsErrorCodeClassifier};
78109
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};
82113
use aws_smithy_types::error::metadata::ProvideErrorMetadata;
83114
use aws_smithy_types::error::ErrorMetadata;
84115
use aws_smithy_types::retry::{ErrorKind, ProvideErrorKind};
116+
use aws_smithy_types::type_erasure::{TypeErasedBox, TypeErasedError};
85117
use std::fmt;
86118
use std::time::Duration;
87119

@@ -96,6 +128,17 @@ mod test {
96128

97129
impl std::error::Error for UnmodeledError {}
98130

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)]
99142
struct CodedError {
100143
metadata: ErrorMetadata,
101144
}
@@ -108,16 +151,14 @@ mod test {
108151
}
109152
}
110153

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")
118157
}
119158
}
120159

160+
impl std::error::Error for CodedError {}
161+
121162
impl ProvideErrorMetadata for CodedError {
122163
fn meta(&self) -> &ErrorMetadata {
123164
&self.metadata
@@ -126,31 +167,39 @@ mod test {
126167

127168
#[test]
128169
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+
))));
132175

133176
assert_eq!(
134-
policy.classify_error(&err),
177+
policy.classify_retry(&ctx),
135178
Some(RetryReason::Error(ErrorKind::ThrottlingError))
136179
);
137180

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+
))));
140185
assert_eq!(
141-
policy.classify_error(&err),
186+
policy.classify_retry(&ctx),
142187
Some(RetryReason::Error(ErrorKind::TransientError))
143188
)
144189
}
145190

146191
#[test]
147192
fn classify_generic() {
148-
let policy = AwsErrorCodeClassifier;
149-
let res = http::Response::new("OK");
193+
let policy = AwsErrorCodeClassifier::<ErrorMetadata>::new();
150194
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+
152201
assert_eq!(
153-
policy.classify_error(&err),
202+
policy.classify_retry(&ctx),
154203
Some(RetryReason::Error(ErrorKind::ThrottlingError))
155204
);
156205
}
@@ -163,11 +212,14 @@ mod test {
163212
.body("retry later")
164213
.unwrap()
165214
.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+
))));
168220

169221
assert_eq!(
170-
policy.classify_error(&err),
222+
policy.classify_retry(&ctx),
171223
Some(RetryReason::Explicit(Duration::from_millis(5000))),
172224
);
173225
}

aws/rust-runtime/aws-runtime/src/user_agent.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ mod tests {
117117
fn expect_header<'a>(context: &'a InterceptorContext, header_name: &str) -> &'a str {
118118
context
119119
.request()
120+
.expect("request is set")
120121
.headers()
121122
.get(header_name)
122123
.unwrap()

0 commit comments

Comments
 (0)