fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #3427 from hey-api/copilot/fix-crash-report-issue

fix: surface actual error message when spec fetch fails

authored by

Lubos and committed by
GitHub
937486e6 b82d54bb

+121 -4
+6
.changeset/fluffy-turtles-explain.md
··· 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
+4 -1
packages/openapi-python/src/createClient.ts
··· 66 66 // if in watch mode, subsequent errors won't throw to gracefully handle 67 67 // cases where server might be reloading 68 68 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 + ); 70 73 } 71 74 72 75 return { arrayBuffer, resolvedInput };
+4 -1
packages/openapi-ts/src/createClient.ts
··· 66 66 // if in watch mode, subsequent errors won't throw to gracefully handle 67 67 // cases where server might be reloading 68 68 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 + ); 70 73 } 71 74 72 75 return { arrayBuffer, resolvedInput };
+103
packages/shared/src/__tests__/getSpec.test.ts
··· 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 + });
+4 -2
packages/shared/src/getSpec.ts
··· 105 105 106 106 response = request.response; 107 107 } catch (error) { 108 + const message = error instanceof Error ? error.message : String(error); 108 109 return { 109 110 error: 'not-ok', 110 - response: new Response(error instanceof Error ? error.message : String(error)), 111 + response: new Response(message, { status: 500 }), 111 112 }; 112 113 } 113 114 ··· 181 182 182 183 response = request.response; 183 184 } catch (error) { 185 + const message = error instanceof Error ? error.message : String(error); 184 186 return { 185 187 error: 'not-ok', 186 - response: new Response(error instanceof Error ? error.message : String(error)), 188 + response: new Response(message, { status: 500 }), 187 189 }; 188 190 } 189 191