Files
umami/packages/api-client/src/errors.ts
T

101 lines
2.2 KiB
TypeScript

export interface UmamiApiErrorBody {
error?: {
message?: string;
code?: string;
status?: number;
[key: string]: unknown;
};
[key: string]: unknown;
}
/**
* Thrown for any non-2xx response. `code` mirrors the API's error code
* (`unauthorized`, `forbidden`, `not-found`, `bad-request`, `server-error`, …).
*/
export class UmamiApiError extends Error {
readonly status: number;
readonly code: string;
readonly method: string;
readonly url: string;
readonly body: unknown;
constructor(options: {
status: number;
code?: string;
message?: string;
method: string;
url: string;
body?: unknown;
}) {
super(options.message ?? defaultMessage(options.status));
this.name = 'UmamiApiError';
this.status = options.status;
this.code = options.code ?? defaultCode(options.status);
this.method = options.method;
this.url = options.url;
this.body = options.body;
}
get isUnauthorized() {
return this.status === 401;
}
get isForbidden() {
return this.status === 403;
}
get isNotFound() {
return this.status === 404;
}
get isRateLimited() {
return this.status === 429;
}
get isServerError() {
return this.status >= 500;
}
}
export function isUmamiApiError(error: unknown): error is UmamiApiError {
return error instanceof UmamiApiError || (error as { name?: string })?.name === 'UmamiApiError';
}
function defaultCode(status: number) {
switch (status) {
case 400:
return 'bad-request';
case 401:
return 'unauthorized';
case 403:
return 'forbidden';
case 404:
return 'not-found';
case 413:
return 'payload-too-large';
case 429:
return 'rate-limited';
case 503:
return 'service-unavailable';
default:
return status >= 500 ? 'server-error' : 'request-failed';
}
}
function defaultMessage(status: number) {
switch (status) {
case 400:
return 'Bad request';
case 401:
return 'Unauthorized';
case 403:
return 'Forbidden';
case 404:
return 'Not found';
case 429:
return 'Too many requests';
default:
return status >= 500 ? 'Server error' : `Request failed with status ${status}`;
}
}