feat(github): implement PRReader + FileReader client (#80) #93
+28
-18
@@ -255,25 +255,11 @@ func (c *Client) doRequest(ctx context.Context, method, reqURL string, accept st
|
||||
return nil, fmt.Errorf("do request: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes+1))
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response body: %w", err)
|
||||
}
|
||||
if len(body) > maxResponseBytes {
|
||||
return nil, fmt.Errorf("response body exceeded %d bytes (truncated)", maxResponseBytes)
|
||||
}
|
||||
return body, nil
|
||||
body, done, err := c.handleResponse(resp, maxResponseBytes, maxErrorBodyBytes)
|
||||
if done {
|
||||
|
|
||||
return body, err
|
||||
}
|
||||
|
||||
errBody, readErr := io.ReadAll(io.LimitReader(resp.Body, maxErrorBodyBytes))
|
||||
if readErr != nil && len(errBody) == 0 {
|
||||
errBody = []byte(fmt.Sprintf("[error reading response body: %v]", readErr))
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
lastErr = &APIError{StatusCode: resp.StatusCode, Body: string(errBody)}
|
||||
lastErr = err
|
||||
|
||||
|
sonnet-review-bot
commented
[NIT] In **[NIT]** In `doRequest`, after the `handleResponse` call, there's a check `if resp.StatusCode == http.StatusTooManyRequests` but `resp` could theoretically have been closed by `handleResponse`. The body is closed, but the `resp.StatusCode` field is still accessible on the struct. This is correct and safe in Go's net/http — just worth being aware of.
|
||||
// Retry on 429 rate limit
|
||||
if resp.StatusCode == http.StatusTooManyRequests && attempt < maxAttempts-1 {
|
||||
@@ -311,6 +297,30 @@ func (c *Client) doRequest(ctx context.Context, method, reqURL string, accept st
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
// handleResponse reads and closes the response body, returning the result.
|
||||
// It uses defer to ensure the body is always closed regardless of code path.
|
||||
// Returns (body, done, err) where done=true means the caller should return immediately.
|
||||
func (c *Client) handleResponse(resp *http.Response, maxRespBytes int, maxErrBytes int) ([]byte, bool, error) {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, int64(maxRespBytes)+1))
|
||||
if err != nil {
|
||||
return nil, true, fmt.Errorf("read response body: %w", err)
|
||||
}
|
||||
if len(body) > maxRespBytes {
|
||||
return nil, true, fmt.Errorf("response body exceeded %d bytes (truncated)", maxRespBytes)
|
||||
}
|
||||
return body, true, nil
|
||||
}
|
||||
|
||||
errBody, readErr := io.ReadAll(io.LimitReader(resp.Body, int64(maxErrBytes)))
|
||||
if readErr != nil && len(errBody) == 0 {
|
||||
errBody = []byte(fmt.Sprintf("[error reading response body: %v]", readErr))
|
||||
}
|
||||
return nil, false, &APIError{StatusCode: resp.StatusCode, Body: string(errBody)}
|
||||
}
|
||||
|
||||
// doGet is a convenience wrapper for GET requests with the default Accept header.
|
||||
func (c *Client) doGet(ctx context.Context, reqURL string) ([]byte, error) {
|
||||
return c.doRequest(ctx, http.MethodGet, reqURL, "")
|
||||
|
||||
Reference in New Issue
Block a user
[MINOR] The
int64cast inif int64(len(body)) >= maxResponseBytesis unnecessary sincemaxResponseBytesis an untyped constant andlen()returnsint. Both sides of the comparison areint-typed. The cast is harmless but adds noise.