From 58e637085d74ab17747d2dca0d8df93841ff29a2 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 17 May 2026 18:30:39 +0200 Subject: [PATCH] bugfix: don't JSON.parse empty 200/201 bodies (0.19.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $(addMangaToCollection crashed when the backend returned 201/200 with no body — the shared client only short-circuited 204. Now any empty body returns undefined.) Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/Cargo.lock | 2 +- backend/Cargo.toml | 2 +- frontend/package.json | 2 +- frontend/src/lib/api/client.test.ts | 16 +++++++++++++++- frontend/src/lib/api/client.ts | 10 +++++++++- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index d770042..503509c 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1033,7 +1033,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mangalord" -version = "0.19.0" +version = "0.19.1" dependencies = [ "anyhow", "argon2", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index f9d9f6b..30fce3e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mangalord" -version = "0.19.0" +version = "0.19.1" edition = "2021" [lib] diff --git a/frontend/package.json b/frontend/package.json index f30a238..507f546 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mangalord-frontend", - "version": "0.19.0", + "version": "0.19.1", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/lib/api/client.test.ts b/frontend/src/lib/api/client.test.ts index f544715..49e292c 100644 --- a/frontend/src/lib/api/client.test.ts +++ b/frontend/src/lib/api/client.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from 'vitest'; -import { ApiError } from './client'; +import { ApiError, request } from './client'; import { getManga } from './mangas'; describe('request error envelope parsing', () => { @@ -48,6 +48,20 @@ describe('request error envelope parsing', () => { expect(err.code).toBe('http_error'); }); + it('treats empty 200/201 bodies as undefined (no JSON.parse crash)', async () => { + // Regression: addMangaToCollection is typed `void` and the + // backend returns 201 (created) / 200 (already there) with + // no body. Without the empty-body short-circuit, `res.json()` + // would throw `JSON.parse: unexpected end of data`. + fetchSpy.mockResolvedValueOnce(new Response(null, { status: 201 })); + const created = await request('/v1/whatever', { method: 'POST' }); + expect(created).toBeUndefined(); + + fetchSpy.mockResolvedValueOnce(new Response(null, { status: 200 })); + const ok200 = await request('/v1/whatever', { method: 'POST' }); + expect(ok200).toBeUndefined(); + }); + it('falls back to http_error code when JSON has no error envelope', async () => { fetchSpy.mockResolvedValueOnce( new Response(JSON.stringify({ message: 'oops' }), { diff --git a/frontend/src/lib/api/client.ts b/frontend/src/lib/api/client.ts index fc8db66..a272be7 100644 --- a/frontend/src/lib/api/client.ts +++ b/frontend/src/lib/api/client.ts @@ -56,10 +56,18 @@ export async function request(path: string, init?: RequestInit): Promise { } throw new ApiError(res.status, code, message); } + // Any empty body (not just 204) returns undefined — the manga-add + // endpoint, for instance, signals create-vs-already-present via + // 201/200 with no body, and callers typed `request` would + // otherwise blow up on `res.json()` parsing an empty string. if (res.status === 204) { return undefined as T; } - return (await res.json()) as T; + const text = await res.text(); + if (!text) { + return undefined as T; + } + return JSON.parse(text) as T; } export type Manga = {