SweatHost
SDK Reference

Error Handling

Complete guide to handling errors in the SweatHost SDK

Overview

The SweatHost SDK provides comprehensive error handling with typed errors and detailed error information.

Error Types

SweatHostError

Base error class for all SDK errors.

class SweatHostError extends Error {
  /**
   * Error code
   */
  code: string;

  /**
   * HTTP status code
   */
  statusCode: number;

  /**
   * Additional error details
   */
  details?: Record<string, any>;

  /**
   * Request ID for debugging
   */
  requestId?: string;

  /**
   * Timestamp when error occurred
   */
  timestamp: string;
}

Error Codes

CodeStatusDescription
UNAUTHORIZED401Invalid or missing API key
FORBIDDEN403No permission to access resource
NOT_FOUND404Resource not found
INVALID_PARAMS400Invalid request parameters
VALIDATION_ERROR422Request validation failed
RATE_LIMIT_EXCEEDED429Too many requests
SERVER_ERROR500Internal server error
SERVICE_UNAVAILABLE503Service temporarily unavailable
TIMEOUT408Request timeout
NETWORK_ERROR0Network connection error
INSUFFICIENT_CAPACITY503No available server capacity
QUOTA_EXCEEDED403Account limit reached
PAYMENT_REQUIRED402Insufficient credits
SERVER_BUSY409Resource is currently busy
SERVER_NOT_RUNNING409Server must be running
RCON_FAILED500RCON command failed

Basic Error Handling

Try-Catch

import { SweatHostClient, SweatHostError } from '@sweathost/sdk';

const client = new SweatHostClient({
  apiKey: process.env.SWEATHOST_API_KEY!,
});

try {
  const server = await client.gameServers.get('server_123');
  console.log(server);
} catch (error) {
  if (error instanceof SweatHostError) {
    console.error(`Error ${error.code}: ${error.message}`);
    console.error(`Status: ${error.statusCode}`);
    console.error(`Request ID: ${error.requestId}`);
  } else {
    console.error('Unexpected error:', error);
  }
}

Checking Error Codes

try {
  const server = await client.gameServers.get('invalid_id');
} catch (error) {
  if (error instanceof SweatHostError) {
    switch (error.code) {
      case 'NOT_FOUND':
        console.error('Server not found');
        break;
      case 'UNAUTHORIZED':
        console.error('Invalid API key');
        break;
      case 'FORBIDDEN':
        console.error('No access to this server');
        break;
      default:
        console.error(`Error: ${error.message}`);
    }
  }
}

Specific Error Scenarios

Authentication Errors

try {
  const servers = await client.gameServers.list();
} catch (error) {
  if (error instanceof SweatHostError && error.code === 'UNAUTHORIZED') {
    console.error('Authentication failed. Please check your API key.');
    console.error('Get your API key from: https://dashboard.sweathost.com/settings/api-keys');
    process.exit(1);
  }
}

Not Found Errors

async function getServerSafely(serverId: string) {
  try {
    return await client.gameServers.get(serverId);
  } catch (error) {
    if (error instanceof SweatHostError && error.code === 'NOT_FOUND') {
      console.warn(`Server ${serverId} not found`);
      return null;
    }
    throw error; // Re-throw other errors
  }
}

const server = await getServerSafely('server_123');
if (server) {
  console.log(`Found server: ${server.name}`);
} else {
  console.log('Server does not exist');
}

Rate Limiting

