From 5e5b7fdc726df5634d810383104df4b325effe41 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 25 Oct 2024 23:18:19 +0200 Subject: [PATCH 01/18] remove invisible character --- packages/openapi-fetch/test/common/response.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-fetch/test/common/response.test.ts b/packages/openapi-fetch/test/common/response.test.ts index 7a3aacf39..ac7fe1b6b 100644 --- a/packages/openapi-fetch/test/common/response.test.ts +++ b/packages/openapi-fetch/test/common/response.test.ts @@ -65,7 +65,7 @@ describe("response", () => { } }); - test("returns union for mismatched errors", async () => { + test("returns union for mismatched errors", async () => { const client = createObservedClient(); const result = await client.GET("/mismatched-errors"); if (result.data) { From de4b652108c8a8ee7119580c0f0b0525169b432d Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 26 Oct 2024 00:23:50 +0200 Subject: [PATCH 02/18] return status from fetch --- packages/openapi-fetch/src/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/openapi-fetch/src/index.js b/packages/openapi-fetch/src/index.js index 1b8502c6f..71d3d1421 100644 --- a/packages/openapi-fetch/src/index.js +++ b/packages/openapi-fetch/src/index.js @@ -152,16 +152,16 @@ export default function createClient(clientOptions) { // handle empty content if (response.status === 204 || response.headers.get("Content-Length") === "0") { - return response.ok ? { data: undefined, response } : { error: undefined, response }; + return response.ok ? { data: undefined, response, status: response.status } : { error: undefined, response, status: response.status }; } // parse response (falling back to .text() when necessary) if (response.ok) { // if "stream", skip parsing entirely if (parseAs === "stream") { - return { data: response.body, response }; + return { data: response.body, response, status: response.status }; } - return { data: await response[parseAs](), response }; + return { data: await response[parseAs](), response, status: response.status }; } // handle errors @@ -171,7 +171,7 @@ export default function createClient(clientOptions) { } catch { // noop } - return { error, response }; + return { error, response, status: response.status }; } return { From 7ce026ec073753c3666a84997e979268114883e1 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 26 Oct 2024 00:24:03 +0200 Subject: [PATCH 03/18] type narrowing from status --- packages/openapi-fetch/src/index.d.ts | 34 +++++++++++++------ .../openapi-typescript-helpers/index.d.ts | 13 +++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index a7b26a1be..9f5a15670 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -9,6 +9,10 @@ import type { ResponseObjectMap, RequiredKeysOf, SuccessResponse, + GetResponseContent, + ErrorStatus, + OkStatus, + OpenApiStatusToHttpStatus, } from "openapi-typescript-helpers"; /** Options for each client instance */ @@ -98,17 +102,25 @@ export type RequestBodyOption = OperationRequestBodyContent extends never export type FetchOptions = RequestOptions & Omit; -export type FetchResponse, Options, Media extends MediaType> = - | { - data: ParseAsResponse, Media>, Options>; - error?: never; - response: Response; - } - | { - data?: never; - error: ErrorResponse, Media>; - response: Response; - }; +export type FetchResponse< + T extends Record, + Options, + Media extends MediaType, + TStatus extends keyof ResponseObjectMap = keyof ResponseObjectMap, +> = { + [S in TStatus]: { + response: Response; + status: OpenApiStatusToHttpStatus; + } & (S extends OkStatus + ? { + data: ParseAsResponse, Media, S>, Options>; + error: never; + } + : { + data: never; + error: GetResponseContent, Media, S>; + }); +}[TStatus]; export type RequestOptions = ParamsOption & RequestBodyOption & { diff --git a/packages/openapi-typescript-helpers/index.d.ts b/packages/openapi-typescript-helpers/index.d.ts index 1715c07cc..ce1096afc 100644 --- a/packages/openapi-typescript-helpers/index.d.ts +++ b/packages/openapi-typescript-helpers/index.d.ts @@ -7,6 +7,19 @@ export type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX"; // biome-ignore format: keep on one line export type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | '5XX' | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | '4XX' | "default"; +/** + * 'default' returns every status code + * '2XX' returns all 2XX status codes + * '4XX' returns all 4XX status codes + * '5XX' returns all 5XX status codes + */ +export type OpenApiStatusToHttpStatus = + Status extends number ? Status : + Status extends "default" ? number : + Status extends "2XX" ? Exclude : + Status extends "4XX" | "5XX" ? Exclude : + never; + /** Get a union of OK Statuses */ export type OKStatusUnion = FilterKeys; From af40b6185982dea46ac61c12c5495248b9be3d6c Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 26 Oct 2024 00:51:55 +0200 Subject: [PATCH 04/18] fallback to undefined | never --- packages/openapi-fetch/src/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index 9f5a15670..1a9cac315 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -114,10 +114,10 @@ export type FetchResponse< } & (S extends OkStatus ? { data: ParseAsResponse, Media, S>, Options>; - error: never; + error?: never; } : { - data: never; + data?: never; error: GetResponseContent, Media, S>; }); }[TStatus]; From 650c88b9c3054c07a53650166fbd281ff6cfe2f5 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 26 Oct 2024 00:52:03 +0200 Subject: [PATCH 05/18] add test for type narrowing --- .../never-response/never-response.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/openapi-fetch/test/never-response/never-response.test.ts b/packages/openapi-fetch/test/never-response/never-response.test.ts index 43cdb9a4b..fa865930d 100644 --- a/packages/openapi-fetch/test/never-response/never-response.test.ts +++ b/packages/openapi-fetch/test/never-response/never-response.test.ts @@ -140,4 +140,36 @@ describe("GET", () => { expect(data).toBeUndefined(); expect(error).toBe("Unauthorized"); }); + + test("type narrowing on status", async () => { + const mockData = { + id: 123, + title: "My Post", + }; + + let actualPathname = ""; + const client = createObservedClient({}, async (req) => { + actualPathname = new URL(req.url).pathname; + return Response.json(mockData); + }); + + const { data, error, response, status } = await client.GET("/posts/{id}", { + params: { path: { id: 123 } }, + }); + + if (status === 200) { + // @ts-expect-error FIXME: The '200' will never be undefined + assertType(data); + } else if (status === 204) { + assertType(data); + } else if (status === 400) { + assertType(error); + } else if (status === 500) { + // @ts-expect-error FIXME: The 'default' response should be excluded + assertType(error); + } else { + // @ts-expect-error FIXME: The '500' has already been caught so the undefined should be excluded + assertType(error); + } + }); }); From 6c405f8610e70576f4e0431025215d052dce0d80 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 26 Oct 2024 00:52:10 +0200 Subject: [PATCH 06/18] lint-fix --- packages/openapi-fetch/src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/openapi-fetch/src/index.js b/packages/openapi-fetch/src/index.js index 71d3d1421..c0f4712d7 100644 --- a/packages/openapi-fetch/src/index.js +++ b/packages/openapi-fetch/src/index.js @@ -152,7 +152,9 @@ export default function createClient(clientOptions) { // handle empty content if (response.status === 204 || response.headers.get("Content-Length") === "0") { - return response.ok ? { data: undefined, response, status: response.status } : { error: undefined, response, status: response.status }; + return response.ok + ? { data: undefined, response, status: response.status } + : { error: undefined, response, status: response.status }; } // parse response (falling back to .text() when necessary) From aea776ab42094c428150564e18b8409db15b1f6f Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 5 Nov 2024 10:58:43 +0100 Subject: [PATCH 07/18] Ensure default response type gets parsed properly --- packages/openapi-fetch/src/index.d.ts | 32 ++- .../test/common/response.test.ts | 15 +- .../never-response/never-response.test.ts | 11 +- packages/openapi-fetch/test/types.test.ts | 183 +++++++++++++++++- .../openapi-typescript-helpers/index.d.ts | 19 +- 5 files changed, 223 insertions(+), 37 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index 1a9cac315..19f787faa 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -102,25 +102,23 @@ export type RequestBodyOption = OperationRequestBodyContent extends never export type FetchOptions = RequestOptions & Omit; -export type FetchResponse< - T extends Record, - Options, - Media extends MediaType, - TStatus extends keyof ResponseObjectMap = keyof ResponseObjectMap, -> = { - [S in TStatus]: { +export type FetchResponse, Options, Media extends MediaType> = { + [S in keyof ResponseObjectMap]: { response: Response; - status: OpenApiStatusToHttpStatus; - } & (S extends OkStatus - ? { - data: ParseAsResponse, Media, S>, Options>; - error?: never; + status: OpenApiStatusToHttpStatus>; + } & ( + | { + error: never; + data: S extends OkStatus ? ParseAsResponse, Media, S>, Options> : never; + } + | { + error: S extends ErrorStatus + ? ParseAsResponse, Media, S>, Options> + : never; + data: never; } - : { - data?: never; - error: GetResponseContent, Media, S>; - }); -}[TStatus]; + ); +}[keyof ResponseObjectMap]; export type RequestOptions = ParamsOption & RequestBodyOption & { diff --git a/packages/openapi-fetch/test/common/response.test.ts b/packages/openapi-fetch/test/common/response.test.ts index ac7fe1b6b..d916235c5 100644 --- a/packages/openapi-fetch/test/common/response.test.ts +++ b/packages/openapi-fetch/test/common/response.test.ts @@ -20,22 +20,24 @@ describe("response", () => { // 2. assert data is not undefined inside condition block if (result.data) { assertType>(result.data); - assertType(result.error); + // @ts-expect-error FIXME: This is a limitation within Typescript + assertType(result.error); } // 2b. inverse should work, too if (!result.error) { assertType>(result.data); - assertType(result.error); + assertType(result.error); } // 3. assert error is not undefined inside condition block if (result.error) { - assertType(result.data); + // @ts-expect-error FIXME: This is a limitation within Typescript + assertType(result.data); assertType>(result.error); } // 3b. inverse should work, too if (!result.data) { - assertType(result.data); + assertType(result.data); assertType>(result.error); } }); @@ -49,9 +51,8 @@ describe("response", () => { {}, ); - //@ts-expect-error impossible to determine data type for invalid path assertType(result.data); - assertType(result.error); + assertType(result.error); }); test("returns union for mismatched response", async () => { @@ -72,7 +73,7 @@ describe("response", () => { expectTypeOf(result.data).toEqualTypeOf(); expectTypeOf(result.data).toEqualTypeOf>(); } else { - expectTypeOf(result.data).toBeUndefined(); + expectTypeOf(result.data).toBeNever(); expectTypeOf(result.error).extract<{ code: number }>().toEqualTypeOf<{ code: number; message: string }>(); expectTypeOf(result.error).exclude<{ code: number }>().toEqualTypeOf(undefined); } diff --git a/packages/openapi-fetch/test/never-response/never-response.test.ts b/packages/openapi-fetch/test/never-response/never-response.test.ts index fa865930d..728bd35f0 100644 --- a/packages/openapi-fetch/test/never-response/never-response.test.ts +++ b/packages/openapi-fetch/test/never-response/never-response.test.ts @@ -153,22 +153,25 @@ describe("GET", () => { return Response.json(mockData); }); - const { data, error, response, status } = await client.GET("/posts/{id}", { + const { data, error, status } = await client.GET("/posts/{id}", { params: { path: { id: 123 } }, }); if (status === 200) { - // @ts-expect-error FIXME: The '200' will never be undefined assertType(data); + assertType(error); } else if (status === 204) { assertType(data); } else if (status === 400) { assertType(error); + } else if (status === 201) { + // Grabs the 'default' response + assertType(error); } else if (status === 500) { - // @ts-expect-error FIXME: The 'default' response should be excluded + assertType(data); assertType(error); } else { - // @ts-expect-error FIXME: The '500' has already been caught so the undefined should be excluded + // All other status codes are handles with the 'default' response assertType(error); } }); diff --git a/packages/openapi-fetch/test/types.test.ts b/packages/openapi-fetch/test/types.test.ts index a44e437eb..186cb8b49 100644 --- a/packages/openapi-fetch/test/types.test.ts +++ b/packages/openapi-fetch/test/types.test.ts @@ -1,5 +1,11 @@ import { assertType, describe, test } from "vitest"; -import type { ErrorResponse, GetResponseContent, OkStatus, SuccessResponse } from "openapi-typescript-helpers"; +import type { + ErrorResponse, + GetResponseContent, + OkStatus, + OpenApiStatusToHttpStatus, + SuccessResponse, +} from "openapi-typescript-helpers"; describe("types", () => { describe("GetResponseContent", () => { @@ -280,4 +286,179 @@ describe("types", () => { assertType({ error: "default application/json" }); }); }); + + describe("OpenApiStatusToHttpStatus", () => { + test("returns numeric status code", () => { + assertType>(200); + assertType>(200); + assertType>(204); + + assertType>( + // @ts-expect-error 200 is not a valid + 200, + ); + assertType>( + // @ts-expect-error 200 is not a valid + 200, + ); + + assertType>(404); + }); + + test("returns default response", () => { + type Status = OpenApiStatusToHttpStatus<"default", 200 | 204 | 206 | 404 | 500 | "default">; + assertType( + // @ts-expect-error 200 has been manually defined + 200, + ); + assertType( + // @ts-expect-error 204 has been manually defined + 204, + ); + assertType(201); + assertType(504); + }); + + test("returns 200 likes response", () => { + type Status = OpenApiStatusToHttpStatus<"2XX", 200 | 204 | 206 | 404 | 500 | "default">; + assertType(200); + assertType(201); + assertType(202); + assertType(203); + assertType(204); + assertType( + // @ts-expect-error 205 is not a valid 2XX status code + 205, + ); + assertType(206); + assertType(207); + assertType( + // @ts-expect-error '2XX' is not a numeric status code + "2XX", + ); + assertType( + // @ts-expect-error 205 is not a valid 2XX status code + 208, + ); + + assertType( + // @ts-expect-error '4XX' is not a numeric status code + "4XX", + ); + assertType( + // @ts-expect-error '5XX' is not a numeric status code + "5XX", + ); + }); + + test("returns error responses for 4XX", () => { + type Status = OpenApiStatusToHttpStatus<"4XX", 200 | 204 | 206 | 404 | 500 | "default">; + assertType(400); + assertType(401); + assertType(402); + assertType(403); + assertType(404); + assertType(405); + assertType(406); + assertType(407); + assertType(408); + assertType(409); + assertType(410); + assertType(411); + assertType(412); + assertType(413); + assertType(414); + assertType(415); + assertType(416); + assertType(417); + assertType(418); + assertType(500); + assertType(501); + assertType(502); + assertType(503); + assertType(504); + assertType(505); + assertType(506); + assertType(507); + assertType(508); + assertType( + // @ts-expect-error 509 is not a valid error status code + 509, + ); + assertType(510); + assertType(511); + + assertType( + // @ts-expect-error 200 is not a valid error status code + 200, + ); + assertType( + // @ts-expect-error '2XX' is not a numeric status code + "2XX", + ); + assertType( + // @ts-expect-error '4XX' is not a numeric status code + "4XX", + ); + assertType( + // @ts-expect-error '5XX' is not a numeric status code + "5XX", + ); + }); + + test("returns error responses for 5XX", () => { + type Status = OpenApiStatusToHttpStatus<"5XX", 200 | 204 | 206 | 404 | 500 | "default">; + assertType(400); + assertType(401); + assertType(402); + assertType(403); + assertType(404); + assertType(405); + assertType(406); + assertType(407); + assertType(408); + assertType(409); + assertType(410); + assertType(411); + assertType(412); + assertType(413); + assertType(414); + assertType(415); + assertType(416); + assertType(417); + assertType(418); + assertType(500); + assertType(501); + assertType(502); + assertType(503); + assertType(504); + assertType(505); + assertType(506); + assertType(507); + assertType(508); + assertType( + // @ts-expect-error 509 is not a valid error status code + 509, + ); + assertType(510); + assertType(511); + + assertType( + // @ts-expect-error 200 is not a valid error status code + 200, + ); + assertType( + // @ts-expect-error '2XX' is not a numeric status code + "2XX", + ); + assertType( + // @ts-expect-error '4XX' is not a numeric status code + "4XX", + ); + assertType( + // @ts-expect-error '5XX' is not a numeric status code + "5XX", + ); + }); + }); }); diff --git a/packages/openapi-typescript-helpers/index.d.ts b/packages/openapi-typescript-helpers/index.d.ts index ce1096afc..a79ea83c4 100644 --- a/packages/openapi-typescript-helpers/index.d.ts +++ b/packages/openapi-typescript-helpers/index.d.ts @@ -8,17 +8,20 @@ export type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX"; export type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | '5XX' | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | '4XX' | "default"; /** - * 'default' returns every status code + * 'default' returns every not explicitly defined status code * '2XX' returns all 2XX status codes * '4XX' returns all 4XX status codes * '5XX' returns all 5XX status codes */ -export type OpenApiStatusToHttpStatus = - Status extends number ? Status : - Status extends "default" ? number : - Status extends "2XX" ? Exclude : - Status extends "4XX" | "5XX" ? Exclude : - never; +export type OpenApiStatusToHttpStatus = Status extends number + ? Status + : Status extends "default" + ? Exclude, AllStatuses> + : Status extends "2XX" + ? Exclude + : Status extends "4XX" | "5XX" + ? Exclude + : never; /** Get a union of OK Statuses */ export type OKStatusUnion = FilterKeys; @@ -139,7 +142,7 @@ type GetResponseContent< T extends Record, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T, -> = ResponseCode extends keyof T +> = OpenApiStatusToHttpStatus extends number ? { [K in ResponseCode]: T[K]["content"] extends Record ? FilterKeys extends never From 17dd38a425cd9ff93c49f18215ad7193f4b6e60e Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 13:27:25 +0100 Subject: [PATCH 08/18] Remove Required --- packages/openapi-react-query/src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts index 082577536..f0728fff9 100644 --- a/packages/openapi-react-query/src/index.ts +++ b/packages/openapi-react-query/src/index.ts @@ -26,7 +26,7 @@ export type QueryOptionsFunction, Init extends MaybeOptionalInit, - Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types + Response extends FetchResponse, Options extends Omit< UseQueryOptions>, "queryKey" | "queryFn" @@ -43,7 +43,7 @@ export type UseQueryMethod>, Method extends HttpMethod, Path extends PathsWithMethod, Init extends MaybeOptionalInit, - Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types + Response extends FetchResponse, Options extends Omit< UseQueryOptions>, "queryKey" | "queryFn" @@ -60,7 +60,7 @@ export type UseSuspenseQueryMethod, Init extends MaybeOptionalInit, - Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types + Response extends FetchResponse, Options extends Omit< UseSuspenseQueryOptions>, "queryKey" | "queryFn" @@ -77,7 +77,7 @@ export type UseMutationMethod, Init extends MaybeOptionalInit, - Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types + Response extends FetchResponse, Options extends Omit, "mutationKey" | "mutationFn">, >( method: Method, From 9e4e0a0b1b0be0c7159b6dc9f5145582cad90634 Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 13:28:41 +0100 Subject: [PATCH 09/18] add status type test --- .../openapi-fetch/test/common/response.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/openapi-fetch/test/common/response.test.ts b/packages/openapi-fetch/test/common/response.test.ts index d916235c5..bed69258f 100644 --- a/packages/openapi-fetch/test/common/response.test.ts +++ b/packages/openapi-fetch/test/common/response.test.ts @@ -29,6 +29,20 @@ describe("response", () => { assertType(result.error); } + if (result.status === 200) { + assertType>(result.data); + assertType(result.error); + } + + if (result.status === 500) { + assertType(result.data); + assertType(result.error); + } + + // @ts-expect-error 204 is not defined in the schema + if (result.status === 204) { + } + // 3. assert error is not undefined inside condition block if (result.error) { // @ts-expect-error FIXME: This is a limitation within Typescript From da4130381994eddf8ce965ca2fa760c1f9400356 Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 13:28:53 +0100 Subject: [PATCH 10/18] simplify type --- packages/openapi-fetch/src/index.d.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index 19f787faa..f2061f7e1 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -106,18 +106,9 @@ export type FetchResponse, Options, Media [S in keyof ResponseObjectMap]: { response: Response; status: OpenApiStatusToHttpStatus>; - } & ( - | { - error: never; - data: S extends OkStatus ? ParseAsResponse, Media, S>, Options> : never; - } - | { - error: S extends ErrorStatus - ? ParseAsResponse, Media, S>, Options> - : never; - data: never; - } - ); + data: S extends OkStatus ? ParseAsResponse, Media, S>, Options> : never; + error: S extends ErrorStatus ? ParseAsResponse, Media, S>, Options> : never; + }; }[keyof ResponseObjectMap]; export type RequestOptions = ParamsOption & From c11622d200c048e93d758b482a35d1221bd3b564 Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 13:43:25 +0100 Subject: [PATCH 11/18] remove unused imports --- packages/openapi-fetch/src/index.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index f2061f7e1..29d3e9891 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -1,5 +1,4 @@ import type { - ErrorResponse, FilterKeys, HttpMethod, IsOperationRequestBodyOptional, @@ -8,7 +7,6 @@ import type { PathsWithMethod, ResponseObjectMap, RequiredKeysOf, - SuccessResponse, GetResponseContent, ErrorStatus, OkStatus, From a969c133ca629f2d1a01ddf5079a386e72c27bd1 Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 13:44:25 +0100 Subject: [PATCH 12/18] cleanup GetResponseContent --- .../openapi-typescript-helpers/index.d.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/openapi-typescript-helpers/index.d.ts b/packages/openapi-typescript-helpers/index.d.ts index a79ea83c4..4166921ee 100644 --- a/packages/openapi-typescript-helpers/index.d.ts +++ b/packages/openapi-typescript-helpers/index.d.ts @@ -142,17 +142,15 @@ type GetResponseContent< T extends Record, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T, -> = OpenApiStatusToHttpStatus extends number - ? { - [K in ResponseCode]: T[K]["content"] extends Record - ? FilterKeys extends never - ? T[K]["content"] - : FilterKeys - : K extends keyof T - ? T[K]["content"] - : never; - }[ResponseCode] - : never; +> = { + [K in ResponseCode]: T[K]["content"] extends Record + ? FilterKeys extends never + ? T[K]["content"] + : FilterKeys + : K extends keyof T + ? T[K]["content"] + : never; +}[ResponseCode]; /** * Return all 5XX and 4XX responses (in that order) from a Response Object Map From a3422b2ad06db274ced3d0b2e759aed2820f752d Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 13:44:39 +0100 Subject: [PATCH 13/18] fix: GetResponseContent not being exported --- packages/openapi-typescript-helpers/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-typescript-helpers/index.d.ts b/packages/openapi-typescript-helpers/index.d.ts index 4166921ee..dc6ad962a 100644 --- a/packages/openapi-typescript-helpers/index.d.ts +++ b/packages/openapi-typescript-helpers/index.d.ts @@ -138,7 +138,7 @@ export type SuccessResponse< Media extends MediaType = MediaType, > = GetResponseContent; -type GetResponseContent< +export type GetResponseContent< T extends Record, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T, From c4f66cea766e0c1c82f9d5a0b22eb00ad8ddc340 Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 13:47:49 +0100 Subject: [PATCH 14/18] do not use ParseAsResponse for error --- packages/openapi-fetch/src/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index 29d3e9891..7594bbfee 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -105,7 +105,7 @@ export type FetchResponse, Options, Media response: Response; status: OpenApiStatusToHttpStatus>; data: S extends OkStatus ? ParseAsResponse, Media, S>, Options> : never; - error: S extends ErrorStatus ? ParseAsResponse, Media, S>, Options> : never; + error: S extends ErrorStatus ? GetResponseContent, Media, S> : never; }; }[keyof ResponseObjectMap]; From 8fcfc5747b56f4cd332599dd9bb0cbb4f279583a Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 13:54:50 +0100 Subject: [PATCH 15/18] changeset --- .changeset/rare-grapes-explode.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/rare-grapes-explode.md diff --git a/.changeset/rare-grapes-explode.md b/.changeset/rare-grapes-explode.md new file mode 100644 index 000000000..ceb8ce429 --- /dev/null +++ b/.changeset/rare-grapes-explode.md @@ -0,0 +1,7 @@ +--- +"openapi-typescript-helpers": minor +"openapi-react-query": minor +"openapi-fetch": minor +--- + +add `status` to openapi-fetch & type narrowing for `data` and `error` based on `status` From 21c7f26927c467a56b708ae78e5718207d953e1b Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 14:07:27 +0100 Subject: [PATCH 16/18] add status docs --- docs/openapi-fetch/index.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/openapi-fetch/index.md b/docs/openapi-fetch/index.md index 844eb9dbc..686ab132b 100644 --- a/docs/openapi-fetch/index.md +++ b/docs/openapi-fetch/index.md @@ -30,6 +30,7 @@ const client = createClient({ baseUrl: "https://myapi.dev/v1/" }); const { data, // only present if 2XX response error, // only present if 4XX or 5XX response + status, // HTTP Status code } = await client.GET("/blogposts/{post_id}", { params: { path: { post_id: "123" }, @@ -47,6 +48,8 @@ await client.PUT("/blogposts", { `data` and `error` are typechecked and expose their shapes to Intellisense in VS Code (and any other IDE with TypeScript support). Likewise, the request `body` will also typecheck its fields, erring if any required params are missing, or if there’s a type mismatch. +The `status` property is also available and contains the HTTP status code of the response (`response.status`). This property is useful for handling different status codes in your application and narrowing down the `data` and `error` properties. + `GET()`, `PUT()`, `POST()`, etc. are thin wrappers around the native [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) (which you can [swap for any call](/openapi-fetch/api#create-client)). Notice there are no generics, and no manual typing. Your endpoint’s request and response were inferred automatically. This is a huge improvement in the type safety of your endpoints because **every manual assertion could lead to a bug**! This eliminates all of the following: @@ -158,16 +161,17 @@ The `POST()` request required a `body` object that provided all necessary [reque ### Response -All methods return an object with **data**, **error**, and **response**. +All methods return an object with **data**, **error**, **status** and **response**. ```ts -const { data, error, response } = await client.GET("/url"); +const { data, error, status, response } = await client.GET("/url"); ``` | Object | Response | | :--------- | :-------------------------------------------------------------------------------------------------------------------------- | | `data` | `2xx` response if OK; otherwise `undefined` | | `error` | `5xx`, `4xx`, or `default` response if not OK; otherwise `undefined` | +| `status` | HTTP status code of the response (`response.status`) | | `response` | [The original Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) which contains `status`, `headers`, etc. | ### Path-property style From 4f8192ac28d09e31f0d9716ef275a40c220585eb Mon Sep 17 00:00:00 2001 From: djordy Date: Thu, 28 Nov 2024 14:38:48 +0100 Subject: [PATCH 17/18] update `status` explanation with link to mozilla documentation --- docs/openapi-fetch/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/openapi-fetch/index.md b/docs/openapi-fetch/index.md index 686ab132b..b615bfdde 100644 --- a/docs/openapi-fetch/index.md +++ b/docs/openapi-fetch/index.md @@ -167,12 +167,12 @@ All methods return an object with **data**, **error**, **status** and **response const { data, error, status, response } = await client.GET("/url"); ``` -| Object | Response | -| :--------- | :-------------------------------------------------------------------------------------------------------------------------- | -| `data` | `2xx` response if OK; otherwise `undefined` | -| `error` | `5xx`, `4xx`, or `default` response if not OK; otherwise `undefined` | -| `status` | HTTP status code of the response (`response.status`) | -| `response` | [The original Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) which contains `status`, `headers`, etc. | +| Object | Response | +|:-----------|:------------------------------------------------------------------------------------------------------------------------------| +| `data` | `2xx` response if OK; otherwise `undefined` | +| `error` | `5xx`, `4xx`, or `default` response if not OK; otherwise `undefined` | +| `status` | The HTTP response status code of [the original response](https://developer.mozilla.org/en-US/docs/Web/API/Response/status) | +| `response` | [The original Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) which contains `status`, `headers`, etc. | ### Path-property style From 7aa73ca6f47ada1d4c870cd333438aea1213f09c Mon Sep 17 00:00:00 2001 From: Djordy Koert Date: Fri, 6 Dec 2024 15:53:00 +0100 Subject: [PATCH 18/18] Change version bump to patch Co-authored-by: Drew Powers --- .changeset/rare-grapes-explode.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/rare-grapes-explode.md b/.changeset/rare-grapes-explode.md index ceb8ce429..8c9cf1b4d 100644 --- a/.changeset/rare-grapes-explode.md +++ b/.changeset/rare-grapes-explode.md @@ -1,7 +1,7 @@ --- -"openapi-typescript-helpers": minor -"openapi-react-query": minor -"openapi-fetch": minor +"openapi-typescript-helpers": patch +"openapi-react-query": patch +"openapi-fetch": patch --- add `status` to openapi-fetch & type narrowing for `data` and `error` based on `status`