Feat/http client - #47
dev-bramwel wants to merge 5 commits into
Conversation
|
Hello, kindly fix the failing linter tests or convert this PR to a draft if you are still working on it. |
|
This PR resolves all errcheck static analysis warnings raised by golangci-lint in http.go and http_test.go by explicitly suppressing expected/non-critical error return values using _, _ = or _ =. http.go:
http_test.go:
These unhandled return values were non-critical side effects or safe test operations where handling errors programmatically is unnecessary. Explicitly marking them as ignored ensures clear intent for maintainers and satisfies the errcheck linter rule, allowing CI workflow checks to pass cleanly. TestingRan golangci-lint run locally—all errcheck warnings in the repository are now cleared. |
| } | ||
|
|
||
| var lastErr error | ||
| for attempt := 1; attempt <= attempts; attempt++ { |
There was a problem hiding this comment.
Avoid automatically retrying for non-idempotent http methods such as POST and PATCH, unlessan idempotency key has been provided.
| } | ||
|
|
||
| if err := sleepBeforeRetry(ctx, c.retryBaseDelay, attempt); err != nil { | ||
| if lastErr != nil { |
There was a problem hiding this comment.
Instead of choosing to return one error over the other, return both of them by via error wrapping. For example:
return nil, fmt.Errorf("%w: previous request error: %v", err, lastErr)| "error", err, | ||
| ) | ||
|
|
||
| if !shouldRetryError(err) || attempt == attempts { |
There was a problem hiding this comment.
Avoid automatic retry after a 429 Too Many Requests. For this case retry after the time given in the Retry-After header or exit with an error.
| } | ||
|
|
||
| func shouldRetryError(err error) bool { | ||
| return err != nil |
There was a problem hiding this comment.
Not all errors should cause a retry. Some errors will not change the response from the server even after retrying.
| return nil, lastErr | ||
| } | ||
|
|
||
| func makeRequestBodyReplayable(req *http.Request) error { |
There was a problem hiding this comment.
makeRequestBodyReplayable reads the entire request body into memory using io.ReadAll. This creates an unbounded allocation and silently changes streaming requests into fully buffered requests. For sufficiently large bodies (e.g, from large files), this could cause excessive memory use or an out-of-memory failure. If body size can be influenced by untrusted input, it may also become a denial-of-service risk.
I suggest removing makeRequestBodyReplayable rather than automatically buffering arbitrary bodies.
For requests with a body, automatic retries should require req.GetBody method to be non-nil so that a fresh body can be created for each attempt without loading the complete payload into memory. When req.Body != nil && req.GetBody == nil, the client:
- sends the request once.
- if the request fails, return a clear error explaining that the body cannot be replayed for retries.
For example:
if attempts > 1 && req.Body != nil && req.GetBody == nil {
return nil, errors.New(
"paykit: request body cannot be replayed; provide GetBody or disable retries",
)
}
carsonak
left a comment
There was a problem hiding this comment.
Great work! I have requested some changes, kindly take a look at them and fix the issues and add tests for the fixes. Once done you can push the commits to this PR and mark the changes as resolved so I can merge this PR.
|
I will review them |
This addition resolves #6