Errors
What a failed call throws, what it carries, and how to pull a per-field message out of it.
A form rejected your input. Which field, and what did it say? The client hands you that without you parsing anything.
The one you will catch
Every method that goes through fetch throws QuestpieClientError on a
non-2xx response. That is collections, globals, routes and search. Uploads are
the exception and throw UploadError instead.
import { QuestpieClientError } from "questpie/client";
try {
// `title` was declared as f.text(255).
await client.collections.posts.create({ title: tooLong, slug: "hello" });
} catch (err) {
if (err instanceof QuestpieClientError) {
err.status; // 400
err.isCode("VALIDATION_ERROR"); // true
err.getFieldError("title"); // { path: "title", message: "…" }
err.getFieldErrorsMap(); // { title: ["…"] }
}
}TypeScript already caught the missing keys and the wrong types. What is left here is what only the server knows: a rule your schema declared, a unique column, an access decision.
What it carries
| Property | What it holds |
|---|---|
status | The HTTP status |
statusText | The HTTP status text |
url | The full URL that was called |
message | The server's message, or Request failed: and the status text |
code | The app's own error code |
fieldErrors | An array of { path, message }, plus translation keys |
context | Extra detail the server attached, such as which access rule fired |
The first four are always there. code, fieldErrors and context need the
body to have been the app's own error envelope. A proxy returning its own HTML
leaves all three undefined.
Reading field errors
Three ways in, depending on what your form wants.
err.fieldErrors; // the raw array
err.getFieldError("email"); // the first entry for one path, or undefined
err.getFieldErrorsMap(); // { email: ["Invalid format"], password: ["Too short"] }path is dotted for nested values, so a bad title on the second nested post is
posts.1.title. getFieldErrorsMap() groups by path, which is usually what a
form wants. It returns {} when there were no field errors.
The codes
isCode(code) compares against these. Its argument is typed to the union
below, so a misspelt code is a compile error rather than a silent false.
| Code | Status |
|---|---|
BAD_REQUEST, VALIDATION_ERROR | 400 |
UNAUTHORIZED | 401 |
FORBIDDEN | 403 |
NOT_FOUND | 404 |
CONFLICT | 409 |
PRECONDITION_FAILED | 412 |
UNPROCESSABLE_CONTENT | 422 |
INTERNAL_SERVER_ERROR, HOOK_ERROR | 500 |
NOT_IMPLEMENTED | 501 |
VALIDATION_ERROR is the usual source of fieldErrors, but not the only one.
A BAD_REQUEST can carry them, and a unique-constraint clash arrives as a
CONFLICT with the offending column in fieldErrors. Branch on fieldErrors
rather than the code. CONFLICT also covers a stale expectedRevision, and
NOT_IMPLEMENTED is what a feature the collection never enabled returns.
The other error types
| Type | Thrown by |
|---|---|
UploadError | upload and uploadMany |
RealtimeTopicRejectedError | A subscription the server refused |
RealtimeCrdtBindingRejectedError | A collaborative document binding refused |
All of them are value exports from questpie/client, so instanceof works.
Next
Framework adapters covers mounting the handler these calls reach.