Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions src/adapter/web-standard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,21 +151,11 @@ export const WebStandardAdapter: ElysiaAdapter = {
composeError: {
mapResponseContext: '',
validationError:
`return new Response(` +
`error.message,` +
`{` +
`headers:Object.assign(` +
`{'content-type':'application/json'},` +
`set.headers` +
`),` +
`status:set.status` +
`}` +
`)`,
`set.headers['content-type']='application/json';` +
`return mapResponse(error.message,set)`,
unknownError:
`return new Response(` +
`error.message,` +
`{headers:set.headers,status:error.status??set.status??500}` +
`)`
`set.status=error.status??set.status??500;` +
`return mapResponse(error.message,set)`
},
listen() {
return () => {
Expand Down
82 changes: 40 additions & 42 deletions src/dynamic-handle.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import type { AnyElysia, CookieOptions } from './index'

import { TransformDecodeError } from '@sinclair/typebox/value'
import { TypeCheck } from './type-system'

import type { Context } from './context'
import type { ElysiaTypeCheck } from './schema'

import { parseCookie } from './cookies'
import {
ElysiaCustomStatusResponse,
ElysiaErrors,
status,
type ElysiaErrors,
NotFoundError,
status,
ValidationError
} from './error'

import type { AnyElysia, CookieOptions } from './index'
import { parseQuery } from './parse-query'

import { redirect, signCookie, StatusMap } from './utils'
import { parseCookie } from './cookies'

import type { ElysiaTypeCheck } from './schema'
import type { TypeCheck } from './type-system'
import type { Handler, LifeCycleStore, SchemaValidator } from './types'
import { redirect, StatusMap, signCookie } from './utils'

// JIT Handler
export type DynamicHandler = {
Expand Down Expand Up @@ -51,7 +45,7 @@ const injectDefaultValues = (
export const createDynamicHandler = (app: AnyElysia) => {
const { mapResponse, mapEarlyResponse } = app['~adapter'].handler

// @ts-ignore
// @ts-expect-error
const defaultHeader = app.setHeaders

return async (request: Request): Promise<Response> => {
Expand Down Expand Up @@ -108,7 +102,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
app.router.dynamic.find('ALL', path)

if (!handler) {
// @ts-ignore
// @ts-expect-error
context.query =
qi === -1 ? {} : parseQuery(url.substring(qi + 1))

Expand Down Expand Up @@ -137,7 +131,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
body = await request.arrayBuffer()
break

case 'multipart/form-data':
case 'multipart/form-data': {
body = {}

const form = await request.formData()
Expand All @@ -150,6 +144,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
}

break
}
}
} else {
let contentType
Expand Down Expand Up @@ -193,7 +188,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
break

case 'formdata':
case 'multipart/form-data':
case 'multipart/form-data': {
body = {}

const form =
Expand All @@ -208,8 +203,9 @@ export const createDynamicHandler = (app: AnyElysia) => {
}

break
}

default:
default: {
const parser = app['~parser'][hook]
if (parser) {
let temp = parser(
Expand All @@ -225,6 +221,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
}
}
break
}
}
else {
let temp = hook(context as any, contentType)
Expand Down Expand Up @@ -260,7 +257,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
body = await request.arrayBuffer()
break

case 'multipart/form-data':
case 'multipart/form-data': {
body = {}

const form = await request.formData()
Expand All @@ -274,6 +271,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
}

break
}
}
}
}
Expand All @@ -284,7 +282,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
context.body = body
context.params = handler?.params || undefined

// @ts-ignore
// @ts-expect-error
context.query = qi === -1 ? {} : parseQuery(url.substring(qi + 1))

context.headers = {}
Expand All @@ -294,43 +292,43 @@ export const createDynamicHandler = (app: AnyElysia) => {
const cookieMeta = {
domain:
app.config.cookie?.domain ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.domain,
expires:
app.config.cookie?.expires ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.expires,
httpOnly:
app.config.cookie?.httpOnly ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.httpOnly,
maxAge:
app.config.cookie?.maxAge ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.maxAge,
// @ts-ignore
// @ts-expect-error
path: app.config.cookie?.path ?? validator?.cookie?.config.path,
priority:
app.config.cookie?.priority ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.priority,
partitioned:
app.config.cookie?.partitioned ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.partitioned,
sameSite:
app.config.cookie?.sameSite ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.sameSite,
secure:
app.config.cookie?.secure ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.secure,
secrets:
app.config.cookie?.secrets ??
// @ts-ignore
// @ts-expect-error
validator?.cookie?.config.secrets,
// @ts-ignore
// @ts-expect-error
sign: app.config.cookie?.sign ?? validator?.cookie?.config.sign
} as CookieOptions & {
sign?: true | string | string[]
Expand Down Expand Up @@ -363,7 +361,7 @@ export const createDynamicHandler = (app: AnyElysia) => {

if (response instanceof Promise) response = await response

// @ts-ignore jusut in case
// @ts-expect-error just in case
if (response instanceof ElysiaCustomStatusResponse) {
const result = mapEarlyResponse(response, context.set)
if (result)
Expand Down Expand Up @@ -415,7 +413,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
typeof context.query[property] === 'string' &&
context.query[property]
) {
// @ts-ignore
// @ts-expect-error
context.query[property] =
context.query[property].split(',')
}
Expand Down Expand Up @@ -609,7 +607,7 @@ export const createDynamicHandler = (app: AnyElysia) => {
responseValidator.Clean(response)

const result = mapEarlyResponse(response, context.set)
// @ts-ignore
// @ts-expect-error
if (result !== undefined) return (context.response = result)
}
}
Expand Down Expand Up @@ -684,10 +682,12 @@ export const createDynamicErrorHandler = (app: AnyElysia) => {
const errorContext = Object.assign(context, { error, code: error.code })
errorContext.set = context.set

// @ts-expect-error
if (typeof error?.toResponse === 'function' &&
if (
// @ts-expect-error
typeof error?.toResponse === 'function' &&
!(error instanceof ValidationError) &&
!(error instanceof TransformDecodeError)) {
!(error instanceof TransformDecodeError)
) {
try {
// @ts-expect-error
let raw = error.toResponse()
Expand Down Expand Up @@ -725,12 +725,10 @@ export const createDynamicErrorHandler = (app: AnyElysia) => {
return mapResponse(context.response, context.set)
}

return new Response(
context.set.status = error.status ?? 500
return mapResponse(
typeof error.cause === 'string' ? error.cause : error.message,
{
headers: context.set.headers as any,
status: error.status ?? 500
}
context.set
)
}
}
107 changes: 107 additions & 0 deletions test/core/handle-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,111 @@ describe('Handle Error', () => {
expect(res.status).toBe(500)
expect(await res.text()).toBe('original error')
})

it('send set-cookie header when error is thrown', async () => {
const app = new Elysia().get('/', ({ cookie }) => {
cookie.session.value = 'test-session-id'
throw new Error('test error')
})

const res = await app.handle(req('/'))

expect(res.status).toBe(500)
expect(res.headers.get('set-cookie')).toContain('session=test-session-id')
})

it('send set-cookie header when response validation error occurs', async () => {
const app = new Elysia().get('/', ({ cookie }) => {
cookie.session.value = 'test-session-id'
return 'invalid response'
}, {
response: t.Number()
})

const res = await app.handle(req('/'))

expect(res.status).toBe(422)
expect(res.headers.get('set-cookie')).toContain('session=test-session-id')
})

it('send set-cookie header when error is thrown with onError hook', async () => {
const app = new Elysia()
.onError(({ error }) => {
return error.message
})
.get('/', ({ cookie }) => {
cookie.session.value = 'test-session-id'
throw new Error('custom error')
})

const res = await app.handle(req('/'))

expect(res.status).toBe(500)
expect(await res.text()).toBe('custom error')
expect(res.headers.get('set-cookie')).toContain('session=test-session-id')
})

it('send set-cookie header when NotFoundError is thrown', async () => {
const app = new Elysia().get('/', ({ cookie }) => {
cookie.session.value = 'test-session-id'
throw new NotFoundError()
})

const res = await app.handle(req('/'))

expect(res.status).toBe(404)
expect(res.headers.get('set-cookie')).toContain('session=test-session-id')
})

it('send set-cookie header when InternalServerError is thrown', async () => {
const app = new Elysia().get('/', ({ cookie }) => {
cookie.session.value = 'test-session-id'
throw new InternalServerError()
})

const res = await app.handle(req('/'))

expect(res.status).toBe(500)
expect(res.headers.get('set-cookie')).toContain('session=test-session-id')
})

it('send set-cookie header in AOT mode when error is thrown', async () => {
const app = new Elysia({ aot: true }).get('/', ({ cookie }) => {
cookie.session.value = 'test-session-id'
throw new Error('test error')
})

const res = await app.handle(req('/'))

expect(res.status).toBe(500)
expect(res.headers.get('set-cookie')).toContain('session=test-session-id')
})

it('preserve multiple cookies when error is thrown', async () => {
const app = new Elysia().get('/', ({ cookie }) => {
cookie.session.value = 'session-123'
cookie.user.value = 'user-456'
throw new Error('test error')
})

const res = await app.handle(req('/'))

expect(res.status).toBe(500)
const setCookie = res.headers.get('set-cookie')
expect(setCookie).toContain('session=session-123')
expect(setCookie).toContain('user=user-456')
})

it('send set-cookie header when error has custom headers', async () => {
const app = new Elysia().get('/', ({ cookie, set }) => {
cookie.session.value = 'test-session-id'
set.headers['x-custom'] = 'value'
throw new Error('test error')
})

const res = await app.handle(req('/'))

expect(res.headers.get('set-cookie')).toContain('session=test-session-id')
expect(res.headers.get('x-custom')).toBe('value')
})
})
Loading