-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Error handling fix #5314
Error handling fix #5314
Conversation
🦋 Changeset detectedLatest commit: 83c6252 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Alright, I got completely carried away with this PR, but I think it's done now. Thank you @cdcarson for getting the ball rolling, and thank you @georgecrawford for #4032 — apologies, I totally dropped the ball on that PR due to other things getting in the way. In addition to turning thrown errors into JSON responses, this PR now does the same thing for explicitly returned errors. If the // post handler can return validation errors
export function post({ request, locals }) {
if (!locals.user) {
// fatal error — results in error page
return {
status: 401,
body: new Error('show yourself, coward')
};
}
if (!locals.user.mojo < 50) {
// fatal error — results in error page
return {
status: 403,
body: new Error('insufficient mojo')
};
}
const [errors, item] = await create_item(await request.formData());
if (errors) {
// validation errors — mixed with GET props and rendered
return {
status: 400,
body: { errors }
};
}
// great success
return {
body: { item }
};
} Previously, the only way to trigger the error page in the first two cases (the 401 and 403) would have been to throw the error, which has two problems:
Turning errors into JSON involves being slightly opinionated. The Since all this requires documentation, and since it was a bit awkward to fit into the existing docs, I overhauled the relevant section. |
Thanks @Rich-Harris. (I especially enjoyed some of your commit comments.) This looks good. |
@@ -481,6 +493,17 @@ async function load_shadow_data(route, event, options, prerender) { | |||
add_cookies(/** @type {string[]} */ (data.cookies), headers); | |||
data.status = status; | |||
|
|||
if (body instanceof Error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could refactor this into a reusable function since the other new code added here does the same thing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feels like unnecessary indirection to me — you add function call overhead, a new utility somewhere in the codebase, an import declaration per consuming module... you shave off a few characters at the callsite but make the codebase larger and arguably less easy to read
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, you're talking about the whole block, not just the highlighted line. thought you mean is_error(body)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is one of those cases where you basically can't DRY it out, because of the control flow (i.e. mutating an existing object and conditionally returning it). Or rather you can, but the resulting function is larger than the code you saved by deduplicating. i reckon it's probably not worth it
* @param {(error: Error) => string | undefined} get_stack | ||
*/ | ||
export function serialize_error(error, get_stack) { | ||
return JSON.stringify(clone_error(error, get_stack)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it could use a comment explaining why we're doing a clone
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
@@ -19,3 +19,55 @@ export function to_headers(object) { | |||
|
|||
return headers; | |||
} | |||
|
|||
/** | |||
* @param {string} accept |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might not hurt to add descriptions for these two. I guess it's the accept header and types that we're okay using or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
Great stuff @Rich-Harris - thanks so much for getting to this, and no worries for the delay. I was going for longest-open-PR 🥇 ! |
This fixes #4802, which was previously attempted in #4032.
Currently, when an error is thrown from an endpoint, the server ignores whether the request was the result of a
fetch
call from the client side. There are two cases where this matters: (1) a get request from the client router and (2) afetch
request from userland client code.__error.svelte
rather than the error as JSON.__error.svelte
ends up getting hydrated with a default error rather than the thrown error.Two changes, as light-handed as possible:
In packages/kit/src/runtime/server/index.js we check for the two cases above, and if applicable, return the error as JSON. NB: Rather than worrying about serializing potentially complex or sensitive errors, it just returns an
Error
-like object{message: string}
. IMO, this will take care of most userland use cases (where you just want to show the user what went wrong.) It doesn't preclude folks from throwing more complex error structures as JSON.Additionally, a minor change was necessary in packages/kit/src/runtime/client/client.js.
There are three addition tests that cover off on the scenarios mentioned above. All the existing relevant tests seem to be passing, but I'm not sure how much coverage there was to begin with.
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
Tests
pnpm test
and lint the project withpnpm lint
andpnpm check
Changesets
pnpm changeset
and following the prompts. All changesets should bepatch
until SvelteKit 1.0