Skip to main content

Overview

This guide covers robust error handling patterns for production use:
  • Catching specific errors
  • Implementing fallback behavior
  • Retry logic with exponential backoff
  • Logging and monitoring
  • Graceful degradation

Basic Error Handling

Handle the most common errors:
package main

import (
	"errors"
	"fmt"
	"log"

	"github.com/switchport-ai/switchport-go/switchport"
)

func main() {
	client, err := switchport.NewClient("")
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	response, err := client.Prompts.Execute("my-prompt", nil, nil)
	if err != nil {
		var authErr *switchport.AuthenticationError
		var promptNotFound *switchport.PromptNotFoundError
		var apiErr *switchport.APIError

		switch {
		case errors.As(err, &authErr):
			fmt.Println("Authentication failed. Check your API key.")
		case errors.As(err, &promptNotFound):
			fmt.Println("Prompt not found. Create it in the dashboard.")
		case errors.As(err, &apiErr):
			fmt.Printf("API error: %v\n", apiErr)
			fmt.Printf("Status code: %d\n", apiErr.StatusCode)
		default:
			fmt.Printf("Unexpected error: %v\n", err)
		}
		return
	}

	fmt.Println(response.Text)
}

Fallback Behavior

Provide default responses when the API fails:
package main

import (
	"fmt"
	"log"

	"github.com/switchport-ai/switchport-go/switchport"
)

func getWelcomeMessage(client *switchport.Client, userName string) string {
	// Try to get AI-generated message
	response, err := client.Prompts.Execute(
		"welcome-message",
		nil,
		map[string]interface{}{"name": userName},
	)

	if err != nil {
		var switchportErr *switchport.SwitchportError
		if errors.As(err, &switchportErr) {
			log.Printf("Switchport error: %v", err)
		}

		// Fallback to default message
		return fmt.Sprintf("Welcome, %s! Thanks for joining us.", userName)
	}

	return response.Text
}

func main() {
	client, err := switchport.NewClient("")
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	message := getWelcomeMessage(client, "Alice")
	fmt.Println(message)
}

Retry Logic with Exponential Backoff

Implement retry logic for transient failures:
package main

import (
	"errors"
	"fmt"
	"log"
	"time"

	"github.com/switchport-ai/switchport-go/switchport"
)

func executeWithRetry(
	client *switchport.Client,
	promptKey string,
	subject switchport.Subject,
	variables map[string]interface{},
	maxRetries int,
	initialDelay time.Duration,
) (*switchport.PromptResponse, error) {
	delay := initialDelay

	for attempt := 0; attempt < maxRetries; attempt++ {
		response, err := client.Prompts.Execute(promptKey, subject, variables)

		if err == nil {
			return response, nil
		}

		// Check if we should retry
		var apiErr *switchport.APIError
		shouldRetry := false

		if errors.As(err, &apiErr) {
			// Retry on 5xx errors or rate limiting (429)
			if apiErr.StatusCode >= 500 || apiErr.StatusCode == 429 {
				shouldRetry = true
			}
		}

		if shouldRetry && attempt < maxRetries-1 {
			log.Printf("Attempt %d failed. Retrying in %v...", attempt+1, delay)
			time.Sleep(delay)
			delay *= 2 // Exponential backoff
		} else {
			return nil, err
		}
	}

	return nil, fmt.Errorf("max retries (%d) reached", maxRetries)
}

func main() {
	client, err := switchport.NewClient("")
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	response, err := executeWithRetry(
		client,
		"my-prompt",
		nil,
		map[string]interface{}{"name": "Alice"},
		3,                  // max retries
		1*time.Second,      // initial delay
	)

	if err != nil {
		log.Printf("Failed after retries: %v", err)
		return
	}

	fmt.Println(response.Text)
}

Logging for Production

Comprehensive logging for debugging and monitoring:
package main

import (
	"log"

	"github.com/switchport-ai/switchport-go/switchport"
)

type PromptExecutor struct {
	client *switchport.Client
	logger *log.Logger
}

func NewPromptExecutor(client *switchport.Client) *PromptExecutor {
	return &PromptExecutor{
		client: client,
		logger: log.Default(),
	}
}

