Skip to content

Handle malformed saved gigs JSON#183

Merged
ralyodio merged 1 commit into
profullstack:masterfrom
morganschp:fix-saved-gigs-invalid-json
May 23, 2026
Merged

Handle malformed saved gigs JSON#183
ralyodio merged 1 commit into
profullstack:masterfrom
morganschp:fix-saved-gigs-invalid-json

Conversation

@morganschp
Copy link
Copy Markdown
Contributor

Summary

  • Return a deterministic 400 for malformed POST /api/saved-gigs and DELETE /api/saved-gigs request bodies.
  • Keep valid JSON payloads on the existing Zod validation path.
  • Add regression coverage proving malformed bodies do not query Supabase.

Fixes #182

Verification

  • ./node_modules/.bin/vitest run src/app/api/saved-gigs/route.test.ts
  • ./node_modules/.bin/prettier --check src/app/api/saved-gigs/route.ts src/app/api/saved-gigs/route.test.ts
  • ./node_modules/.bin/eslint src/app/api/saved-gigs/route.ts src/app/api/saved-gigs/route.test.ts
  • npm run type-check
  • git diff --cached --check

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 23, 2026

Greptile Summary

This PR guards POST /api/saved-gigs and DELETE /api/saved-gigs against malformed request bodies by introducing a parseJsonBody helper that catches JSON parse errors before they reach Zod validation or Supabase. The change also includes cosmetic reformatting of existing NextResponse.json calls and a new regression test file.

  • parseJsonBody helper — wraps request.json() in a try/catch and returns a discriminated-union result; callers short-circuit with a 400 when parsed.response is set.
  • POST + DELETE handlers — replaced the bare await request.json() with the new helper, keeping the existing Zod validation and Supabase paths unchanged.
  • Tests — two new cases verify that a truncated-JSON body ("{") returns { error: "Invalid JSON body" } with status 400 and that supabase.from is never invoked.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to intercepting JSON parse failures before they reach Zod or Supabase, with no modifications to the auth, validation, or database write paths.

The introduced helper is small and its logic is straightforward: it wraps request.json() and returns a discriminated union. Both handlers correctly guard on the error branch before proceeding. The formatting-only changes to the rest of the file carry no functional risk.

No files require special attention.

Important Files Changed

Filename Overview
src/app/api/saved-gigs/route.ts Extracts parseJsonBody helper that returns a discriminated union, then wires it into POST and DELETE before Zod validation; remaining diff is purely cosmetic reformatting.
src/app/api/saved-gigs/route.test.ts New test file with two regression tests (POST + DELETE) confirming malformed JSON returns 400 and never reaches Supabase; mocks auth and the Supabase client correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming Request] --> B[getAuthContext]
    B -- no auth --> C[401 Unauthorized]
    B -- authenticated --> D[parseJsonBody]
    D -- parse error --> E[400 Invalid JSON body]
    D -- valid JSON --> F[Zod safeParse]
    F -- invalid schema --> G[400 Validation error]
    F -- valid schema --> H[Supabase query]
    H -- db error --> I[400 DB error]
    H -- success --> J[200 / 201 Response]
Loading

Reviews (3): Last reviewed commit: "Handle malformed saved gig bodies" | Re-trigger Greptile

Comment on lines +24 to +50
describe("saved gigs invalid JSON handling", () => {
beforeEach(() => {
vi.clearAllMocks();
mockGetAuthContext.mockResolvedValue({
user: { id: "user-1" },
supabase: mockSupabase,
});
});

it("returns 400 for malformed POST bodies without querying Supabase", async () => {
const res = await POST(makeRequest("POST", "{"));
const body = await res.json();

expect(res.status).toBe(400);
expect(body.error).toBe("Invalid JSON body");
expect(mockFrom).not.toHaveBeenCalled();
});

it("returns 400 for malformed DELETE bodies without querying Supabase", async () => {
const res = await DELETE(makeRequest("DELETE", "{"));
const body = await res.json();

expect(res.status).toBe(400);
expect(body.error).toBe("Invalid JSON body");
expect(mockFrom).not.toHaveBeenCalled();
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Test suite covers only one error case

The two tests verify that malformed JSON returns 400 and skips Supabase, which is the targeted regression. However, there is no test for valid JSON that fails Zod validation (e.g., a missing or non-UUID gig_id) nor for the normal success path (valid body → Supabase insert/delete called). The route.test.ts is a new file, so this is the only test coverage that exists for these handlers; a subsequent regression in Zod validation or the Supabase write path would go undetected.

Comment on lines +9 to +17
async function parseJsonBody(request: NextRequest) {
try {
return { body: await request.json() };
} catch {
return {
response: NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }),
};
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Broad catch swallows non-parse errors

request.json() can throw for reasons beyond a SyntaxError (e.g., a body-read interruption). The bare catch {} maps all of those to a 400 "Invalid JSON body", which misrepresents infrastructure-level failures as client errors. Narrowing the catch to SyntaxError (or re-throwing anything else) would keep the 400 semantically accurate and let genuine 500-class errors surface to the outer handler.

Suggested change
async function parseJsonBody(request: NextRequest) {
try {
return { body: await request.json() };
} catch {
return {
response: NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }),
};
}
}
async function parseJsonBody(request: NextRequest) {
try {
return { body: await request.json() };
} catch (err) {
if (err instanceof SyntaxError) {
return {
response: NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }),
};
}
throw err;
}
}

@ralyodio ralyodio closed this May 23, 2026
@ralyodio ralyodio reopened this May 23, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 23, 2026

Want your agent to iterate on Greptile's feedback? Try greploops.

@ralyodio ralyodio closed this May 23, 2026
@ralyodio ralyodio reopened this May 23, 2026
@ralyodio ralyodio merged commit c0465b0 into profullstack:master May 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: saved gigs API returns 500 on malformed JSON

2 participants