feat(github): implement PRReader + FileReader client (#80) #93

Closed
rodin wants to merge 16 commits from review-bot-issue-80 into feature/github-support
3 changed files with 6 additions and 3 deletions
Showing only changes of commit 30798ff023 - Show all commits
+1 -1
View File
18
@@ -225,7 +225,7 @@ func (c *Client) doRequest(ctx context.Context, method, reqURL string, accept st
timer := time.NewTimer(delay)
select {
case <-timer.C:
timer.Stop() // no-op after fire, releases runtime resources promptly
timer.Stop() // no-op after fire; kept for symmetry with the ctx.Done case
case <-ctx.Done():
timer.Stop()
Review

[MINOR] After resp.Body.Close() is called for a successful response, if io.ReadAll returns an error, the body is already closed which is correct, but the success path reads up to maxResponseBytes+1 bytes via io.LimitReader. This is the intentional over-read trick to detect exceeding the limit, which is correct. However, on the error path for non-2xx responses (line 245), errBody, _ := io.ReadAll(...) silently discards the read error. While acceptable in practice since it's for error body capture, the convention in this codebase is to check all error returns. This is minor since the worst case is an empty error body.

**[MINOR]** After `resp.Body.Close()` is called for a successful response, if `io.ReadAll` returns an error, the body is already closed which is correct, but the success path reads up to `maxResponseBytes+1` bytes via `io.LimitReader`. This is the intentional over-read trick to detect exceeding the limit, which is correct. However, on the error path for non-2xx responses (line 245), `errBody, _ := io.ReadAll(...)` silently discards the read error. While acceptable in practice since it's for error body capture, the convention in this codebase is to check all error returns. This is minor since the worst case is an empty error body.
return nil, ctx.Err()
6
+1 -1
View File
7
@@ -81,7 +81,7 @@ func (c *Client) ListContents(ctx context.Context, owner, repo, path string) ([]
if err := json.Unmarshal(body, &entries); err != nil {
var single entry
if err2 := json.Unmarshal(body, &single); err2 != nil {
return nil, fmt.Errorf("parse contents JSON: %w", err2)
return nil, fmt.Errorf("parse contents JSON: as array: %v; as object: %w", err, err2)
}
// Guard against empty objects ({}) or unexpected shapes that
// unmarshal successfully but carry no useful data.
1
+4 -1
View File
9
@@ -127,6 +127,9 @@ func (c *Client) GetPullRequestFiles(ctx context.Context, owner, repo string, nu
// Returns nil (not an empty slice) when there are no statuses or check runs.
// If the commit statuses endpoint fails (e.g. 404 for an unknown SHA), the
// function returns immediately without attempting the check-runs endpoint.
// If the check-runs endpoint fails after statuses were fetched successfully,
// the function returns an error (not a partial result) so callers always get
Review

[NIT] The GetPullRequestFiles comment says 'Returns nil (not an empty slice) when the PR has no changed files' but if the first page returns an empty array, allFiles remains nil — this is correct. However the GitHub API actually returns an empty array for PRs with 0 changed files (valid edge case), so the nil vs empty distinction in the doc comment is accurate but subtle. No code change needed.

**[NIT]** The `GetPullRequestFiles` comment says 'Returns nil (not an empty slice) when the PR has no changed files' but if the first page returns an empty array, `allFiles` remains nil — this is correct. However the GitHub API actually returns an empty array for PRs with 0 changed files (valid edge case), so the nil vs empty distinction in the doc comment is accurate but subtle. No code change needed.
// either a complete view or a clear error signal.
func (c *Client) GetCommitStatuses(ctx context.Context, owner, repo, sha string) ([]vcs.CommitStatus, error) {
var result []vcs.CommitStatus
10
@@ -192,7 +195,7 @@ func mapCheckRunStatus(conclusion *string) string {
case "failure", "action_required", "timed_out":
Review

[MINOR] The mapCheckRunStatus mapping treats cancelled, skipped, and neutral as "success" (non-blocking). This is a policy decision that should be documented more prominently — cancelled in particular could reasonably be mapped to failure in some contexts. The comment // non-blocking is brief; a note explaining the design rationale (e.g. 'these do not indicate a blocking failure per GitHub's check suite semantics') would help future maintainers.

**[MINOR]** The `mapCheckRunStatus` mapping treats `cancelled`, `skipped`, and `neutral` as `"success"` (non-blocking). This is a policy decision that should be documented more prominently — `cancelled` in particular could reasonably be mapped to `failure` in some contexts. The comment `// non-blocking` is brief; a note explaining the design rationale (e.g. 'these do not indicate a blocking failure per GitHub's check suite semantics') would help future maintainers.
return "failure"
case "cancelled", "skipped", "neutral":
return "success" // non-blocking
return "success" // non-blocking: these do not indicate a blocking failure per GitHub check suite semantics
case "stale", "waiting":
return "pending"
default:
1