Error handling
Handle error responses of the Redmine API.
When Redmine answers with a status code that isn't a success response of the operation, the method throws a RestError with:
| Property | Description |
|---|---|
status | The HTTP status code as a string, e.g. "404" |
body | The response body: parsed JSON (e.g. the validation errors), the text (e.g. HTML) or undefined |
headers | The response headers |
request | The request, with its method and url |
response | The raw response |
message | e.g. Unexpected HTTP status code: 404 - HTTP 404 received for GET … |
Network errors (server not reachable, invalid certificate) are thrown by the runtime as its own RestError class from @typespec/ts-http-runtime, exported as RuntimeRestError. It has a code such as "ECONNREFUSED", but no status, so it isn't the exported RestError. See Network errors.
Status codes
| Status | Meaning in Redmine |
|---|---|
401 | Missing or invalid API key, or the REST API is disabled |
403 | The user isn't allowed to do this, e.g. an admin-only endpoint or an archived project |
404 | The resource doesn't exist, or the user can't see it (e.g. an issue of a private project) |
409 | The resource was changed in the meantime, e.g. a wiki page saved with an outdated version |
422 | Validation failed, the messages are in body.errors |
Checking the status
isRedmineRestError checks if an error is a RestError with the given status code. It compares the status as a number, so you don't have to convert the string:
import { isRedmineRestError } from "redmine-api-client";
try {
await client.issuesClient.createIssue({ issue: { projectId: 1, subject: "" } });
} catch (error) {
if (isRedmineRestError(error, 422)) {
console.error(error.body?.errors); // ["Subject cannot be blank"]
} else {
throw error;
}
}RestError is exported as well, for instanceof checks:
import { RestError } from "redmine-api-client";
if (error instanceof RestError) {
console.error(error.request.method, error.request.url, error.status);
}body is typed as any, as each status code has its own body. Only 422 responses have errors, so access them with optional chaining.
Network errors
Without a status code, isRedmineRestError matches error responses and network errors, e.g. to show one message for every failed request:
try {
await client.issuesClient.getIssue(1);
} catch (error) {
if (isRedmineRestError(error)) {
toast.error(`API error: ${error.message}`);
} else {
throw error;
}
}The narrowed error is a RestError or the runtime's error, so only their shared properties (name, message, request, response) are available without further checks. Use error instanceof RestError or error instanceof RuntimeRestError to tell them apart. With a status code, network errors never match, as they have no status.
The runtime retries network errors a few times with a backoff before throwing. Pass retryOptions: { maxRetries: 0 } to the client options to fail right away.
Not found
To treat a missing resource as undefined instead of an error:
const findIssue = async (id: number) => {
try {
return (await client.issuesClient.getIssue(id)).issue;
} catch (error) {
if (isRedmineRestError(error, 404)) return undefined;
throw error;
}
};