Skip to content

fix(contributor-profile): add GitHub response validation#708

Open
Tanayajadhav1 wants to merge 1 commit into
GitMetricsLab:mainfrom
Tanayajadhav1:fix/contributorprofile-response-validation
Open

fix(contributor-profile): add GitHub response validation#708
Tanayajadhav1 wants to merge 1 commit into
GitMetricsLab:mainfrom
Tanayajadhav1:fix/contributorprofile-response-validation

Conversation

@Tanayajadhav1
Copy link
Copy Markdown
Contributor

@Tanayajadhav1 Tanayajadhav1 commented Jun 4, 2026

Related Issue

Description

This PR adds runtime validation and error handling to the ContributorProfile page when fetching GitHub user and pull request data.

Changes made:

  • Added runtime type guards for profile and pull request responses.
  • Added HTTP response validation using res.ok.
  • Added validation for GitHub profile response structure before updating state.
  • Added validation for pull request response structure before updating state.
  • Added dedicated error state and fallback UI for invalid API responses.
  • Added handling for common GitHub API errors such as 404 (user not found) and 403 (rate limit exceeded).
  • Prevented malformed API responses from causing runtime crashes or invalid UI states.

How Has This Been Tested?

  • Verified that valid GitHub profile data loads correctly.
  • Verified that pull request data renders correctly for valid users.
  • Reviewed error handling paths for:
    • Invalid API responses
    • User not found (404)
    • GitHub rate limiting (403)

Screenshots (if applicable)

N/A

Type of Change

  • Bug fix
  • New feature
  • Code style update
  • Breaking change
  • Documentation update

suggested labels:
gssoc'26, level:intermediate, bug, type:fix , size:s , area:quality,priority:medium

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling in the Contributor Profile and Contributors pages with clearer, context-specific error messages.
    • Added timeout protection for API requests.
    • Error messages now distinguish between "user not found," rate limiting, and other failure scenarios, providing better guidance to users.

Copilot AI review requested due to automatic review settings June 4, 2026 05:07
@netlify
Copy link
Copy Markdown

netlify Bot commented Jun 4, 2026

Deploy Preview for github-spy ready!

Name Link
🔨 Latest commit 21d34f4
🔍 Latest deploy log https://app.netlify.com/projects/github-spy/deploys/6a2108081aeb160008ab5b7a
😎 Deploy Preview https://deploy-preview-708--github-spy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

ContributorProfile and Contributors pages now validate GitHub API responses before state updates and surface structured error states. Type guards ensure data shape correctness; distinct error handling branches address 404, rate-limit, and timeout scenarios. Error UI displays status-specific messaging.

Changes

API Error Handling and Validation Pattern

Layer / File(s) Summary
ContributorProfile response validation and error state
src/pages/ContributorProfile/ContributorProfile.tsx
Profile type allows null bio; runtime type guards isProfile and isPrArray validate GitHub user and PR search responses before state update; dedicated error state tracks fetch failures.
ContributorProfile fetch logic with error handling and validation
src/pages/ContributorProfile/ContributorProfile.tsx
Fetch effect tracks loading/error, fails fast on missing username, maps GitHub status codes (404, 403) to tailored errors, validates parsed responses with type guards, updates state only on success, resets profile/prs on error, and ends loading in finally block.
ContributorProfile error message rendering
src/pages/ContributorProfile/ContributorProfile.tsx
Early render branch displays current error message when present.
Contributors response validation and error types
src/pages/Contributors/Contributors.tsx
Defines FetchError interface and ContributorsError class to structure error state with message, statusCode, and isRateLimited flag; isContributorArray type guard validates API response shape.
Contributors fetch logic with timeout and error mapping
src/pages/Contributors/Contributors.tsx
Fetch effect validates response.data with isContributorArray, adds Axios timeout, maps rate-limit/404/timeout/HTTP/unknown errors into FetchError, logs error, clears contributors on failure.
Contributors error Alert with conditional helper text
src/pages/Contributors/Contributors.tsx
Renders MUI Alert from error.message with conditional explanatory text for rate-limit, 404, and 408 statuses.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • GitMetricsLab/github_tracker#560: Modifies src/pages/Contributors/Contributors.tsx to add structured GitHub error handling with HTTP 403 rate-limit detection and dedicated MUI Alert UI.
  • GitMetricsLab/github_tracker#484: Adds response validation and safer PR parsing to ContributorProfile.tsx fetch logic, forming a foundation for this PR's extended error handling and type guards.
  • GitMetricsLab/github_tracker#532: Introduces the same structured FetchError/ContributorsError, isContributorArray validation, axios timeout, and MUI Alert–based error UI in Contributors.tsx.