func (e *PromptExecutor) ExecuteWithLogging(
	promptKey string,
	subject switchport.Subject,
	variables map[string]interface{},
) (*switchport.PromptResponse, error) {
	e.logger.Printf("Executing prompt: key=%s, subject=%v, variables=%v",
		promptKey, subject, variables)

	response, err := e.client.Prompts.Execute(promptKey, subject, variables)

	if err != nil {
		e.logger.Printf("Prompt execution failed: key=%s, error=%v",
			promptKey, err)
		return nil, err
	}

	e.logger.Printf("Prompt executed successfully: key=%s, version=%s, request_id=%s",
		promptKey, response.VersionName, response.RequestID)

	return response, nil
}

func (e *PromptExecutor) RecordMetricWithLogging(
	metricKey string,
	value interface{},
	subject switchport.Subject,
) error {
	e.logger.Printf("Recording metric: key=%s, value=%v, subject=%v",
		metricKey, value, subject)

	result, err := e.client.Metrics.Record(metricKey, value, subject, nil)

	if err != nil {
		e.logger.Printf("Metric recording failed: key=%s, error=%v",
			metricKey, err)
		return err
	}

	e.logger.Printf("Metric recorded successfully: key=%s, event_id=%s",
		metricKey, result.MetricEventID)

	return nil
}

func main() {
	client, err := switchport.NewClient("")
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	executor := NewPromptExecutor(client)

	response, err := executor.ExecuteWithLogging(
		"welcome-message",
		map[string]interface{}{"user_id": "user_123"},
		map[string]interface{}{"name": "Alice"},
	)

	if err != nil {
		log.Printf("Error: %v", err)
		return
	}

	log.Printf("Response: %s", response.Text)
}

Circuit Breaker Pattern

Prevent cascading failures:
package main

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/switchport-ai/switchport-go/switchport"
)

type CircuitState int

const (
	StateClosed CircuitState = iota
	StateOpen
	StateHalfOpen
)

type CircuitBreaker struct {
	client         *switchport.Client
	maxFailures    int
	resetTimeout   time.Duration
	failureCount   int
	lastFailTime   time.Time
	state          CircuitState
	mu             sync.RWMutex
}

func NewCircuitBreaker(client *switchport.Client, maxFailures int, resetTimeout time.Duration) *CircuitBreaker {
	return &CircuitBreaker{
		client:       client,
		maxFailures:  maxFailures,
		resetTimeout: resetTimeout,
		state:        StateClosed,
	}
}

func (cb *CircuitBreaker) Execute(
	promptKey string,
	subject switchport.Subject,
	variables map[string]interface{},
) (*switchport.PromptResponse, error) {
	cb.mu.Lock()

	// Check if circuit should reset
	if cb.state == StateOpen && time.Since(cb.lastFailTime) > cb.resetTimeout {
		cb.state = StateHalfOpen
		cb.failureCount = 0
	}

	// Circuit is open - fail fast
	if cb.state == StateOpen {
		cb.mu.Unlock()
		return nil, errors.New("circuit breaker is open")
	}

	cb.mu.Unlock()

	// Attempt execution
	response, err := cb.client.Prompts.Execute(promptKey, subject, variables)

	cb.mu.Lock()
	defer cb.mu.Unlock()

	if err != nil {
		cb.failureCount++
		cb.lastFailTime = time.Now()

		if cb.failureCount >= cb.maxFailures {
			cb.state = StateOpen
			fmt.Printf("Circuit breaker opened after %d failures\n", cb.failureCount)
		}

		return nil, err
	}

	// Success - reset circuit
	if cb.state == StateHalfOpen {
		cb.state = StateClosed
		cb.failureCount = 0
		fmt.Println("Circuit breaker closed - service recovered")
	}

	return response, nil
}

func main() {
	client, err := switchport.NewClient("")
	if err != nil {
		fmt.Printf("Failed to create client: %v\n", err)
		return
	}

	cb := NewCircuitBreaker(client, 3, 30*time.Second)

	// Simulate multiple requests
	for i := 0; i < 10; i++ {
		response, err := cb.Execute(
			"my-prompt",
			map[string]interface{}{"request_num": i},
			nil,
		)

		if err != nil {
			fmt.Printf("Request %d failed: %v\n", i, err)
			time.Sleep(1 * time.Second)
			continue
		}

		fmt.Printf("Request %d succeeded: %s\n", i, response.VersionName)
	}
}

Graceful Degradation

Handle API unavailability gracefully:
package main

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/switchport-ai/switchport-go/switchport"
)

type MessageService struct {
	client        *switchport.Client
	cache         map[string]string
	fallbackMode  bool
}

