diff --git a/src/errors/masto-conflict-error.ts b/src/errors/masto-conflict-error.ts index 30b5d84e8..630025c5d 100644 --- a/src/errors/masto-conflict-error.ts +++ b/src/errors/masto-conflict-error.ts @@ -1,13 +1,16 @@ -import { MastoError } from './masto-error'; +import { MastoError, MastoErrorDetails } from './masto-error'; /** * Mastodon forbidden error */ export class MastoConflictError extends MastoError { readonly name = 'MastoConflictError'; - readonly statusCode = 409; - constructor(readonly message: string, readonly description?: string) { - super(); + constructor( + readonly message: string, + readonly description?: string, + readonly details?: MastoErrorDetails, + ) { + super(message, 409, description, details); } } diff --git a/src/errors/masto-error.ts b/src/errors/masto-error.ts index 234da0c17..75ae49332 100644 --- a/src/errors/masto-error.ts +++ b/src/errors/masto-error.ts @@ -1,14 +1,41 @@ +// https://github.com/tootsuite/mastodon/pull/15803 +export type MastoErrorType = + | 'ERR_BLOCKED' + | 'ERR_UNREACHABLE' + | 'ERR_TAKEN' + | 'ERR_RESERVED' + | 'ERR_ACCEPTED' + | 'ERR_BLANK' + | 'ERR_INVALID' + | 'ERR_TOO_LONG' + | 'ERR_TOO_SHORT' + | 'ERR_INCLUSION'; + +export interface MastoErrorDetail { + readonly error: MastoErrorType; + readonly description: string; +} + +export type MastoErrorDetails = Record; + /** * Error object * @see https://docs.joinmastodon.org/entities/error/ */ -export abstract class MastoError extends Error { +export class MastoError extends Error { /** Helper to check if the error has been thrown from Masto */ readonly isMastoError = true; - /** HTTP status code */ - readonly statusCode?: number; - /** The error message. Equivalent for the `error` field from the Error entity */ - abstract readonly message: string; - /** A longer description of the error, mainly provided with the OAuth API. */ - abstract readonly description?: string | null; + + constructor( + /** The error message. Equivalent for the `error` field from the Error entity */ + readonly message: string, + /** HTTP status code */ + readonly statusCode?: number, + /** A longer description of the error, mainly provided with the OAuth API. */ + readonly description?: string | null, + /** Used by /api/v1/accounts */ + readonly details?: MastoErrorDetails | null, + ) { + super(); + } } diff --git a/src/errors/masto-forbidden-error.ts b/src/errors/masto-forbidden-error.ts index 33da49bf0..9721b1289 100644 --- a/src/errors/masto-forbidden-error.ts +++ b/src/errors/masto-forbidden-error.ts @@ -1,13 +1,16 @@ -import { MastoError } from './masto-error'; +import { MastoError, MastoErrorDetails } from './masto-error'; /** * Mastodon forbidden error */ export class MastoForbiddenError extends MastoError { readonly name = 'MastoForbiddenError'; - readonly statusCode = 403; - constructor(readonly message: string, readonly description?: string) { - super(); + constructor( + message: string, + description?: string, + details?: MastoErrorDetails, + ) { + super(message, 403, description, details); } } diff --git a/src/errors/masto-gone-error.ts b/src/errors/masto-gone-error.ts index cfe3469f2..7d9767854 100644 --- a/src/errors/masto-gone-error.ts +++ b/src/errors/masto-gone-error.ts @@ -1,13 +1,16 @@ -import { MastoError } from './masto-error'; +import { MastoError, MastoErrorDetails } from './masto-error'; /** * Mastodon gone error */ export class MastoGoneError extends MastoError { readonly name = 'MastoGoneError'; - readonly statusCode = 410; - constructor(readonly message: string, readonly description?: string) { - super(); + constructor( + message: string, + description?: string, + details?: MastoErrorDetails, + ) { + super(message, 410, description, details); } } diff --git a/src/errors/masto-not-found-error.ts b/src/errors/masto-not-found-error.ts index 866a9e699..ec3de5a03 100644 --- a/src/errors/masto-not-found-error.ts +++ b/src/errors/masto-not-found-error.ts @@ -1,13 +1,16 @@ -import { MastoError } from './masto-error'; +import { MastoError, MastoErrorDetails } from './masto-error'; /** * Mastodon not found error class */ export class MastoNotFoundError extends MastoError { - readonly statusCode = 404; readonly name = 'MastoNotFoundError'; - constructor(readonly message: string, readonly description?: string) { - super(); + constructor( + message: string, + description?: string, + details?: MastoErrorDetails, + ) { + super(message, 404, description, details); } } diff --git a/src/errors/masto-rate-limit-error.ts b/src/errors/masto-rate-limit-error.ts index 64d7c895d..6456db54c 100644 --- a/src/errors/masto-rate-limit-error.ts +++ b/src/errors/masto-rate-limit-error.ts @@ -1,4 +1,4 @@ -import { MastoError } from './masto-error'; +import { MastoError, MastoErrorDetails } from './masto-error'; /** * Mastodon rate limit error class @@ -6,18 +6,18 @@ import { MastoError } from './masto-error'; */ export class MastoRateLimitError extends MastoError { readonly name = 'MastoRateLimitError'; - readonly statusCode = 429; constructor( - readonly message: string, + message: string, /** Number of requests permitted per time period */ readonly limit: number, /** Number of requests you can still make */ readonly remaining: number, /** Timestamp when your rate limit will reset */ readonly reset: string, - readonly description?: string, + description?: string, + details?: MastoErrorDetails, ) { - super(); + super(message, 429, description, details); } } diff --git a/src/errors/masto-timeout-error.ts b/src/errors/masto-timeout-error.ts index 77f2c8c04..ab4489e15 100644 --- a/src/errors/masto-timeout-error.ts +++ b/src/errors/masto-timeout-error.ts @@ -1,4 +1,4 @@ -import { MastoError } from './masto-error'; +import { MastoError, MastoErrorDetails } from './masto-error'; /** * Mastodon Timeout error @@ -7,7 +7,11 @@ import { MastoError } from './masto-error'; export class MastoTimeoutError extends MastoError { readonly name = 'MastoTimeoutError'; - constructor(readonly message: string, readonly description?: string) { - super(); + constructor( + message: string, + description?: string, + details?: MastoErrorDetails, + ) { + super(message, undefined, description, details); } } diff --git a/src/errors/masto-unauthorized-error.ts b/src/errors/masto-unauthorized-error.ts index 5dc85a329..5d046c762 100644 --- a/src/errors/masto-unauthorized-error.ts +++ b/src/errors/masto-unauthorized-error.ts @@ -1,14 +1,17 @@ -import { MastoError } from './masto-error'; +import { MastoError, MastoErrorDetails } from './masto-error'; /** * Mastodon unauthorized error class * @param message Message for users */ export class MastoUnauthorizedError extends MastoError { - readonly statusCode = 401; readonly name = 'MastoUnauthorizedError'; - constructor(readonly message: string, readonly description?: string) { - super(); + constructor( + message: string, + description?: string, + details?: MastoErrorDetails, + ) { + super(message, 401, description, details); } } diff --git a/src/errors/masto-unprocessable-entity-error.ts b/src/errors/masto-unprocessable-entity-error.ts index 7e13d601c..b0b1ab2cc 100644 --- a/src/errors/masto-unprocessable-entity-error.ts +++ b/src/errors/masto-unprocessable-entity-error.ts @@ -1,4 +1,4 @@ -import { MastoError } from './masto-error'; +import { MastoError, MastoErrorDetails } from './masto-error'; /** * Mastodon unprocessable entity @@ -6,9 +6,12 @@ import { MastoError } from './masto-error'; */ export class MastoUnprocessableEntityError extends MastoError { readonly name = 'MastoUnprocessableEntityError'; - readonly statusCode = 422; - constructor(readonly message: string, readonly description?: string) { - super(); + constructor( + message: string, + description?: string, + details?: MastoErrorDetails, + ) { + super(message, 422, description, details); } } diff --git a/src/http/http-axios-impl.ts b/src/http/http-axios-impl.ts index b48078a74..5b0fb51e2 100644 --- a/src/http/http-axios-impl.ts +++ b/src/http/http-axios-impl.ts @@ -3,6 +3,7 @@ import axios, { AxiosInstance } from 'axios'; import { MastoConfig } from '../config'; import { MastoConflictError, + MastoError, MastoForbiddenError, MastoGoneError, MastoNotFoundError, @@ -73,20 +74,22 @@ export class HttpAxiosImpl implements Http { const message = error?.response?.data?.error ?? 'Unexpected error occurred'; const description = error?.response?.data?.errorDescription; + const details = error?.response?.data?.details; + const args = [message, description, details] as const; switch (status) { case 401: - throw new MastoUnauthorizedError(message, description); + throw new MastoUnauthorizedError(...args); case 403: - throw new MastoForbiddenError(message, description); + throw new MastoForbiddenError(...args); case 404: - throw new MastoNotFoundError(message, description); + throw new MastoNotFoundError(...args); case 409: - throw new MastoConflictError(message, description); + throw new MastoConflictError(...args); case 410: - throw new MastoGoneError(message, description); + throw new MastoGoneError(...args); case 422: - throw new MastoUnprocessableEntityError(message, description); + throw new MastoUnprocessableEntityError(...args); case 429: throw new MastoRateLimitError( message, @@ -96,7 +99,7 @@ export class HttpAxiosImpl implements Http { description, ); default: - throw error; + throw new MastoError(...args); } } }