refactor(client): expose error hierarchy via src/client/errors.ts and add code property

This commit is contained in:
Lucas Winther
2026-09-06 00:48:04 +02:00
parent f8f9ae65a2
commit 290d9a2cc9
3 changed files with 60 additions and 16 deletions
+43 -16
View File
@@ -19,6 +19,7 @@ export class OpenProjectError extends Error {
readonly statusCode?: number; readonly statusCode?: number;
readonly errorIdentifier?: string; readonly errorIdentifier?: string;
readonly details?: unknown; readonly details?: unknown;
readonly code: string;
constructor( constructor(
message: string, message: string,
@@ -27,6 +28,7 @@ export class OpenProjectError extends Error {
errorIdentifier?: string; errorIdentifier?: string;
details?: unknown; details?: unknown;
cause?: unknown; cause?: unknown;
code?: string;
} }
) { ) {
super(message); super(message);
@@ -34,6 +36,7 @@ export class OpenProjectError extends Error {
this.statusCode = options?.statusCode; this.statusCode = options?.statusCode;
this.errorIdentifier = options?.errorIdentifier; this.errorIdentifier = options?.errorIdentifier;
this.details = options?.details; this.details = options?.details;
this.code = options?.code ?? options?.errorIdentifier ?? "OPENPROJECT_ERROR";
if (options?.cause) { if (options?.cause) {
this.cause = options.cause; this.cause = options.cause;
} }
@@ -41,36 +44,51 @@ export class OpenProjectError extends Error {
} }
export class OpenProjectAuthenticationError extends OpenProjectError { export class OpenProjectAuthenticationError extends OpenProjectError {
constructor(message = "Authentication failed. Please check your OpenProject API key.", options?: { errorIdentifier?: string; details?: unknown; cause?: unknown }) { constructor(
super(message, { statusCode: 401, ...options }); message = "Authentication failed. Please check your OpenProject API key.",
options?: { errorIdentifier?: string; details?: unknown; cause?: unknown; code?: string }
) {
super(message, { statusCode: 401, code: options?.code ?? options?.errorIdentifier ?? "OPENPROJECT_AUTH_ERROR", ...options });
this.name = "OpenProjectAuthenticationError"; this.name = "OpenProjectAuthenticationError";
} }
} }
export class OpenProjectForbiddenError extends OpenProjectError { export class OpenProjectForbiddenError extends OpenProjectError {
constructor(message = "Access forbidden. You do not have permission to access this resource.", options?: { errorIdentifier?: string; details?: unknown; cause?: unknown }) { constructor(
super(message, { statusCode: 403, ...options }); message = "Access forbidden. You do not have permission to access this resource.",
options?: { errorIdentifier?: string; details?: unknown; cause?: unknown; code?: string }
) {
super(message, { statusCode: 403, code: options?.code ?? options?.errorIdentifier ?? "OPENPROJECT_FORBIDDEN", ...options });
this.name = "OpenProjectForbiddenError"; this.name = "OpenProjectForbiddenError";
} }
} }
export class OpenProjectNotFoundError extends OpenProjectError { export class OpenProjectNotFoundError extends OpenProjectError {
constructor(message = "Resource not found.", options?: { errorIdentifier?: string; details?: unknown; cause?: unknown }) { constructor(
super(message, { statusCode: 404, ...options }); message = "Resource not found.",
options?: { errorIdentifier?: string; details?: unknown; cause?: unknown; code?: string }
) {
super(message, { statusCode: 404, code: options?.code ?? options?.errorIdentifier ?? "OPENPROJECT_NOT_FOUND", ...options });
this.name = "OpenProjectNotFoundError"; this.name = "OpenProjectNotFoundError";
} }
} }
export class OpenProjectConflictError extends OpenProjectError { export class OpenProjectConflictError extends OpenProjectError {
constructor(message = "Conflict updating resource. Lock version mismatch.", options?: { errorIdentifier?: string; details?: unknown; cause?: unknown }) { constructor(
super(message, { statusCode: 409, ...options }); message = "Conflict updating resource. Lock version mismatch.",
options?: { errorIdentifier?: string; details?: unknown; cause?: unknown; code?: string }
) {
super(message, { statusCode: 409, code: options?.code ?? options?.errorIdentifier ?? "OPENPROJECT_CONFLICT", ...options });
this.name = "OpenProjectConflictError"; this.name = "OpenProjectConflictError";
} }
} }
export class OpenProjectValidationError extends OpenProjectError { export class OpenProjectValidationError extends OpenProjectError {
constructor(message = "Validation failed for request data.", options?: { errorIdentifier?: string; details?: unknown; cause?: unknown }) { constructor(
super(message, { statusCode: 422, ...options }); message = "Validation failed for request data.",
options?: { errorIdentifier?: string; details?: unknown; cause?: unknown; code?: string }
) {
super(message, { statusCode: 422, code: options?.code ?? options?.errorIdentifier ?? "OPENPROJECT_VALIDATION_ERROR", ...options });
this.name = "OpenProjectValidationError"; this.name = "OpenProjectValidationError";
} }
} }
@@ -78,23 +96,32 @@ export class OpenProjectValidationError extends OpenProjectError {
export class OpenProjectRateLimitError extends OpenProjectError { export class OpenProjectRateLimitError extends OpenProjectError {
readonly retryAfter?: number; readonly retryAfter?: number;
constructor(message = "Rate limit exceeded. Please wait before retrying.", options?: { retryAfter?: number; errorIdentifier?: string; details?: unknown; cause?: unknown }) { constructor(
super(message, { statusCode: 429, ...options }); message = "Rate limit exceeded. Please wait before retrying.",
options?: { retryAfter?: number; errorIdentifier?: string; details?: unknown; cause?: unknown; code?: string }
) {
super(message, { statusCode: 429, code: options?.code ?? options?.errorIdentifier ?? "OPENPROJECT_RATE_LIMIT", ...options });
this.name = "OpenProjectRateLimitError"; this.name = "OpenProjectRateLimitError";
this.retryAfter = options?.retryAfter; this.retryAfter = options?.retryAfter;
} }
} }
export class OpenProjectServerError extends OpenProjectError { export class OpenProjectServerError extends OpenProjectError {
constructor(message = "OpenProject internal server error.", options?: { statusCode?: number; errorIdentifier?: string; details?: unknown; cause?: unknown }) { constructor(
super(message, { statusCode: options?.statusCode ?? 500, ...options }); message = "OpenProject internal server error.",
options?: { statusCode?: number; errorIdentifier?: string; details?: unknown; cause?: unknown; code?: string }
) {
super(message, { statusCode: options?.statusCode ?? 500, code: options?.code ?? options?.errorIdentifier ?? "OPENPROJECT_SERVER_ERROR", ...options });
this.name = "OpenProjectServerError"; this.name = "OpenProjectServerError";
} }
} }
export class OpenProjectNetworkError extends OpenProjectError { export class OpenProjectNetworkError extends OpenProjectError {
constructor(message = "Network error connecting to OpenProject instance.", options?: { cause?: unknown }) { constructor(
super(message, options); message = "Network error connecting to OpenProject instance.",
options?: { cause?: unknown; code?: string }
) {
super(message, { code: options?.code ?? "OPENPROJECT_NETWORK_ERROR", ...options });
this.name = "OpenProjectNetworkError"; this.name = "OpenProjectNetworkError";
} }
} }
+15
View File
@@ -0,0 +1,15 @@
/**
* OpenProject Client Errors
*/
export {
OpenProjectError,
OpenProjectAuthenticationError,
OpenProjectForbiddenError,
OpenProjectNotFoundError,
OpenProjectConflictError,
OpenProjectValidationError,
OpenProjectRateLimitError,
OpenProjectServerError,
OpenProjectNetworkError,
} from "./api-client.ts";
+2
View File
@@ -2,3 +2,5 @@ export * from "./types.ts";
export * from "./api-client.ts"; export * from "./api-client.ts";
export * from "./hal-parser.ts"; export * from "./hal-parser.ts";
export * from "./filter-builder.ts"; export * from "./filter-builder.ts";
export * from "./errors.ts";