Skip to content
This repository has been archived by the owner on May 22, 2023. It is now read-only.

Commit

Permalink
fix: fix request body type when optional (#48)
Browse files Browse the repository at this point in the history
* fix: fix request body type when optional

* fix: cleanup tests

* fix: missing mock responses
  • Loading branch information
nickcaballero authored May 11, 2023
1 parent d5538fc commit 63ebe48
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 4 deletions.
36 changes: 36 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,42 @@ describe('post()', () => {
// assert error is empty
expect(error).toBe(undefined);
});

it('request body type when optional', async() => {
fetchMocker.mockResponse(() => ({ status: 201, body: '{}' }));
const client = createClient<paths>();

// expect error on wrong body type
// @ts-expect-error
await client.post('/post/optional', { body: { error: true } })

// (no error)
await client.post('/post/optional', {
body: {
title: '',
publish_date: 3,
body: ''
}
})
})

it('request body type when optional inline', async() => {
fetchMocker.mockResponse(() => ({ status: 201, body: '{}' }));
const client = createClient<paths>();

// expect error on wrong body type
// @ts-expect-error
await client.post('/post/optional/inline', { body: { error: true } })

// (no error)
await client.post('/post/optional/inline', {
body: {
title: '',
publish_date: 3,
body: ''
}
})
})
});

describe('delete()', () => {
Expand Down
9 changes: 6 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,19 @@ export type FilterKeys<Obj, Matchers> = { [K in keyof Obj]: K extends Matchers ?
/** handle "application/json", "application/vnd.api+json", "appliacation/json;charset=utf-8" and more */
export type JSONLike = `${string}json${string}`;

// fetch types
// general purpose types
export type Params<O> = O extends { parameters: any } ? { params: NonNullable<O['parameters']> } : BaseParams;
export type RequestBodyObj<O> = O extends { requestBody: any } ? O['requestBody'] : never;
export type RequestBodyObj<O> = O extends { requestBody?: any } ? O['requestBody'] : never;
export type RequestBodyContent<O> = undefined extends RequestBodyObj<O> ? FilterKeys<NonNullable<RequestBodyObj<O>>, 'content'> | undefined : FilterKeys<RequestBodyObj<O>, 'content'>;
export type RequestBodyJSON<O> = FilterKeys<RequestBodyContent<O>, JSONLike> extends never ? FilterKeys<NonNullable<RequestBodyContent<O>>, JSONLike> | undefined : FilterKeys<RequestBodyContent<O>, JSONLike>;
export type RequestBody<O> = undefined extends RequestBodyJSON<O> ? { body?: RequestBodyJSON<O> } : { body: RequestBodyJSON<O> };
export type QuerySerializer<O> = (query: O extends { parameters: { query: any } } ? O['parameters']['query'] : Record<string, unknown>) => string;
export type FetchOptions<T> = Params<T> & RequestBody<T> & Omit<RequestInit, 'body'> & { querySerializer?: QuerySerializer<T> };
export type RequestOptions<T> = Params<T> & RequestBody<T> & { querySerializer?: QuerySerializer<T> };
export type Success<O> = FilterKeys<FilterKeys<O, OkStatus>, 'content'>;
export type Error<O> = FilterKeys<FilterKeys<O, ErrorStatus>, 'content'>;

// fetch types
export type FetchOptions<T> = RequestOptions<T> & Omit<RequestInit, 'body'>;
export type FetchResponse<T> =
| { data: T extends { responses: any } ? NonNullable<FilterKeys<Success<T['responses']>, JSONLike>> : unknown; error?: never; response: Response }
| { data?: never; error: T extends { responses: any } ? NonNullable<FilterKeys<Error<T['responses']>, JSONLike>> : unknown; response: Response };
Expand Down
31 changes: 31 additions & 0 deletions test/v1.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@ export interface paths {
};
};
};
"/post/optional": {
post: {
requestBody: components["requestBodies"]["CreatePostOptional"];
responses: {
201: components["responses"]["CreatePost"];
500: components["responses"]["Error"];
};
};
};
"/post/optional/inline": {
post: {
requestBody?: {
content: {
"application/json": components["schemas"]["Post"];
};
};
responses: {
201: components["responses"]["CreatePost"];
500: components["responses"]["Error"];
};
};
};
"/posts": {
get: {
responses: {
Expand Down Expand Up @@ -271,6 +293,15 @@ export interface components {
};
};
};
CreatePostOptional?: {
content: {
"application/json": {
title: string;
body: string;
publish_date: number;
};
};
};
CreateTag?: {
content: {
"application/json": {
Expand Down
42 changes: 41 additions & 1 deletion test/v1.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
openapi:
openapi: 3.0.3
info:
title: Test Specification
version: '3.1'
paths:
/comment:
Expand All @@ -19,6 +21,27 @@ paths:
$ref: '#/components/responses/CreatePost'
500:
$ref: '#/components/responses/Error'
/post/optional:
post:
requestBody:
$ref: '#/components/requestBodies/CreatePostOptional'
responses:
201:
$ref: '#/components/responses/CreatePost'
500:
$ref: '#/components/responses/Error'
/post/optional/inline:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Post'
responses:
201:
$ref: '#/components/responses/CreatePost'
500:
$ref: '#/components/responses/Error'
/posts:
get:
responses:
Expand Down Expand Up @@ -214,6 +237,23 @@ components:
- title
- body
- publish_date
CreatePostOptional:
required: false
content:
application/json:
schema:
type: object
properties:
title:
type: string
body:
type: string
publish_date:
type: number
required:
- title
- body
- publish_date
CreateTag:
content:
application/json:
Expand Down

0 comments on commit 63ebe48

Please sign in to comment.