Skip to content

Commit

Permalink
fix: narrow body type for HttpResponse.json() to be serializable …
Browse files Browse the repository at this point in the history
…json (#1838)
  • Loading branch information
kettanaito committed Nov 7, 2023
1 parent d49d48e commit 2af274c
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 8 deletions.
92 changes: 86 additions & 6 deletions src/core/HttpResponse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ it('creates a plain response', async () => {
expect(response.statusText).toBe('Moved Permanently')
expect(response.body).toBe(null)
expect(await response.text()).toBe('')
expect(Object.fromEntries(response.headers.entries())).toEqual({})
})

it('creates a text response', async () => {
Expand All @@ -19,15 +20,83 @@ it('creates a text response', async () => {
expect(response.statusText).toBe('Created')
expect(response.body).toBeInstanceOf(ReadableStream)
expect(await response.text()).toBe('hello world')
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-type': 'text/plain',
})
})

it('creates a json response', async () => {
const response = HttpResponse.json({ firstName: 'John' })
describe('HttpResponse.json()', () => {
it('creates a json response given an object', async () => {
const response = HttpResponse.json({ firstName: 'John' })

expect(response.status).toBe(200)
expect(response.statusText).toBe('OK')
expect(response.body).toBeInstanceOf(ReadableStream)
expect(await response.json()).toEqual({ firstName: 'John' })
expect(response.status).toBe(200)
expect(response.statusText).toBe('OK')
expect(response.body).toBeInstanceOf(ReadableStream)
expect(await response.json()).toEqual({ firstName: 'John' })
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-type': 'application/json',
})
})

it('creates a json response given an array', async () => {
const response = HttpResponse.json([1, 2, 3])

expect(response.status).toBe(200)
expect(response.statusText).toBe('OK')
expect(response.body).toBeInstanceOf(ReadableStream)
expect(await response.json()).toEqual([1, 2, 3])
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-type': 'application/json',
})
})

it('creates a json response given a plain string', async () => {
const response = HttpResponse.json(`"hello"`)

expect(response.status).toBe(200)
expect(response.statusText).toBe('OK')
expect(response.body).toBeInstanceOf(ReadableStream)
expect(await response.json()).toBe(`"hello"`)
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-type': 'application/json',
})
})

it('creates a json response given a number', async () => {
const response = HttpResponse.json(123)

expect(response.status).toBe(200)
expect(response.statusText).toBe('OK')
expect(response.body).toBeInstanceOf(ReadableStream)
expect(await response.json()).toBe(123)
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-type': 'application/json',
})
})

it('creates a json response given a json ReadableStream', async () => {
const encoder = new TextEncoder()
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(`{"firstName`))
controller.enqueue(encoder.encode(`":"John`))
controller.enqueue(encoder.encode(`"}`))
controller.close()
},
})
const response = HttpResponse.json(stream)

expect(response.status).toBe(200)
expect(response.statusText).toBe('OK')
expect(response.body).toBeInstanceOf(ReadableStream)
// A ReadableStream instance is not a valid body init
// for the "Response.json()" static method. It gets serialized
// into a plain object.
expect(await response.json()).toEqual({})
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-type': 'application/json',
})
})
})

it('creates an xml response', async () => {
Expand All @@ -37,6 +106,9 @@ it('creates an xml response', async () => {
expect(response.statusText).toBe('OK')
expect(response.body).toBeInstanceOf(ReadableStream)
expect(await response.text()).toBe('<user name="John" />')
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-type': 'text/xml',
})
})

it('creates an array buffer response', async () => {
Expand All @@ -49,6 +121,9 @@ it('creates an array buffer response', async () => {

const responseData = await response.arrayBuffer()
expect(responseData).toEqual(buffer.buffer)
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-length': '11',
})
})

it('creates a form data response', async () => {
Expand All @@ -62,4 +137,9 @@ it('creates a form data response', async () => {

const responseData = await response.formData()
expect(responseData.get('firstName')).toBe('John')
expect(Object.fromEntries(response.headers.entries())).toEqual({
'content-type': expect.stringContaining(
'multipart/form-data; boundary=----',
),
})
})
4 changes: 2 additions & 2 deletions src/core/HttpResponse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DefaultBodyType } from './handlers/RequestHandler'
import type { DefaultBodyType, JsonBodyType } from './handlers/RequestHandler'
import {
decorateResponse,
normalizeResponseInit,
Expand Down Expand Up @@ -62,7 +62,7 @@ export class HttpResponse extends Response {
* HttpResponse.json({ firstName: 'John' })
* HttpResponse.json({ error: 'Not Authorized' }, { status: 401 })
*/
static json<BodyType extends DefaultBodyType>(
static json<BodyType extends JsonBodyType>(
body?: BodyType | null,
init?: HttpResponseInit,
): StrictResponse<BodyType> {
Expand Down
8 changes: 8 additions & 0 deletions src/core/handlers/RequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export type DefaultBodyType =
| null
| undefined

export type JsonBodyType =
| Record<string, any>
| string
| number
| boolean
| null
| undefined

export interface RequestHandlerDefaultInfo {
header: string
}
Expand Down
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type {
RequestHandlerOptions,
DefaultBodyType,
DefaultRequestMultipartBody,
JsonBodyType,
} from './handlers/RequestHandler'

export type {
Expand Down

0 comments on commit 2af274c

Please sign in to comment.