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:

PropertyDescription
statusThe HTTP status code as a string, e.g. "404"
bodyThe response body: parsed JSON (e.g. the validation errors), the text (e.g. HTML) or undefined
headersThe response headers
requestThe request, with its method and url
responseThe raw response
messagee.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

StatusMeaning in Redmine
401Missing or invalid API key, or the REST API is disabled
403The user isn't allowed to do this, e.g. an admin-only endpoint or an archived project
404The resource doesn't exist, or the user can't see it (e.g. an issue of a private project)
409The resource was changed in the meantime, e.g. a wiki page saved with an outdated version
422Validation 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;
  }
};

On this page