Skip to content

Commit

Permalink
refactor: (draft) improving API error handling for coinbase integration
Browse files Browse the repository at this point in the history
  • Loading branch information
ai16z-demirix committed Jan 3, 2025
1 parent 76d4f42 commit 3a9e84e
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 25 deletions.
127 changes: 107 additions & 20 deletions packages/plugin-coinbase/advanced-sdk-ts/src/rest/errors.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,123 @@
import { Response } from 'node-fetch';

class CoinbaseError extends Error {
statusCode: number;
response: Response;
// Define specific error types for different scenarios
export enum CoinbaseErrorType {
AUTHENTICATION = 'AUTHENTICATION',
PERMISSION = 'PERMISSION',
VALIDATION = 'VALIDATION',
RATE_LIMIT = 'RATE_LIMIT',
SERVER_ERROR = 'SERVER_ERROR',
NETWORK_ERROR = 'NETWORK_ERROR',
UNKNOWN = 'UNKNOWN'
}

export interface CoinbaseErrorDetails {
type: CoinbaseErrorType;
message: string;
details?: Record<string, any>;
suggestion?: string;
}

constructor(message: string, statusCode: number, response: Response) {
super(message);
export class CoinbaseError extends Error {
readonly statusCode: number;
readonly response: Response;
readonly type: CoinbaseErrorType;
readonly details?: Record<string, any>;
readonly suggestion?: string;

constructor(errorDetails: CoinbaseErrorDetails, statusCode: number, response: Response) {
super(errorDetails.message);
this.name = 'CoinbaseError';
this.statusCode = statusCode;
this.response = response;
this.type = errorDetails.type;
this.details = errorDetails.details;
this.suggestion = errorDetails.suggestion;
}
}

function parseErrorResponse(responseText: string): Record<string, any> {
try {
return JSON.parse(responseText);
} catch {
return {};
}
}

function getErrorDetails(response: Response, responseText: string): CoinbaseErrorDetails {
const parsedError = parseErrorResponse(responseText);
const status = response.status;

// Authentication errors
if (status === 401) {
return {
type: CoinbaseErrorType.AUTHENTICATION,
message: 'Invalid API credentials',
suggestion: 'Please verify your API key and secret are correct and not expired.'
};
}

// Permission errors
if (status === 403) {
if (responseText.includes('"error_details":"Missing required scopes"')) {
return {
type: CoinbaseErrorType.PERMISSION,
message: 'Missing required API permissions',
suggestion: 'Please verify your API key has the necessary permissions enabled in your Coinbase account settings.'
};
}
return {
type: CoinbaseErrorType.PERMISSION,
message: 'Access denied',
suggestion: 'Please check if you have the necessary permissions to perform this action.'
};
}

// Validation errors
if (status === 400) {
return {
type: CoinbaseErrorType.VALIDATION,
message: parsedError.message || 'Invalid request parameters',
details: parsedError,
suggestion: 'Please verify all required parameters are provided and have valid values.'
};
}

// Rate limit errors
if (status === 429) {
return {
type: CoinbaseErrorType.RATE_LIMIT,
message: 'Rate limit exceeded',
suggestion: 'Please reduce your request frequency or wait before trying again.'
};
}

// Server errors
if (status >= 500) {
return {
type: CoinbaseErrorType.SERVER_ERROR,
message: 'Coinbase service error',
suggestion: 'This is a temporary issue with Coinbase. Please try again later.'
};
}

// Default unknown error
return {
type: CoinbaseErrorType.UNKNOWN,
message: `Unexpected error: ${response.statusText}`,
details: parsedError,
suggestion: 'If this persists, please contact team with the error details.'
};
}

export function handleException(
response: Response,
responseText: string,
reason: string
) {
let message: string | undefined;

if (
(400 <= response.status && response.status <= 499) ||
(500 <= response.status && response.status <= 599)
) {
if (
response.status == 403 &&
responseText.includes('"error_details":"Missing required scopes"')
) {
message = `${response.status} Coinbase Error: Missing Required Scopes. Please verify your API keys include the necessary permissions.`;
} else
message = `${response.status} Coinbase Error: ${reason} ${responseText}`;

throw new CoinbaseError(message, response.status, response);
if ((400 <= response.status && response.status <= 499) ||
(500 <= response.status && response.status <= 599)) {
const errorDetails = getErrorDetails(response, responseText);
throw new CoinbaseError(errorDetails, response.status, response);
}
}
33 changes: 28 additions & 5 deletions packages/plugin-coinbase/advanced-sdk-ts/src/rest/rest-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fetch, { Headers, RequestInit, Response } from 'node-fetch';
import { BASE_URL, USER_AGENT } from '../constants';
import { RequestOptions } from './types/request-types';
import { handleException } from './errors';
import { CoinbaseError, CoinbaseErrorType } from './errors';

export class RESTBase {
private apiKey: string | undefined;
Expand Down Expand Up @@ -60,11 +61,33 @@ export class RESTBase {
requestOptions: RequestInit,
url: string
) {
const response: Response = await fetch(url, requestOptions);
const responseText = await response.text();
handleException(response, responseText, response.statusText);

return responseText;
try {
const response: Response = await fetch(url, requestOptions);
const responseText = await response.text();

// Handle API errors
handleException(response, responseText, response.statusText);

// Parse successful response
try {
return JSON.parse(responseText);
} catch {
// If response is not JSON, return raw text
return responseText;
}
} catch (error) {
if (error instanceof CoinbaseError) {
// Re-throw Coinbase specific errors
throw error;
}
// Handle network or other errors
throw new CoinbaseError({
type: CoinbaseErrorType.NETWORK_ERROR,
message: 'Failed to connect to Coinbase',
details: { originalError: error },
suggestion: 'Please check your internet connection and try again.'
}, 0, new Response());
}
}

setHeaders(httpMethod: string, urlPath: string, isPublic?: boolean) {
Expand Down

0 comments on commit 3a9e84e

Please sign in to comment.