Skip to content

Commit

Permalink
feat: Add details field for MastoError
Browse files Browse the repository at this point in the history
  • Loading branch information
neet committed May 10, 2021
1 parent 0f47ad2 commit 5a369f6
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 46 deletions.
11 changes: 7 additions & 4 deletions src/errors/masto-conflict-error.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
41 changes: 34 additions & 7 deletions src/errors/masto-error.ts
Original file line number Diff line number Diff line change
@@ -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<string, readonly MastoErrorDetail[]>;

/**
* 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();
}
}
11 changes: 7 additions & 4 deletions src/errors/masto-forbidden-error.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
11 changes: 7 additions & 4 deletions src/errors/masto-gone-error.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
11 changes: 7 additions & 4 deletions src/errors/masto-not-found-error.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
10 changes: 5 additions & 5 deletions src/errors/masto-rate-limit-error.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { MastoError } from './masto-error';
import { MastoError, MastoErrorDetails } from './masto-error';

/**
* Mastodon rate limit error class
* @param message Message for users
*/
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);
}
}
10 changes: 7 additions & 3 deletions src/errors/masto-timeout-error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MastoError } from './masto-error';
import { MastoError, MastoErrorDetails } from './masto-error';

/**
* Mastodon Timeout error
Expand All @@ -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);
}
}
11 changes: 7 additions & 4 deletions src/errors/masto-unauthorized-error.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
11 changes: 7 additions & 4 deletions src/errors/masto-unprocessable-entity-error.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { MastoError } from './masto-error';
import { MastoError, MastoErrorDetails } from './masto-error';

/**
* Mastodon unprocessable entity
* @param message Message for users
*/
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);
}
}
17 changes: 10 additions & 7 deletions src/http/http-axios-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import axios, { AxiosInstance } from 'axios';
import { MastoConfig } from '../config';
import {
MastoConflictError,
MastoError,
MastoForbiddenError,
MastoGoneError,
MastoNotFoundError,
Expand Down Expand Up @@ -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,
Expand All @@ -96,7 +99,7 @@ export class HttpAxiosImpl implements Http {
description,
);
default:
throw error;
throw new MastoError(...args);
}
}
}
Expand Down

0 comments on commit 5a369f6

Please sign in to comment.