diff --git a/.changeset/honest-parrots-compare.md b/.changeset/honest-parrots-compare.md new file mode 100644 index 000000000000..4c8442e71c9d --- /dev/null +++ b/.changeset/honest-parrots-compare.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Handle `throw error/redirect` in `+server.js` diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js index 338791b4d0b1..9d112214a478 100644 --- a/packages/kit/src/runtime/server/endpoint.js +++ b/packages/kit/src/runtime/server/endpoint.js @@ -1,3 +1,4 @@ +import { HttpError, Redirect } from '../../index/private.js'; import { check_method_names, method_not_allowed } from './utils.js'; /** @@ -29,16 +30,29 @@ export async function render_endpoint(event, route) { return method_not_allowed(mod, method); } - const response = await handler( - /** @type {import('types').RequestEvent>} */ (event) - ); - - if (!(response instanceof Response)) { - return new Response( - `Invalid response from route ${event.url.pathname}: handler should return a Response object`, - { status: 500 } + try { + const response = await handler( + /** @type {import('types').RequestEvent>} */ (event) ); - } - return response; + if (!(response instanceof Response)) { + return new Response( + `Invalid response from route ${event.url.pathname}: handler should return a Response object`, + { status: 500 } + ); + } + + return response; + } catch (error) { + if (error instanceof HttpError) { + return new Response(error.message, { status: error.status }); + } else if (error instanceof Redirect) { + return new Response(undefined, { + status: error.status, + headers: { Location: error.location } + }); + } else { + throw error; + } + } } diff --git a/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-error/+server.js b/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-error/+server.js new file mode 100644 index 000000000000..14c9aec1c9bb --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-error/+server.js @@ -0,0 +1,5 @@ +import { error } from '@sveltejs/kit'; + +export function GET() { + throw error(401, 'You shall not pass'); +} diff --git a/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-redirect/+server.js b/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-redirect/+server.js new file mode 100644 index 000000000000..26300742e98d --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-redirect/+server.js @@ -0,0 +1,5 @@ +import { redirect } from '@sveltejs/kit'; + +export function GET() { + throw redirect(302, '/'); +} diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index 45615bcb5630..7405120da05a 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -181,6 +181,26 @@ test.describe('Errors', () => { 'This is your custom error page saying: "Cannot read properties of undefined (reading \'toUpperCase\')"' ); }); + + test('throw error(..) in endpoint', async ({ page, read_errors }) => { + const res = await page.goto('/errors/endpoint-throw-error'); + + const error = read_errors('/errors/endpoint-throw-error'); + expect(error).toBe(undefined); + + expect(await res?.text()).toBe('You shall not pass'); + expect(res?.status()).toBe(401); + }); + + test('throw redirect(..) in endpoint', async ({ page, read_errors }) => { + const res = await page.goto('/errors/endpoint-throw-redirect'); + expect(res?.status()).toBe(200); // redirects are opaque to the browser + + const error = read_errors('/errors/endpoint-throw-redirect'); + expect(error).toBe(undefined); + + expect(await page.textContent('h1')).toBe('the answer is 42'); + }); }); test.describe('Load', () => {