-
-
Notifications
You must be signed in to change notification settings - Fork 470
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(openapi-fetch): Return union of possible responses (data & error) (…
…#1937) * Get all possible responses as a union * Add tests * lint fix * Add tests for standalone types * lint fix * changeset * Fix passing more keys (OkStatus) returning unknown * add test for non-existent media type * Update invalid path test * lint-fix * better @ts-expect-error scoping
- Loading branch information
1 parent
4e17f27
commit 06163a2
Showing
8 changed files
with
692 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"openapi-typescript-helpers": patch | ||
"openapi-fetch": patch | ||
--- | ||
|
||
client data & error now return a union of possible types |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
packages/openapi-fetch/test/never-response/never-response.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { assertType, describe, expect, test } from "vitest"; | ||
import { createObservedClient } from "../helpers.js"; | ||
import type { components, paths } from "./schemas/never-response.js"; | ||
|
||
describe("GET", () => { | ||
test("sends correct method", async () => { | ||
let method = ""; | ||
const client = createObservedClient<paths>({}, async (req) => { | ||
method = req.method; | ||
return Response.json({}); | ||
}); | ||
await client.GET("/posts"); | ||
expect(method).toBe("GET"); | ||
}); | ||
|
||
test("sends correct options, returns success", async () => { | ||
const mockData = { | ||
id: 123, | ||
title: "My Post", | ||
}; | ||
|
||
let actualPathname = ""; | ||
const client = createObservedClient<paths>({}, async (req) => { | ||
actualPathname = new URL(req.url).pathname; | ||
return Response.json(mockData); | ||
}); | ||
|
||
const { data, error, response } = await client.GET("/posts/{id}", { | ||
params: { path: { id: 123 } }, | ||
}); | ||
|
||
assertType<typeof mockData | undefined>(data); | ||
|
||
// assert correct URL was called | ||
expect(actualPathname).toBe("/posts/123"); | ||
|
||
// assert correct data was returned | ||
expect(data).toEqual(mockData); | ||
expect(response.status).toBe(200); | ||
|
||
// assert error is empty | ||
expect(error).toBeUndefined(); | ||
}); | ||
|
||
test("sends correct options, returns undefined on 204", async () => { | ||
let actualPathname = ""; | ||
const client = createObservedClient<paths>({}, async (req) => { | ||
actualPathname = new URL(req.url).pathname; | ||
return new Response(null, { status: 204 }); | ||
}); | ||
|
||
const { data, error, response } = await client.GET("/posts/{id}", { | ||
params: { path: { id: 123 } }, | ||
}); | ||
|
||
assertType<components["schemas"]["Post"] | undefined>(data); | ||
|
||
// assert correct URL was called | ||
expect(actualPathname).toBe("/posts/123"); | ||
|
||
// assert 204 to be transformed to empty object | ||
expect(data).toEqual({}); | ||
expect(response.status).toBe(204); | ||
|
||
// assert error is empty | ||
expect(error).toBeUndefined(); | ||
}); | ||
|
||
test("sends correct options, returns error", async () => { | ||
const mockError = { code: 404, message: "Post not found" }; | ||
|
||
let method = ""; | ||
let actualPathname = ""; | ||
const client = createObservedClient<paths>({}, async (req) => { | ||
method = req.method; | ||
actualPathname = new URL(req.url).pathname; | ||
return Response.json(mockError, { status: 404 }); | ||
}); | ||
|
||
const { data, error, response } = await client.GET("/posts/{id}", { | ||
params: { path: { id: 123 } }, | ||
}); | ||
|
||
assertType<typeof mockError | undefined>(error); | ||
|
||
// assert correct URL was called | ||
expect(actualPathname).toBe("/posts/123"); | ||
|
||
// assert correct method was called | ||
expect(method).toBe("GET"); | ||
|
||
// assert correct error was returned | ||
expect(error).toEqual(mockError); | ||
expect(response.status).toBe(404); | ||
|
||
// assert data is empty | ||
expect(data).toBeUndefined(); | ||
}); | ||
|
||
test("handles array-type responses", async () => { | ||
const client = createObservedClient<paths>({}, async () => Response.json([])); | ||
|
||
const { data } = await client.GET("/posts", { params: {} }); | ||
if (!data) { | ||
throw new Error("data empty"); | ||
} | ||
|
||
// assert array type (and only array type) was inferred | ||
expect(data.length).toBe(0); | ||
}); | ||
|
||
test("handles empty-array-type 204 response", async () => { | ||
let method = ""; | ||
let actualPathname = ""; | ||
const client = createObservedClient<paths>({}, async (req) => { | ||
method = req.method; | ||
actualPathname = new URL(req.url).pathname; | ||
return new Response(null, { status: 204 }); | ||
}); | ||
|
||
const { data } = await client.GET("/posts", { params: {} }); | ||
|
||
assertType<components["schemas"]["Post"][] | unknown[] | undefined>(data); | ||
|
||
// assert correct URL was called | ||
expect(actualPathname).toBe("/posts"); | ||
|
||
// assert correct method was called | ||
expect(method).toBe("GET"); | ||
|
||
// assert 204 to be transformed to empty object | ||
expect(data).toEqual({}); | ||
}); | ||
|
||
test("gracefully handles invalid JSON for errors", async () => { | ||
const client = createObservedClient<paths>({}, async () => new Response("Unauthorized", { status: 401 })); | ||
|
||
const { data, error } = await client.GET("/posts"); | ||
|
||
expect(data).toBeUndefined(); | ||
expect(error).toBe("Unauthorized"); | ||
}); | ||
}); |
142 changes: 142 additions & 0 deletions
142
packages/openapi-fetch/test/never-response/schemas/never-response.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/** | ||
* This file was auto-generated by openapi-typescript. | ||
* Do not make direct changes to the file. | ||
*/ | ||
|
||
export interface paths { | ||
"/posts": { | ||
parameters: { | ||
query?: never; | ||
header?: never; | ||
path?: never; | ||
cookie?: never; | ||
}; | ||
get: { | ||
parameters: { | ||
query?: never; | ||
header?: never; | ||
path?: never; | ||
cookie?: never; | ||
}; | ||
requestBody?: never; | ||
responses: { | ||
/** @description OK */ | ||
200: { | ||
headers: { | ||
[name: string]: unknown; | ||
}; | ||
content: { | ||
"application/json": components["schemas"]["Post"][]; | ||
}; | ||
}; | ||
/** @description No posts found, but it's OK */ | ||
204: { | ||
headers: { | ||
[name: string]: unknown; | ||
}; | ||
content: { | ||
"application/json": unknown[]; | ||
}; | ||
}; | ||
/** @description Unexpected error */ | ||
default: { | ||
headers: { | ||
[name: string]: unknown; | ||
}; | ||
content: { | ||
"application/json": components["schemas"]["Error"]; | ||
}; | ||
}; | ||
}; | ||
}; | ||
put?: never; | ||
post?: never; | ||
delete?: never; | ||
options?: never; | ||
head?: never; | ||
patch?: never; | ||
trace?: never; | ||
}; | ||
"/posts/{id}": { | ||
parameters: { | ||
query?: never; | ||
header?: never; | ||
path: { | ||
id: number; | ||
}; | ||
cookie?: never; | ||
}; | ||
get: { | ||
parameters: { | ||
query?: never; | ||
header?: never; | ||
path: { | ||
id: number; | ||
}; | ||
cookie?: never; | ||
}; | ||
requestBody?: never; | ||
responses: { | ||
/** @description OK */ | ||
200: { | ||
headers: { | ||
[name: string]: unknown; | ||
}; | ||
content: { | ||
"application/json": components["schemas"]["Post"]; | ||
}; | ||
}; | ||
/** @description No post found, but it's OK */ | ||
204: { | ||
headers: { | ||
[name: string]: unknown; | ||
}; | ||
content?: never; | ||
}; | ||
/** @description A weird error happened */ | ||
500: { | ||
headers: { | ||
[name: string]: unknown; | ||
}; | ||
content?: never; | ||
}; | ||
/** @description Unexpected error */ | ||
default: { | ||
headers: { | ||
[name: string]: unknown; | ||
}; | ||
content: { | ||
"application/json": components["schemas"]["Error"]; | ||
}; | ||
}; | ||
}; | ||
}; | ||
put?: never; | ||
post?: never; | ||
delete?: never; | ||
options?: never; | ||
head?: never; | ||
patch?: never; | ||
trace?: never; | ||
}; | ||
} | ||
export type webhooks = Record<string, never>; | ||
export interface components { | ||
schemas: { | ||
Error: { | ||
code: number; | ||
message: string; | ||
}; | ||
Post: { | ||
id: number; | ||
title: string; | ||
}; | ||
}; | ||
responses: never; | ||
parameters: never; | ||
requestBodies: never; | ||
headers: never; | ||
pathItems: never; | ||
} | ||
export type $defs = Record<string, never>; | ||
export type operations = Record<string, never>; |
Oops, something went wrong.