Skip to content

Commit 3a9e84e

Browse files
committed
refactor: (draft) improving API error handling for coinbase integration
1 parent 76d4f42 commit 3a9e84e

File tree

2 files changed

+135
-25
lines changed

2 files changed

+135
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,123 @@
11
import { Response } from 'node-fetch';
22

3-
class CoinbaseError extends Error {
4-
statusCode: number;
5-
response: Response;
3+
// Define specific error types for different scenarios
4+
export enum CoinbaseErrorType {
5+
AUTHENTICATION = 'AUTHENTICATION',
6+
PERMISSION = 'PERMISSION',
7+
VALIDATION = 'VALIDATION',
8+
RATE_LIMIT = 'RATE_LIMIT',
9+
SERVER_ERROR = 'SERVER_ERROR',
10+
NETWORK_ERROR = 'NETWORK_ERROR',
11+
UNKNOWN = 'UNKNOWN'
12+
}
13+
14+
export interface CoinbaseErrorDetails {
15+
type: CoinbaseErrorType;
16+
message: string;
17+
details?: Record<string, any>;
18+
suggestion?: string;
19+
}
620

7-
constructor(message: string, statusCode: number, response: Response) {
8-
super(message);
21+
export class CoinbaseError extends Error {
22+
readonly statusCode: number;
23+
readonly response: Response;
24+
readonly type: CoinbaseErrorType;
25+
readonly details?: Record<string, any>;
26+
readonly suggestion?: string;
27+
28+
constructor(errorDetails: CoinbaseErrorDetails, statusCode: number, response: Response) {
29+
super(errorDetails.message);
930
this.name = 'CoinbaseError';
1031
this.statusCode = statusCode;
1132
this.response = response;
33+
this.type = errorDetails.type;
34+
this.details = errorDetails.details;
35+
this.suggestion = errorDetails.suggestion;
36+
}
37+
}
38+
39+
function parseErrorResponse(responseText: string): Record<string, any> {
40+
try {
41+
return JSON.parse(responseText);
42+
} catch {
43+
return {};
44+
}
45+
}
46+
47+
function getErrorDetails(response: Response, responseText: string): CoinbaseErrorDetails {
48+
const parsedError = parseErrorResponse(responseText);
49+
const status = response.status;
50+
51+
// Authentication errors
52+
if (status === 401) {
53+
return {
54+
type: CoinbaseErrorType.AUTHENTICATION,
55+
message: 'Invalid API credentials',
56+
suggestion: 'Please verify your API key and secret are correct and not expired.'
57+
};
58+
}
59+
60+
// Permission errors
61+
if (status === 403) {
62+
if (responseText.includes('"error_details":"Missing required scopes"')) {
63+
return {
64+
type: CoinbaseErrorType.PERMISSION,
65+
message: 'Missing required API permissions',
66+
suggestion: 'Please verify your API key has the necessary permissions enabled in your Coinbase account settings.'
67+
};
68+
}
69+
return {
70+
type: CoinbaseErrorType.PERMISSION,
71+
message: 'Access denied',
72+
suggestion: 'Please check if you have the necessary permissions to perform this action.'
73+
};
74+
}
75+
76+
// Validation errors
77+
if (status === 400) {
78+
return {
79+
type: CoinbaseErrorType.VALIDATION,
80+
message: parsedError.message || 'Invalid request parameters',
81+
details: parsedError,
82+
suggestion: 'Please verify all required parameters are provided and have valid values.'
83+
};
1284
}
85+
86+
// Rate limit errors
87+
if (status === 429) {
88+
return {
89+
type: CoinbaseErrorType.RATE_LIMIT,
90+
message: 'Rate limit exceeded',
91+
suggestion: 'Please reduce your request frequency or wait before trying again.'
92+
};
93+
}
94+
95+
// Server errors
96+
if (status >= 500) {
97+
return {
98+
type: CoinbaseErrorType.SERVER_ERROR,
99+
message: 'Coinbase service error',
100+
suggestion: 'This is a temporary issue with Coinbase. Please try again later.'
101+
};
102+
}
103+
104+
// Default unknown error
105+
return {
106+
type: CoinbaseErrorType.UNKNOWN,
107+
message: `Unexpected error: ${response.statusText}`,
108+
details: parsedError,
109+
suggestion: 'If this persists, please contact team with the error details.'
110+
};
13111
}
14112

15113
export function handleException(
16114
response: Response,
17115
responseText: string,
18116
reason: string
19117
) {
20-
let message: string | undefined;
21-
22-
if (
23-
(400 <= response.status && response.status <= 499) ||
24-
(500 <= response.status && response.status <= 599)
25-
) {
26-
if (
27-
response.status == 403 &&
28-
responseText.includes('"error_details":"Missing required scopes"')
29-
) {
30-
message = `${response.status} Coinbase Error: Missing Required Scopes. Please verify your API keys include the necessary permissions.`;
31-
} else
32-
message = `${response.status} Coinbase Error: ${reason} ${responseText}`;
33-
34-
throw new CoinbaseError(message, response.status, response);
118+
if ((400 <= response.status && response.status <= 499) ||
119+
(500 <= response.status && response.status <= 599)) {
120+
const errorDetails = getErrorDetails(response, responseText);
121+
throw new CoinbaseError(errorDetails, response.status, response);
35122
}
36123
}

packages/plugin-coinbase/advanced-sdk-ts/src/rest/rest-base.ts

+28-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fetch, { Headers, RequestInit, Response } from 'node-fetch';
33
import { BASE_URL, USER_AGENT } from '../constants';
44
import { RequestOptions } from './types/request-types';
55
import { handleException } from './errors';
6+
import { CoinbaseError, CoinbaseErrorType } from './errors';
67

78
export class RESTBase {
89
private apiKey: string | undefined;
@@ -60,11 +61,33 @@ export class RESTBase {
6061
requestOptions: RequestInit,
6162
url: string
6263
) {
63-
const response: Response = await fetch(url, requestOptions);
64-
const responseText = await response.text();
65-
handleException(response, responseText, response.statusText);
66-
67-
return responseText;
64+
try {
65+
const response: Response = await fetch(url, requestOptions);
66+
const responseText = await response.text();
67+
68+
// Handle API errors
69+
handleException(response, responseText, response.statusText);
70+
71+
// Parse successful response
72+
try {
73+
return JSON.parse(responseText);
74+
} catch {
75+
// If response is not JSON, return raw text
76+
return responseText;
77+
}
78+
} catch (error) {
79+
if (error instanceof CoinbaseError) {
80+
// Re-throw Coinbase specific errors
81+
throw error;
82+
}
83+
// Handle network or other errors
84+
throw new CoinbaseError({
85+
type: CoinbaseErrorType.NETWORK_ERROR,
86+
message: 'Failed to connect to Coinbase',
87+
details: { originalError: error },
88+
suggestion: 'Please check your internet connection and try again.'
89+
}, 0, new Response());
90+
}
6891
}
6992

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

0 commit comments

Comments
 (0)