Skip to content

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 ClassPurpose
GrapevineErrorBase class for all SDK errors
ContentErrorContent validation and processing errors
AuthErrorAuthentication and wallet errors
ConfigErrorConfiguration and setup errors
ApiErrorAPI request and response errors
ValidationErrorInput 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_KEY

ConfigError

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