func NewMessageService(client *switchport.Client) *MessageService {
	return &MessageService{
		client:       client,
		cache:        make(map[string]string),
		fallbackMode: false,
	}
}

func (s *MessageService) GetMessage(userID, userName string) string {
	// Try cache first
	if cached, ok := s.cache[userID]; ok {
		return cached
	}

	// Try API with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	responseChan := make(chan string, 1)
	errorChan := make(chan error, 1)

	go func() {
		response, err := s.client.Prompts.Execute(
			"welcome-message",
			map[string]interface{}{"user_id": userID},
			map[string]interface{}{"name": userName},
		)
		if err != nil {
			errorChan <- err
			return
		}
		responseChan <- response.Text
	}()

	select {
	case <-ctx.Done():
		// Timeout - use fallback
		s.fallbackMode = true
		return s.getFallbackMessage(userName)
	case err := <-errorChan:
		var apiErr *switchport.APIError
		if errors.As(err, &apiErr) && apiErr.StatusCode >= 500 {
			s.fallbackMode = true
		}
		return s.getFallbackMessage(userName)
	case message := <-responseChan:
		// Success - cache and return
		s.cache[userID] = message
		s.fallbackMode = false
		return message
	}
}

func (s *MessageService) getFallbackMessage(userName string) string {
	return fmt.Sprintf("Welcome, %s! We're glad to have you here.", userName)
}

func (s *MessageService) IsInFallbackMode() bool {
	return s.fallbackMode
}

func main() {
	client, err := switchport.NewClient("")
	if err != nil {
		fmt.Printf("Failed to create client: %v\n", err)
		return
	}

	service := NewMessageService(client)

	// Get messages for multiple users
	users := map[string]string{
		"user_001": "Alice",
		"user_002": "Bob",
		"user_003": "Carol",
	}

	for userID, userName := range users {
		message := service.GetMessage(userID, userName)
		fmt.Printf("%s: %s\n", userName, message)
	}

	if service.IsInFallbackMode() {
		fmt.Println("\n⚠️  Service is in fallback mode")
	}
}

Error Reporting and Monitoring

Integration with error tracking services:
package main

import (
	"errors"
	"fmt"
	"log"

	"github.com/switchport-ai/switchport-go/switchport"
)

type ErrorReporter interface {
	Report(err error, context map[string]interface{})
}

type ConsoleReporter struct{}

func (r *ConsoleReporter) Report(err error, context map[string]interface{}) {
	log.Printf("Error reported: %v, Context: %v", err, context)
}

type SwitchportClient struct {
	client   *switchport.Client
	reporter ErrorReporter
}

func NewSwitchportClient(client *switchport.Client, reporter ErrorReporter) *SwitchportClient {
	return &SwitchportClient{
		client:   client,
		reporter: reporter,
	}
}

func (sc *SwitchportClient) ExecutePrompt(
	promptKey string,
	subject switchport.Subject,
	variables map[string]interface{},
) (*switchport.PromptResponse, error) {
	response, err := sc.client.Prompts.Execute(promptKey, subject, variables)

	if err != nil {
		// Report error with user identification
		errorContext := map[string]interface{}{
			"operation":  "execute_prompt",
			"prompt_key": promptKey,
			"context":    context,
		}

		var apiErr *switchport.APIError
		if errors.As(err, &apiErr) {
			errorContext["status_code"] = apiErr.StatusCode
			errorContext["response_data"] = apiErr.ResponseData
		}

		sc.reporter.Report(err, errorContext)
		return nil, err
	}

	return response, nil
}

func main() {
	client, err := switchport.NewClient("")
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	reporter := &ConsoleReporter{}
	scClient := NewSwitchportClient(client, reporter)

	response, err := scClient.ExecutePrompt(
		"my-prompt",
		map[string]interface{}{"user_id": "user_123"},
		nil,
	)

	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println(response.Text)
}

Best Practices

Never let API failures completely break your application. Always have a fallback strategy.
Set reasonable timeouts for API calls to prevent hanging requests.
Include request IDs, user IDs, and other context in your logs for easier debugging.
Track error rates and set up alerts for unusual spikes.
Only retry on transient errors (5xx, network issues). Don’t retry on 4xx errors.
Implement circuit breakers to prevent cascading failures in high-traffic applications.
Regularly test your error handling code to ensure fallbacks work as expected.

Next Steps

API Reference

See all error types and their properties

Basic Examples

Learn basic SDK usage patterns