-
Notifications
You must be signed in to change notification settings - Fork 133
fix: surface a clear error on non-JSON API responses instead of crashing #1093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -169,7 +169,19 @@ export const createClient = (config: Config = {}): Client => { | |||||||||||||||||||||||
| // Some servers return 200 with no Content-Length and empty body. | ||||||||||||||||||||||||
| // response.json() would throw; read as text and parse if non-empty. | ||||||||||||||||||||||||
| const text = await response.text() | ||||||||||||||||||||||||
| data = text ? JSON.parse(text) : {} | ||||||||||||||||||||||||
| // altimate_change start — upstream_fix: guard JSON parse against non-JSON (HTML) response bodies | ||||||||||||||||||||||||
| // A 200 whose body is an HTML error page from a proxy/gateway/CDN otherwise crashes with a | ||||||||||||||||||||||||
| // raw "JSON Parse error: Unrecognized token '<'". Surface an actionable error instead. | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| data = text ? JSON.parse(text) : {} | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The guard only fires when Both clients default to
Driven against a local server, both clients resolve rather than reject for So this covers only the mislabeled-as-JSON case. A gateway that labels its error page honestly — most of them — still returns a "successful" result whose Also, the description says The cheapest way to close most of this is one line in code this PR doesn't touch. if (contentType === "text/html")
throw new Error("Request is not supported by this version of OpenCode Server (Server responded with text/html)")Exact equality misses if (contentType?.split(";")[0]?.trim().toLowerCase() === "text/html")(v1 has no such interceptor at all, so v1 has neither layer.) Pre-existing and outside the diff, raised only because it's load-bearing for the gap above and is a one-liner. |
||||||||||||||||||||||||
| } catch (cause) { | ||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||
| `Expected a JSON response but received ${response.headers.get("content-type") || "an unknown content type"} ` + | ||||||||||||||||||||||||
| `(HTTP ${response.status}). This is usually a proxy or gateway error page, not the API.`, | ||||||||||||||||||||||||
| { cause }, | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
Comment on lines
+178
to
+182
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The message names the content-type, which in the only case that fires is Because the guard runs only when That's the string in the PR's own verification table, and it reads as self-contradictory — the content-type is the one field that isn't discriminating here. The more concrete loss is that the error carries no request identity. Keeping the body out of the message is right — embedding a gateway page risks logging something sensitive — but it can live on
Suggested change
Same applies to the v1 copy at |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| // altimate_change end | ||||||||||||||||||||||||
|
Comment on lines
+172
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking: this hunk is deleted by the release build, so it never ships.
await createClient({
input: "./openapi.json",
output: { path: "./src/v2/gen", tsConfigPath: ..., clean: true },
...
})
This isn't "if someone runs generate". Two things worth flagging:
Note the asymmetry the description presents as equivalence: |
||||||||||||||||||||||||
| break | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| case "stream": | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undeclared v1 behavior change: an empty body used to throw, now returns
{}.v1's
case "json"wasawait response.json(), which throwsSyntaxError: Unexpected end of JSON inputon an empty body.text ? JSON.parse(text) : {}returns{}instead.The early return above only covers
status === 204andContent-Length === "0"(client.gen.ts:100-107), so a chunked 200 with an empty body and noContent-Lengthreaches this switch and now silently yields{}.Aligning v1 with v2 is probably the right call, but it's outside the stated scope and the PR body's matrix says v1 already returned
{}before — it didn't. One knock-on:responseValidator(client.gen.ts:150) now runs against{}for empty bodies where it was previously never reached.Either call it out in the description or split it into its own commit.