Suggested labels

quality:clean, level:intermediate, gssoc:approved

Poem

🐰 A rabbit hops through GitHub's halls,
Where type guards catch the API's falls,
No null bio breaks the dawn,
With structured errors, bugs are gone!
Rate limits now speak up so clear,
Validation brought good cheer!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding GitHub response validation to the contributor profile component, which matches the primary objective of the PR.
Description check ✅ Passed The description follows the template structure with all required sections completed: Related Issue, Description, Testing, Type of Change. Details are specific and comprehensive about validation and error handling changes.
Linked Issues check ✅ Passed The PR fulfills all coding requirements from issue #707: validates HTTP responses, implements runtime type guards, validates response structures before state updates, handles GitHub errors (404, 403), and prevents malformed responses from causing crashes.
Out of Scope Changes check ✅ Passed Changes are scoped to the two affected components (ContributorProfile and Contributors) with focused modifications to validation, error handling, and type guards. All alterations directly address issue #707 requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Improves reliability and UX of GitHub data fetching on the Contributors list and Contributor Profile pages by adding runtime response validation and more detailed error handling.

Changes:

  • Added structured error state and expanded error UI for contributors fetch failures (rate limit, 404, timeout, invalid payload).
  • Added runtime type guards and fetch() response status handling for contributor profile + PR search.
  • Introduced early-return error UI for missing username / request failures.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
src/pages/Contributors/Contributors.tsx Adds runtime validation + richer error object/state and more detailed error UI for contributor fetches.
src/pages/ContributorProfile/ContributorProfile.tsx Adds runtime validation, better HTTP status handling, and an error state/UI for profile + PR fetching.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pages/Contributors/Contributors.tsx
Comment on lines +149 to +151
<Typography variant="caption" sx={{ display: "block", mt: 1 }}>
You've hit GitHub's API rate limit. The limit resets in 1 hour.
</Typography>
Comment thread src/pages/Contributors/Contributors.tsx
Comment thread src/pages/ContributorProfile/ContributorProfile.tsx
Comment thread src/pages/ContributorProfile/ContributorProfile.tsx
Comment thread src/pages/ContributorProfile/ContributorProfile.tsx
@Tanayajadhav1
Copy link
Copy Markdown
Contributor Author

hi @mehul-m-prajapati please review this PR whenever possible!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/pages/ContributorProfile/ContributorProfile.tsx (2)

112-115: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle clipboard write failures (promise rejection).

navigator.clipboard.writeText(...) returns a Promise, but handleCopyLink doesn’t await or catch it, so clipboard/permission errors won’t trigger any user-facing error state.