async function withRateLimitRetry<T>(
  operation: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      if (error instanceof SweatHostError && error.code === 'RATE_LIMIT_EXCEEDED') {
        const retryAfter = error.details?.retryAfter || 60;
        console.warn(`Rate limited. Retrying in ${retryAfter}s...`);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

// Usage
const servers = await withRateLimitRetry(() =>
  client.gameServers.list()
);

Validation Errors

try {
  const server = await client.gameServers.create({
    name: 'My Server',
    game: 'cs2',
    region: 'invalid-region', // Invalid region
    config: {
      maxPlayers: 100, // Exceeds maximum
      tickRate: 128,
    },
  });
} catch (error) {
  if (error instanceof SweatHostError && error.code === 'VALIDATION_ERROR') {
    console.error('Validation failed:');
    
    // Details contains field-specific errors
    if (error.details?.errors) {
      error.details.errors.forEach((err: any) => {
        console.error(`  ${err.field}: ${err.message}`);
      });
    }
  }
}

Capacity Errors

try {
  const server = await client.gameServers.create({
    name: 'My Server',
    game: 'cs2',
    region: 'us-east',
    config: {
      maxPlayers: 10,
      tickRate: 128,
    },
  });
} catch (error) {
  if (error instanceof SweatHostError) {
    if (error.code === 'INSUFFICIENT_CAPACITY') {
      console.error('No available capacity in this region');
      console.error('Available regions:', error.details?.availableRegions);
      
      // Try alternative region
      const altRegion = error.details?.availableRegions?.[0];
      if (altRegion) {
        console.log(`Trying ${altRegion} instead...`);
        // Retry with alternative region
      }
    } else if (error.code === 'QUOTA_EXCEEDED') {
      console.error('Server limit reached for your account');
      console.error(`Current: ${error.details?.current}`);
      console.error(`Limit: ${error.details?.limit}`);
      console.error('Upgrade at: https://dashboard.sweathost.com/billing');
    }
  }
}

Payment Errors

try {
  const server = await client.gameServers.create({
    name: 'My Server',
    game: 'cs2',
    region: 'us-east',
    config: {
      maxPlayers: 10,
      tickRate: 128,
    },
  });
} catch (error) {
  if (error instanceof SweatHostError && error.code === 'PAYMENT_REQUIRED') {
    console.error('Insufficient credits');
    console.error(`Balance: $${error.details?.balance}`);
    console.error(`Required: $${error.details?.required}`);
    console.error('Add credits at: https://dashboard.sweathost.com/billing');
  }
}

Network Errors

try {
  const servers = await client.gameServers.list();
} catch (error) {
  if (error instanceof SweatHostError) {
    if (error.code === 'NETWORK_ERROR') {
      console.error('Network connection failed');
      console.error('Please check your internet connection');
    } else if (error.code === 'TIMEOUT') {
      console.error('Request timed out');
      console.error('The server took too long to respond');
    } else if (error.code === 'SERVICE_UNAVAILABLE') {
      console.error('Service temporarily unavailable');
      console.error('Please try again later');
    }
  }
}

Advanced Error Handling

Global Error Handler

class ErrorHandler {
  static handle(error: unknown): void {
    if (error instanceof SweatHostError) {
      this.handleSweatHostError(error);
    } else if (error instanceof Error) {
      this.handleGenericError(error);
    } else {
      console.error('Unknown error:', error);
    }
  }

  private static handleSweatHostError(error: SweatHostError): void {
    console.error(`[${error.code}] ${error.message}`);
    
    if (error.requestId) {
      console.error(`Request ID: ${error.requestId}`);
    }

    // Log to monitoring service
    this.logToMonitoring(error);

    // Send to error tracking
    this.sendToErrorTracking(error);
  }

  private static handleGenericError(error: Error): void {
    console.error(`Unexpected error: ${error.message}`);
    console.error(error.stack);
  }

  private static logToMonitoring(error: SweatHostError): void {
    // Send to your monitoring service (DataDog, New Relic, etc.)
  }

  private static sendToErrorTracking(error: SweatHostError): void {
    // Send to error tracking (Sentry, Rollbar, etc.)
  }
}

// Usage
try {
  await client.gameServers.create({...});
} catch (error) {
  ErrorHandler.handle(error);
}

Retry Logic

async function withRetry<T>(
  operation: () => Promise<T>,
  options: {
    maxRetries?: number;
    retryDelay?: number;
    retryOn?: string[];
  } = {}
): Promise<T> {
  const {
    maxRetries = 3,
    retryDelay = 1000,
    retryOn = ['NETWORK_ERROR', 'TIMEOUT', 'SERVICE_UNAVAILABLE'],
  } = options;

  let lastError: Error;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error as Error;

      if (
        error instanceof SweatHostError &&
        retryOn.includes(error.code) &&
        attempt < maxRetries
      ) {
        console.warn(`Attempt ${attempt + 1} failed. Retrying in ${retryDelay}ms...`);
        await new Promise(resolve => setTimeout(resolve, retryDelay * (attempt + 1)));
        continue;
      }

      throw error;
    }
  }

  throw lastError!;
}

