Skip to content

Commit 937486e

Browse files
authored
Merge pull request #3427 from hey-api/copilot/fix-crash-report-issue
fix: surface actual error message when spec fetch fails
2 parents b82d54b + 37dd92c commit 937486e

5 files changed

Lines changed: 121 additions & 4 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@hey-api/openapi-ts": patch
3+
"@hey-api/shared": patch
4+
---
5+
6+
**input**: fix: improve returned status code when spec fetch fails

packages/openapi-python/src/createClient.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ export async function createClient({
6666
// if in watch mode, subsequent errors won't throw to gracefully handle
6767
// cases where server might be reloading
6868
if (error && !_watches) {
69-
throw new Error(`Request failed with status ${response.status}: ${response.statusText}`);
69+
const text = await response.text().catch(() => '');
70+
throw new Error(
71+
`Request failed with status ${response.status}: ${text || response.statusText}`,
72+
);
7073
}
7174

7275
return { arrayBuffer, resolvedInput };

packages/openapi-ts/src/createClient.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ export async function createClient({
6666
// if in watch mode, subsequent errors won't throw to gracefully handle
6767
// cases where server might be reloading
6868
if (error && !_watches) {
69-
throw new Error(`Request failed with status ${response.status}: ${response.statusText}`);
69+
const text = await response.text().catch(() => '');
70+
throw new Error(
71+
`Request failed with status ${response.status}: ${text || response.statusText}`,
72+
);
7073
}
7174

7275
return { arrayBuffer, resolvedInput };
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import * as refParser from '@hey-api/json-schema-ref-parser';
2+
3+
import { getSpec } from '../getSpec';
4+
5+
vi.mock('@hey-api/json-schema-ref-parser', () => ({
6+
getResolvedInput: vi.fn(({ pathOrUrlOrSchema }: { pathOrUrlOrSchema: string }) => ({
7+
path: pathOrUrlOrSchema,
8+
schema: undefined,
9+
type: 'url',
10+
})),
11+
sendRequest: vi.fn(),
12+
}));
13+
14+
const mockSendRequest = vi.mocked(refParser.sendRequest);
15+
16+
describe('getSpec', () => {
17+
beforeEach(() => {
18+
vi.clearAllMocks();
19+
});
20+
21+
describe('URL input', () => {
22+
it('returns error with status 500 and error message when GET request throws an exception', async () => {
23+
mockSendRequest.mockRejectedValueOnce(new Error('fetch failed'));
24+
25+
const result = await getSpec({
26+
fetchOptions: undefined,
27+
inputPath: 'http://example.com/openapi.json',
28+
timeout: undefined,
29+
watch: { headers: new Headers() },
30+
});
31+
32+
expect(result.error).toBe('not-ok');
33+
expect(result.response!.status).toBe(500);
34+
expect(await result.response!.text()).toBe('fetch failed');
35+
});
36+
37+
it('returns error with status 500 and string message when non-Error is thrown during GET request', async () => {
38+
mockSendRequest.mockRejectedValueOnce('network unavailable');
39+
40+
const result = await getSpec({
41+
fetchOptions: undefined,
42+
inputPath: 'http://example.com/openapi.json',
43+
timeout: undefined,
44+
watch: { headers: new Headers() },
45+
});
46+
47+
expect(result.error).toBe('not-ok');
48+
expect(result.response!.status).toBe(500);
49+
expect(await result.response!.text()).toBe('network unavailable');
50+
});
51+
52+
it('returns error when GET response has status >= 300', async () => {
53+
mockSendRequest.mockResolvedValueOnce({
54+
response: new Response(null, { status: 404, statusText: 'Not Found' }),
55+
});
56+
57+
const result = await getSpec({
58+
fetchOptions: undefined,
59+
inputPath: 'http://example.com/openapi.json',
60+
timeout: undefined,
61+
watch: { headers: new Headers() },
62+
});
63+
64+
expect(result.error).toBe('not-ok');
65+
expect(result.response!.status).toBe(404);
66+
});
67+
68+
it('returns error with status 500 and error message when HEAD request throws an exception', async () => {
69+
mockSendRequest.mockRejectedValueOnce(new Error('connection refused'));
70+
71+
const result = await getSpec({
72+
fetchOptions: undefined,
73+
inputPath: 'http://example.com/openapi.json',
74+
timeout: undefined,
75+
watch: { headers: new Headers(), isHeadMethodSupported: true, lastValue: 'previous' },
76+
});
77+
78+
expect(result.error).toBe('not-ok');
79+
expect(result.response!.status).toBe(500);
80+
expect(await result.response!.text()).toBe('connection refused');
81+
});
82+
83+
it('returns arrayBuffer on successful GET', async () => {
84+
const content = '{"openapi":"3.0.0"}';
85+
const encoder = new TextEncoder();
86+
const buffer = encoder.encode(content).buffer as ArrayBuffer;
87+
88+
mockSendRequest.mockResolvedValueOnce({
89+
response: new Response(buffer, { status: 200 }),
90+
});
91+
92+
const result = await getSpec({
93+
fetchOptions: undefined,
94+
inputPath: 'http://example.com/openapi.json',
95+
timeout: undefined,
96+
watch: { headers: new Headers() },
97+
});
98+
99+
expect(result.error).toBeUndefined();
100+
expect(result.arrayBuffer).toBeDefined();
101+
});
102+
});
103+
});

packages/shared/src/getSpec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,10 @@ export async function getSpec({
105105

106106
response = request.response;
107107
} catch (error) {
108+
const message = error instanceof Error ? error.message : String(error);
108109
return {
109110
error: 'not-ok',
110-
response: new Response(error instanceof Error ? error.message : String(error)),
111+
response: new Response(message, { status: 500 }),
111112
};
112113
}
113114

@@ -181,9 +182,10 @@ export async function getSpec({
181182

182183
response = request.response;
183184
} catch (error) {
185+
const message = error instanceof Error ? error.message : String(error);
184186
return {
185187
error: 'not-ok',
186-
response: new Response(error instanceof Error ? error.message : String(error)),
188+
response: new Response(message, { status: 500 }),
187189
};
188190
}
189191

0 commit comments

Comments
 (0)