Suggested fix
-  const handleCopyLink = () => {
-    navigator.clipboard.writeText(window.location.href);
-    toast.success("🔗 Shareable link copied to clipboard!");
-  };
+  const handleCopyLink = async () => {
+    try {
+      await navigator.clipboard.writeText(window.location.href);
+      toast.success("🔗 Shareable link copied to clipboard!");
+    } catch {
+      toast.error("Failed to copy link. Please copy it manually.");
+    }
+  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ContributorProfile/ContributorProfile.tsx` around lines 112 - 115,
The handleCopyLink function currently calls
navigator.clipboard.writeText(window.location.href) without awaiting or handling
rejections; update handleCopyLink to await the writeText Promise (or use
.then/.catch) and on success call toast.success("🔗 Shareable link copied to
clipboard!"), and on failure call toast.error with a user-friendly message and
optionally log the error; target the handleCopyLink function and the
navigator.clipboard.writeText call and ensure clipboard permission/exception
paths are handled.

48-110: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cancel in-flight GitHub requests on username changes to prevent stale state updates.
useEffect starts async fetch calls for the user and PRs but never aborts them; when username changes quickly, an older response can still call setProfile/setPRs/setError. Add an AbortController, pass controller.signal to both fetch calls, abort in the useEffect cleanup, and ignore AbortError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ContributorProfile/ContributorProfile.tsx` around lines 48 - 110,
The effect's async fetchData starts GitHub fetches that can resolve after
username changes and cause stale setProfile/setPRs/setError updates; update
fetchData/useEffect to create an AbortController, pass controller.signal into
both fetch calls (the user fetch and prs fetch), abort the controller in the
useEffect cleanup, and in the catch block ignore errors where err.name ===
'AbortError' so you don't call setState/toast for canceled requests.
src/pages/Contributors/Contributors.tsx (1)

224-225: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add rel="noopener noreferrer" for external new-tab links.

Opening contributor GitHub pages with target="_blank" without rel exposes reverse-tabnabbing risk.

Suggested fix
                         <Button
                             variant="contained"
                             startIcon={<FaGithub />}
                             href={contributor.html_url}
                             target="_blank"
+                            rel="noopener noreferrer"
                             sx={{
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Contributors/Contributors.tsx` around lines 224 - 225, In the
anchor/link elements in Contributors.tsx that set target="_blank" (the JSX
elements around the contributor GitHub links where you currently have
target="_blank" and sx={{...}}), add rel="noopener noreferrer" to the same
element to prevent reverse-tabnabbing; update each instance of the anchor/Link
with target="_blank" to include rel="noopener noreferrer".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/ContributorProfile/ContributorProfile.tsx`:
- Line 60: The code interpolates username raw into GitHub URLs (e.g., the fetch
call in ContributorProfile.tsx: const userRes = await
fetch(`https://api.github.com/users/${username}``) and the PR search q
parameter), which can break requests for usernames with special characters; fix
by applying encodeURIComponent(username) wherever username is inserted into URLs
(both the user fetch and the PR search query construction) so the resulting
URL/query is properly escaped while preserving existing logic and variable
names.
- Around line 17-24: The runtime type checks in ContributorProfile use `as any`;
replace those with safe narrowing via `Record<string, unknown>` and a small
`hasItems` helper: in `isProfile`, cast `data` to `const obj = data as
Record<string, unknown>` and check properties with `typeof obj.avatar_url ===
'string'`, `typeof obj.login === 'string'`, and `(typeof obj.bio === 'string' ||
obj.bio === null)`. Add a `function hasItems(value: unknown): value is unknown[]
{ return Array.isArray(value) && value.length > 0; }` and use it where array
casts were done to validate non-empty arrays and element types instead of `as
any`. Apply the same pattern to the other runtime guards in this file (replace
all `as any` usages) so all runtime parsing is done via `Record<string,
unknown>` checks and `hasItems` assertions.

---

Outside diff comments:
In `@src/pages/ContributorProfile/ContributorProfile.tsx`:
- Around line 112-115: The handleCopyLink function currently calls
navigator.clipboard.writeText(window.location.href) without awaiting or handling
rejections; update handleCopyLink to await the writeText Promise (or use
.then/.catch) and on success call toast.success("🔗 Shareable link copied to
clipboard!"), and on failure call toast.error with a user-friendly message and
optionally log the error; target the handleCopyLink function and the
navigator.clipboard.writeText call and ensure clipboard permission/exception
paths are handled.
- Around line 48-110: The effect's async fetchData starts GitHub fetches that
can resolve after username changes and cause stale setProfile/setPRs/setError
updates; update fetchData/useEffect to create an AbortController, pass
controller.signal into both fetch calls (the user fetch and prs fetch), abort
the controller in the useEffect cleanup, and in the catch block ignore errors
where err.name === 'AbortError' so you don't call setState/toast for canceled
requests.

In `@src/pages/Contributors/Contributors.tsx`:
- Around line 224-225: In the anchor/link elements in Contributors.tsx that set
target="_blank" (the JSX elements around the contributor GitHub links where you
currently have target="_blank" and sx={{...}}), add rel="noopener noreferrer" to
the same element to prevent reverse-tabnabbing; update each instance of the
anchor/Link with target="_blank" to include rel="noopener noreferrer".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d303be58-4f2b-4172-8f75-c56d6415d814

📥 Commits

Reviewing files that changed from the base of the PR and between 53f820b and 21d34f4.

📒 Files selected for processing (2)
  • src/pages/ContributorProfile/ContributorProfile.tsx
  • src/pages/Contributors/Contributors.tsx

Comment on lines +17 to +24
const isProfile = (data: unknown): data is Profile => {
return (
typeof data === "object" &&
data !== null &&
typeof (data as any).avatar_url === "string" &&
typeof (data as any).login === "string" &&
(typeof (data as any).bio === "string" || (data as any).bio === null)
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify remaining explicit-any usage in this file
rg -n --type=ts --type=tsx '\bas\s+any\b' src/pages/ContributorProfile/ContributorProfile.tsx

Repository: GitMetricsLab/github_tracker

Length of output: 100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/pages/ContributorProfile/ContributorProfile.tsx"

echo "== any/as any occurrences =="
rg -n --no-heading --hidden --follow --glob "$FILE" '\bas\s+any\b|\bno-explicit-any\b|\b:\s*any\b' "$FILE" || true

echo
echo "== context around the previously cited lines (15-45) =="
nl -ba "$FILE" | sed -n '1,120p'

echo
echo "== context around the previously cited lines (70-120) =="
nl -ba "$FILE" | sed -n '70,140p'

Repository: GitMetricsLab/github_tracker

Length of output: 719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/pages/ContributorProfile/ContributorProfile.tsx"
wc -l "$FILE"

echo
echo "== Lines 1-60 =="
cat -n "$FILE" | sed -n '1,60p'

echo
echo "== Lines 70-120 =="
cat -n "$FILE" | sed -n '70,120p'

Repository: GitMetricsLab/github_tracker

Length of output: 3996


Remove as any casts in runtime guards/PR parsing to satisfy no-explicit-any and keep validation type-safe.

src/pages/ContributorProfile/ContributorProfile.tsx still uses as any at lines 21-23, 34-36, 91-92, and 97. Replace these casts with Record<string, unknown>-based narrowing and a small hasItems guard.

Suggested fix
+type UnknownRecord = Record<string, unknown>;
+
 const isProfile = (data: unknown): data is Profile => {
+  const d = data as UnknownRecord;
   return (
     typeof data === "object" &&
     data !== null &&
-    typeof (data as any).avatar_url === "string" &&
-    typeof (data as any).login === "string" &&
-    (typeof (data as any).bio === "string" || (data as any).bio === null)
+    typeof d.avatar_url === "string" &&
+    typeof d.login === "string" &&
+    (typeof d.bio === "string" || d.bio === null)
   );
 };
 
 const isPrArray = (data: unknown): data is PR[] => {
   return (
     Array.isArray(data) &&
     data.every(
       (item) =>
         typeof item === "object" &&
         item !== null &&
-        typeof (item as any).title === "string" &&
-        typeof (item as any).html_url === "string" &&
-        typeof (item as any).repository_url === "string"
+        typeof (item as UnknownRecord).title === "string" &&
+        typeof (item as UnknownRecord).html_url === "string" &&
+        typeof (item as UnknownRecord).repository_url === "string"
     )
   );
 };
+
+const hasItems = (data: unknown): data is { items: unknown } =>
+  typeof data === "object" && data !== null && "items" in data;
 
-        if (
-          typeof prsData !== "object" ||
-          prsData === null ||
-          !Array.isArray((prsData as any).items) ||
-          !isPrArray((prsData as any).items)
-        ) {
+        if (!hasItems(prsData) || !Array.isArray(prsData.items) || !isPrArray(prsData.items)) {
           throw new Error("Invalid GitHub PR response.");
         }
 
-        setPRs((prsData as any).items);
+        setPRs(prsData.items);
🧰 Tools
🪛 ESLint

[error] 21-21: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 22-22: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 23-23: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 23-23: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ContributorProfile/ContributorProfile.tsx` around lines 17 - 24,
The runtime type checks in ContributorProfile use `as any`; replace those with
safe narrowing via `Record<string, unknown>` and a small `hasItems` helper: in
`isProfile`, cast `data` to `const obj = data as Record<string, unknown>` and
check properties with `typeof obj.avatar_url === 'string'`, `typeof obj.login
=== 'string'`, and `(typeof obj.bio === 'string' || obj.bio === null)`. Add a
`function hasItems(value: unknown): value is unknown[] { return
Array.isArray(value) && value.length > 0; }` and use it where array casts were
done to validate non-empty arrays and element types instead of `as any`. Apply
the same pattern to the other runtime guards in this file (replace all `as any`
usages) so all runtime parsing is done via `Record<string, unknown>` checks and
`hasItems` assertions.

setError(null);

try {
const userRes = await fetch(`https://api.github.com/users/${username}`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and print the relevant section around the cited line(s).
FILE="src/pages/ContributorProfile/ContributorProfile.tsx"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "== File: $FILE =="
wc -l "$FILE"
sed -n '1,140p' "$FILE" | nl -ba | sed -n '40,100p'

# Also search for usages of `username` and GitHub URL construction within the file.
echo "== rg: username in ContributorProfile.tsx =="
rg -n "username" "$FILE"

echo "== rg: github.com in ContributorProfile.tsx =="
rg -n "github\.com" "$FILE"

Repository: GitMetricsLab/github_tracker

Length of output: 254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/pages/ContributorProfile/ContributorProfile.tsx"

echo "== File: $FILE =="
wc -l "$FILE"

echo "== Lines 40-100 =="
awk 'NR>=40 && NR<=100 {printf "%d\t%s\n", NR, $0}' "$FILE"

echo "== rg: username in ContributorProfile.tsx =="
rg -n "username" "$FILE" || true

echo "== rg: github.com in ContributorProfile.tsx =="
rg -n "github\.com" "$FILE" || true

echo "== Print fetch/search URL constructions =="
rg -n "fetch\\(|search/issues" "$FILE" || true

Repository: GitMetricsLab/github_tracker

Length of output: 3230


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/pages/ContributorProfile/ContributorProfile.tsx"
rg -n "encodeURIComponent" "$FILE" || true

Repository: GitMetricsLab/github_tracker

Length of output: 54


Encode username before building GitHub URLs.
username is interpolated raw into the GitHub user fetch URL and the PR search q parameter; special characters can break requests or change query semantics.

Suggested fix
-        const userRes = await fetch(`https://api.github.com/users/${username}`);
+        const encodedUsername = encodeURIComponent(username);
+        const userRes = await fetch(`https://api.github.com/users/${encodedUsername}`);
...
-          `https://api.github.com/search/issues?q=author:${username}+type:pr`
+          `https://api.github.com/search/issues?q=author:${encodedUsername}+type:pr`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ContributorProfile/ContributorProfile.tsx` at line 60, The code
interpolates username raw into GitHub URLs (e.g., the fetch call in
ContributorProfile.tsx: const userRes = await
fetch(`https://api.github.com/users/${username}``) and the PR search q
parameter), which can break requests for usernames with special characters; fix
by applying encodeURIComponent(username) wherever username is inserted into URLs
(both the user fetch and the PR search query construction) so the resulting
URL/query is properly escaped while preserving existing logic and variable
names.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Bug: ContributorProfile Uses Unvalidated GitHub API Responses

2 participants