-
Notifications
You must be signed in to change notification settings - Fork 9
feat: expose client errors in fetcher and don't retry client errors #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: expose client errors in fetcher and don't retry client errors #72
Conversation
Reviewer's GuideAdds explicit handling of HTTP 4xx responses in the fetcher by introducing a new ClientError error variant, preventing retries for client errors, and covering the behavior with a dedicated test and a small HTTP utility helper. Sequence diagram for Fetcher client error handling and retry behaviorsequenceDiagram
participant Caller
participant Fetcher
participant RetryPolicy
participant HttpClient
participant http
Caller->>Fetcher: fetch(url, processor)
Fetcher->>RetryPolicy: configure retry
loop retry attempts
RetryPolicy->>Fetcher: call fetch_once(url, processor)
Fetcher->>HttpClient: send request
HttpClient-->>Fetcher: Response
Fetcher->>http: get_client_error(response)
http-->>Fetcher: Option_StatusCode
alt client error
Fetcher-->>RetryPolicy: Error::ClientError
RetryPolicy->>RetryPolicy: when(e) skip retry on ClientError
RetryPolicy-->>Caller: return Error::ClientError
else rate limited 429
Fetcher-->>RetryPolicy: Error::RateLimited
RetryPolicy->>RetryPolicy: schedule retry after delay
else success
Fetcher->>processor: process(response)
processor-->>Fetcher: Ok(result)
Fetcher-->>RetryPolicy: Ok(result)
RetryPolicy-->>Caller: Ok(result)
end
end
Class diagram for updated error handling in FetcherclassDiagram
class http {
<<module>>
get_client_error(response) Option_StatusCode
}
class Response {
status() StatusCode
}
class StatusCode {
is_client_error() bool
}
class Error {
<<enum>>
Request
RateLimited
ClientError
}
class Fetcher {
fetch(url, processor)
fetch_once(url, processor) Result
}
class Processor {
process(response) Result
}
Error <|-- Request
Error <|-- RateLimited
Error <|-- ClientError
Fetcher --> Error
Fetcher --> Processor
Fetcher --> http : uses
http --> Response
Response --> StatusCode
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- In
test_404_should_not_retry, the mock body string "Rate limited" is misleading for a 404 case and makes the test harder to read; consider changing it to something like "Not found" to better reflect the scenario being tested. - In
test_404_should_not_retry, instead ofassert_eq!(result.is_err(), true)followed by a manualif let, you can useassert!(matches!(result, Err(Error::ClientError(StatusCode::NOT_FOUND))))to make the expectation about the exact error type and status code more explicit and concise.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `test_404_should_not_retry`, the mock body string "Rate limited" is misleading for a 404 case and makes the test harder to read; consider changing it to something like "Not found" to better reflect the scenario being tested.
- In `test_404_should_not_retry`, instead of `assert_eq!(result.is_err(), true)` followed by a manual `if let`, you can use `assert!(matches!(result, Err(Error::ClientError(StatusCode::NOT_FOUND))))` to make the expectation about the exact error type and status code more explicit and concise.
## Individual Comments
### Comment 1
<location> `common/tests/fetcher.rs:64-73` </location>
<code_context>
}
+#[tokio::test]
+async fn test_404_should_not_retry() {
+ let attempt_count = Arc::new(AtomicUsize::new(0));
+ let attempt_count_clone = attempt_count.clone();
+
+ let server = start_mock_server(move |_req| {
+ attempt_count_clone.fetch_add(1, Ordering::SeqCst);
+ let builder = hyper::Response::builder().status(StatusCode::NOT_FOUND);
+ builder.body("Rate limited".to_string()).unwrap()
+ })
+ .await;
+
+ let fetcher = Fetcher::new(FetcherOptions::new().retries(2))
+ .await
+ .unwrap();
+
+ let result: Result<String, Error> = fetcher.fetch(&server).await;
+ assert_eq!(result.is_err(), true);
+ if let Error::ClientError(status_code) = result.unwrap_err() {
+ assert_eq!(status_code, StatusCode::NOT_FOUND);
+ }
+ assert_eq!(attempt_count.load(Ordering::SeqCst), 1);
+}
+
</code_context>
<issue_to_address>
**issue (testing):** Strengthen the assertion to ensure the error is specifically `Error::ClientError(StatusCode::NOT_FOUND)` rather than any error
Currently the test only asserts `is_err()` and then conditionally inspects the error with `if let`, so any non-`ClientError` variant would still let the test pass. Instead, unwrap as an error and assert the exact variant, e.g.:
```rust
let err = result.expect_err("expected client error");
assert!(matches!(err, Error::ClientError(StatusCode::NOT_FOUND)));
```
or:
```rust
match result {
Err(Error::ClientError(code)) => assert_eq!(code, StatusCode::NOT_FOUND),
other => panic!("expected ClientError(404), got {other:?}"),
}
```
so the test fails if the error type regresses.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
261dd8b to
68c087c
Compare
ctron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nit. But otherwise, looks great!
ctron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha, I could update that myself 😬
d473238 to
8f3470b
Compare
This is the first step towards #69 , more small PRs will follow.
Summary by Sourcery
Expose HTTP client errors from the fetcher and avoid retrying requests that fail with 4xx responses.
New Features:
Bug Fixes:
Enhancements:
Tests: