Error Handling
The Grapevine SDK provides a structured error handling system with specific error classes, error codes, and helpful suggestions for resolution.
Error Classes
The SDK exports several error classes, each designed for specific error scenarios:
| Error Class | Purpose |
|---|---|
GrapevineError | Base class for all SDK errors |
ContentError | Content validation and processing errors |
AuthError | Authentication and wallet errors |
ConfigError | Configuration and setup errors |
ApiError | API request and response errors |
ValidationError | Input validation errors |
Error Code Enum
import { ErrorCode } from '@pinata/grapevine-sdk';
enum ErrorCode {
// Validation errors
VALIDATION_ERROR = 'VALIDATION_ERROR',
CONTENT_REQUIRED = 'CONTENT_REQUIRED',
CONTENT_INVALID = 'CONTENT_INVALID',
CONTENT_EMPTY = 'CONTENT_EMPTY',
CONTENT_BOTH_PROVIDED = 'CONTENT_BOTH_PROVIDED',
BASE64_INVALID = 'BASE64_INVALID',
// Authentication errors
AUTH_NO_WALLET = 'AUTH_NO_WALLET',
AUTH_INVALID_KEY = 'AUTH_INVALID_KEY',
AUTH_FAILED = 'AUTH_FAILED',
// API errors
API_REQUEST_FAILED = 'API_REQUEST_FAILED',
PAYMENT_REQUIRED = 'PAYMENT_REQUIRED',
// Configuration errors
CONFIG_INVALID = 'CONFIG_INVALID',
CONFIG_CONFLICTING = 'CONFIG_CONFLICTING'
}GrapevineError (Base Class)
All SDK errors extend this base class, providing consistent error structure.
interface GrapevineError extends Error {
code: ErrorCode; // Error code for programmatic handling
suggestion?: string; // Helpful suggestion for fixing the error
example?: string; // Code example showing correct usage
context?: Record<string, any>; // Additional context data
}Using GrapevineError
import { GrapevineError, ErrorCode } from '@pinata/grapevine-sdk';
try {
const feed = await grapevine.feeds.create({ name: 'Test' });
} catch (error) {
if (error instanceof GrapevineError) {
console.error('Error code:', error.code);
console.error('Message:', error.message);
if (error.suggestion) {
console.log('Suggestion:', error.suggestion);
}
if (error.example) {
console.log('Example:\n', error.example);
}
// Get detailed message with suggestion and example
console.log(error.getDetailedMessage());
}
}Error to JSON
try {
await grapevine.feeds.create({ name: '' });
} catch (error) {
if (error instanceof GrapevineError) {
// Serialize for logging or API responses
const errorData = error.toJSON();
console.log(JSON.stringify(errorData, null, 2));
/*
{
"name": "ValidationError",
"message": "Invalid name: expected non-empty string",
"code": "VALIDATION_ERROR",
"suggestion": "This field is required and cannot be empty",
"example": null,
"context": null
}
*/
}
}ContentError
Errors related to content validation and processing.
import { ContentError, ErrorCode } from '@pinata/grapevine-sdk';
try {
await grapevine.entries.create(feedId, {
// Missing content
});
} catch (error) {
if (error instanceof ContentError) {
switch (error.code) {
case ErrorCode.CONTENT_REQUIRED:
console.error('Content is required');
break;
case ErrorCode.CONTENT_BOTH_PROVIDED:
console.error('Provide either content OR content_base64, not both');
break;
case ErrorCode.CONTENT_EMPTY:
console.error('Content cannot be empty');
break;
case ErrorCode.BASE64_INVALID:
console.error('Invalid base64 format');
break;
}
}
}Common Content Errors
// ❌ Missing content
await grapevine.entries.create(feedId, {
title: 'No content'
// ContentError: CONTENT_REQUIRED
});
// ❌ Both content types provided
await grapevine.entries.create(feedId, {
content: 'Hello',
content_base64: 'SGVsbG8='
// ContentError: CONTENT_BOTH_PROVIDED
});
// ❌ Empty content
await grapevine.entries.create(feedId, {
content: ''
// ContentError: CONTENT_EMPTY
});
// ❌ Invalid base64
await grapevine.entries.create(feedId, {
content_base64: 'not-valid-base64!!!'
// ContentError: BASE64_INVALID
});AuthError
Errors related to authentication and wallet configuration.
import { AuthError, ErrorCode } from '@pinata/grapevine-sdk';
try {
const feed = await grapevine.feeds.create({ name: 'Test' });
} catch (error) {
if (error instanceof AuthError) {
switch (error.code) {
case ErrorCode.AUTH_NO_WALLET:
console.error('No wallet configured');
console.log('Suggestion:', error.suggestion);
// Configure wallet before making authenticated requests
break;
case ErrorCode.AUTH_INVALID_KEY:
console.error('Invalid private key format');
break;
case ErrorCode.AUTH_FAILED:
console.error('Authentication failed');
break;
}
}
}Common Auth Errors
// ❌ No wallet configured for authenticated request
const client = new GrapevineClient({ network: 'testnet' });
await client.feeds.create({ name: 'Test' });
// AuthError: AUTH_NO_WALLET
// ❌ Invalid private key format
new GrapevineClient({
network: 'testnet',
privateKey: 'invalid-key'
});
// AuthError: AUTH_INVALID_KEY
// ❌ Private key too short
new GrapevineClient({
network: 'testnet',
privateKey: '0x1234'
});
// AuthError: AUTH_INVALID_KEYConfigError
Errors related to client configuration.
import { ConfigError, ErrorCode } from '@pinata/grapevine-sdk';
try {
new GrapevineClient({
privateKey: '0x...',
walletAdapter: someAdapter // Can't provide both!
});
} catch (error) {
if (error instanceof ConfigError) {
if (error.code === ErrorCode.CONFIG_CONFLICTING) {
console.error('Conflicting configuration');
console.log('Suggestion:', error.suggestion);
}
}
}ApiError
Errors from API requests.
import { ApiError, ErrorCode } from '@pinata/grapevine-sdk';
try {
const feed = await grapevine.feeds.get('non-existent-id');
} catch (error) {
if (error instanceof ApiError) {
console.error('API Error:', error.message);
console.error('Status:', error.status);
console.error('Response:', error.response);
// Handle specific HTTP status codes
switch (error.status) {
case 400:
console.error('Bad request - check your input');
break;
case 401:
console.error('Unauthorized - check authentication');
break;
case 402:
console.error('Payment required');
break;
case 403:
console.error('Forbidden - insufficient permissions');
break;
case 404:
console.error('Resource not found');
break;
case 429:
console.error('Rate limited - slow down');
break;
case 500:
console.error('Server error - try again later');
break;
}
}
}ValidationError
Input validation errors with field-specific information.
import { ValidationError } from '@pinata/grapevine-sdk';
try {
await grapevine.feeds.create({
name: '',
category_id: 'not-a-uuid'
});
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.message);
// "Invalid name: expected non-empty string, got "". This field is required..."
// "Invalid category_id: expected valid UUID format..."
}
}Comprehensive Error Handler
import {
GrapevineError,
ContentError,
AuthError,
ConfigError,
ApiError,
ValidationError,
ErrorCode
} from '@pinata/grapevine-sdk';
function handleGrapevineError(error: unknown): string {
if (error instanceof ContentError) {
return `Content Error: ${error.message}`;
}
if (error instanceof AuthError) {
switch (error.code) {
case ErrorCode.AUTH_NO_WALLET:
return 'Please connect your wallet to continue';
case ErrorCode.AUTH_INVALID_KEY:
return 'Invalid wallet configuration';
default:
return 'Authentication failed';
}
}
if (error instanceof ConfigError) {
return `Configuration Error: ${error.message}`;
}
if (error instanceof ApiError) {
switch (error.status) {
case 400: return 'Invalid request - please check your input';
case 401: return 'Please connect your wallet';
case 402: return 'Payment required for this action';
case 403: return 'You don\'t have permission for this action';
case 404: return 'The requested item was not found';
case 429: return 'Too many requests - please wait';
case 500: return 'Server error - please try again later';
default: return 'An error occurred';
}
}
if (error instanceof ValidationError) {
return `Validation Error: ${error.message}`;
}
if (error instanceof GrapevineError) {
return error.getDetailedMessage();
}
if (error instanceof Error) {
return error.message;
}
return 'An unexpected error occurred';
}React Error Handling
function FeedCreator() {
const [error, setError] = useState<string | null>(null);
const createFeed = async () => {
setError(null);
try {
const feed = await grapevine.feeds.create({
name: 'My Feed'
});
console.log('Created:', feed.id);
} catch (err) {
setError(handleGrapevineError(err));
}
};
return (
<div>
{error && (
<div className="error-banner">
{error}
</div>
)}
<button onClick={createFeed}>Create Feed</button>
</div>
);
}Retry Logic with Error Handling
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
// Don't retry client errors (4xx)
if (error instanceof ApiError) {
if (error.status && error.status >= 400 && error.status < 500) {
throw error;
}
}
// Don't retry validation or config errors
if (error instanceof ValidationError ||
error instanceof ConfigError ||
error instanceof AuthError) {
throw error;
}
// Wait before retry (exponential backoff)
if (attempt < maxRetries) {
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
}
}
}
throw lastError;
}
// Usage
const feed = await withRetry(() => grapevine.feeds.create({ name: 'Test' }));Error Logging
function logError(error: unknown, context: Record<string, any> = {}) {
if (error instanceof GrapevineError) {
console.error({
type: error.name,
code: error.code,
message: error.message,
suggestion: error.suggestion,
context: { ...error.context, ...context },
stack: error.stack
});
} else if (error instanceof Error) {
console.error({
type: 'Error',
message: error.message,
context,
stack: error.stack
});
}
}
// Usage
try {
await grapevine.feeds.create({ name: 'Test' });
} catch (error) {
logError(error, { operation: 'createFeed', userId: 'user123' });
}Related
- Configuration - Client setup
- Adapters - Wallet adapter errors
- API Reference - API error responses