QUESTPIE

Errors

What a failed call throws, what it carries, and how to pull a per-field message out of it.

View markdown

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

PropertyWhat it holds
statusThe HTTP status
statusTextThe HTTP status text
urlThe full URL that was called
messageThe server's message, or Request failed: and the status text
codeThe app's own error code
fieldErrorsAn array of { path, message }, plus translation keys
contextExtra 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.

CodeStatus
BAD_REQUEST, VALIDATION_ERROR400
UNAUTHORIZED401
FORBIDDEN403
NOT_FOUND404
CONFLICT409
PRECONDITION_FAILED412
UNPROCESSABLE_CONTENT422
INTERNAL_SERVER_ERROR, HOOK_ERROR500
NOT_IMPLEMENTED501

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

TypeThrown by
UploadErrorupload and uploadMany
RealtimeTopicRejectedErrorA subscription the server refused
RealtimeCrdtBindingRejectedErrorA 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.

On this page