// Usage
const server = await withRetry(
  () => client.gameServers.get('server_123'),
  {
    maxRetries: 5,
    retryDelay: 2000,
  }
);

Circuit Breaker

class CircuitBreaker {
  private failures = 0;
  private lastFailureTime = 0;
  private state: 'closed' | 'open' | 'half-open' = 'closed';

  constructor(
    private threshold = 5,
    private timeout = 60000
  ) {}

  async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'half-open';
      } else {
        throw new Error('Circuit breaker is open');
      }
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failures = 0;
    this.state = 'closed';
  }

  private onFailure(): void {
    this.failures++;
    this.lastFailureTime = Date.now();

    if (this.failures >= this.threshold) {
      this.state = 'open';
      console.warn('Circuit breaker opened');
    }
  }
}

// Usage
const breaker = new CircuitBreaker();

try {
  const server = await breaker.execute(() =>
    client.gameServers.get('server_123')
  );
} catch (error) {
  console.error('Operation failed:', error);
}

Error Logging

Structured Logging

import { SweatHostError } from '@sweathost/sdk';

function logError(error: unknown, context?: Record<string, any>): void {
  const logEntry = {
    timestamp: new Date().toISOString(),
    level: 'error',
    context,
  };

  if (error instanceof SweatHostError) {
    Object.assign(logEntry, {
      errorCode: error.code,
      errorMessage: error.message,
      statusCode: error.statusCode,
      requestId: error.requestId,
      details: error.details,
    });
  } else if (error instanceof Error) {
    Object.assign(logEntry, {
      errorMessage: error.message,
      stack: error.stack,
    });
  }

  console.error(JSON.stringify(logEntry));
}

// Usage
try {
  await client.gameServers.create({...});
} catch (error) {
  logError(error, {
    operation: 'create_server',
    userId: 'user_123',
  });
}

Best Practices

1. Always Handle Errors

// ❌ Bad
const server = await client.gameServers.get('server_123');

// ✅ Good
try {
  const server = await client.gameServers.get('server_123');
} catch (error) {
  console.error('Failed to get server:', error);
}

2. Check Error Types

// ❌ Bad
catch (error) {
  console.error(error.code); // May not exist
}

// ✅ Good
catch (error) {
  if (error instanceof SweatHostError) {
    console.error(error.code);
  }
}

3. Provide Context

// ❌ Bad
throw new Error('Failed');

// ✅ Good
throw new Error(`Failed to create server "${serverName}" in region "${region}"`);

4. Log Request IDs

catch (error) {
  if (error instanceof SweatHostError && error.requestId) {
    console.error(`Request ID: ${error.requestId}`);
    // Include in support requests
  }
}

5. Handle Rate Limits Gracefully

// Implement exponential backoff
async function withBackoff<T>(operation: () => Promise<T>): Promise<T> {
  let delay = 1000;
  
  while (true) {
    try {
      return await operation();
    } catch (error) {
      if (error instanceof SweatHostError && error.code === 'RATE_LIMIT_EXCEEDED') {
        await new Promise(resolve => setTimeout(resolve, delay));
        delay *= 2; // Exponential backoff
        continue;
      }
      throw error;
    }
  }
}

Testing Error Handling

import { describe, it, expect } from '@jest/globals';
import { SweatHostClient, SweatHostError } from '@sweathost/sdk';

describe('Error Handling', () => {
  it('should handle not found errors', async () => {
    const client = new SweatHostClient({
      apiKey: 'test_key',
    });

    try {
      await client.gameServers.get('invalid_id');
      expect.fail('Should have thrown error');
    } catch (error) {
      expect(error).toBeInstanceOf(SweatHostError);
      expect((error as SweatHostError).code).toBe('NOT_FOUND');
      expect((error as SweatHostError).statusCode).toBe(404);
    }
  });

  it('should handle validation errors', async () => {
    const client = new SweatHostClient({
      apiKey: 'test_key',
    });

    try {
      await client.gameServers.create({
        name: '',
        game: 'cs2',
        region: 'us-east',
        config: {
          maxPlayers: -1,
          tickRate: 128,
        },
      });
      expect.fail('Should have thrown error');
    } catch (error) {
      expect(error).toBeInstanceOf(SweatHostError);
      expect((error as SweatHostError).code).toBe('VALIDATION_ERROR');
    }
  });
});

Next Steps

On this page