---
title: "Error Handling"
description: "Error handling patterns and error types in the TraceRTM Go SDK"
---

# Error Handling

The TraceRTM Go SDK provides typed errors for precise error handling.

## Error Types

### NotFoundError

Returned when a resource doesn't exist:

```go
type NotFoundError struct {
    ResourceType string // "project", "item", "link", etc.
    ResourceID   string
    Message      string
}

func (e *NotFoundError) Error() string
```

```go
item, err := client.Items.Get(ctx, "item_nonexistent")
if err != nil {
    var notFoundErr *tracertm.NotFoundError
    if errors.As(err, &notFoundErr) {
        fmt.Printf("Resource not found: %s %s\n", notFoundErr.ResourceType, notFoundErr.ResourceID)
    }
}
```

### ValidationError

Returned when input validation fails:

```go
type ValidationError struct {
    Message string
    Fields  []FieldError
}

type FieldError struct {
    Field   string
    Message string
    Code    string
}

func (e *ValidationError) Error() string
```

```go
item, err := client.Items.Create(ctx, projectID, &tracertm.CreateItemInput{
    Type: "invalid_type",
})
if err != nil {
    var validationErr *tracertm.ValidationError
    if errors.As(err, &validationErr) {
        fmt.Println("Validation failed:")
        for _, field := range validationErr.Fields {
            fmt.Printf("  - %s: %s\n", field.Field, field.Message)
        }
    }
}
```

### AuthenticationError

Returned when authentication fails:

```go
type AuthenticationError struct {
    Message string
}

func (e *AuthenticationError) Error() string
```

```go
_, err := client.Projects.List(ctx, nil)
if err != nil {
    var authErr *tracertm.AuthenticationError
    if errors.As(err, &authErr) {
        fmt.Println("Authentication failed - check your API key")
    }
}
```

### ForbiddenError

Returned when access is denied:

```go
type ForbiddenError struct {
    Message string
    Action  string
    Resource string
}

func (e *ForbiddenError) Error() string
```

```go
err := client.Projects.Delete(ctx, "proj_protected")
if err != nil {
    var forbiddenErr *tracertm.ForbiddenError
    if errors.As(err, &forbiddenErr) {
        fmt.Printf("Access denied: %s on %s\n", forbiddenErr.Action, forbiddenErr.Resource)
    }
}
```

### RateLimitError

Returned when rate limit is exceeded:

```go
type RateLimitError struct {
    Message    string
    RetryAfter int // Seconds to wait before retrying
    Limit      int
    Remaining  int
}

func (e *RateLimitError) Error() string
```

```go
_, err := client.Items.List(ctx, projectID, nil)
if err != nil {
    var rateLimitErr *tracertm.RateLimitError
    if errors.As(err, &rateLimitErr) {
        fmt.Printf("Rate limited. Retry after %d seconds\n", rateLimitErr.RetryAfter)
        time.Sleep(time.Duration(rateLimitErr.RetryAfter) * time.Second)
        // Retry
    }
}
```

### ServerError

Returned for server-side errors:

```go
type ServerError struct {
    StatusCode int
    Message    string
    RequestID  string
}

func (e *ServerError) Error() string
```

```go
_, err := client.Items.Get(ctx, "item_123")
if err != nil {
    var serverErr *tracertm.ServerError
    if errors.As(err, &serverErr) {
        fmt.Printf("Server error (request ID: %s): %s\n", serverErr.RequestID, serverErr.Message)
        // Report to support with request ID
    }
}
```

### ConflictError

Returned for conflicts (e.g., duplicate key):

```go
type ConflictError struct {
    Message string
    Field   string
    Value   string
}

func (e *ConflictError) Error() string
```

```go
_, err := client.Projects.Create(ctx, &tracertm.CreateProjectInput{
    Name: "Duplicate Project",
    Key:  "EXISTING_KEY",
})
if err != nil {
    var conflictErr *tracertm.ConflictError
    if errors.As(err, &conflictErr) {
        fmt.Printf("Conflict on %s: %s already exists\n", conflictErr.Field, conflictErr.Value)
    }
}
```

---

## Error Handling Patterns

### Type Switch Pattern

```go
import "errors"

func handleError(err error) {
    var notFoundErr *tracertm.NotFoundError
    var validationErr *tracertm.ValidationError
    var authErr *tracertm.AuthenticationError
    var forbiddenErr *tracertm.ForbiddenError
    var rateLimitErr *tracertm.RateLimitError
    var serverErr *tracertm.ServerError
    var conflictErr *tracertm.ConflictError

    switch {
    case errors.As(err, &notFoundErr):
        // Handle not found
        fmt.Printf("Not found: %s\n", notFoundErr.ResourceID)

    case errors.As(err, &validationErr):
        // Handle validation error
        for _, f := range validationErr.Fields {
            fmt.Printf("Invalid %s: %s\n", f.Field, f.Message)
        }

    case errors.As(err, &authErr):
        // Handle authentication error
        fmt.Println("Please check your API key")

    case errors.As(err, &forbiddenErr):
        // Handle forbidden error
        fmt.Println("Access denied")

    case errors.As(err, &rateLimitErr):
        // Handle rate limit
        fmt.Printf("Please wait %d seconds\n", rateLimitErr.RetryAfter)

    case errors.As(err, &serverErr):
        // Handle server error
        fmt.Printf("Server error (ID: %s)\n", serverErr.RequestID)

    case errors.As(err, &conflictErr):
        // Handle conflict
        fmt.Printf("Conflict: %s\n", conflictErr.Message)

    default:
        // Handle unknown error
        fmt.Printf("Unexpected error: %v\n", err)
    }
}
```

### Retry Pattern

```go
func withRetry[T any](ctx context.Context, maxRetries int, fn func() (T, error)) (T, error) {
    var result T
    var lastErr error

    for attempt := 0; attempt <= maxRetries; attempt++ {
        result, lastErr = fn()
        if lastErr == nil {
            return result, nil
        }

        // Check if error is retryable
        var rateLimitErr *tracertm.RateLimitError
        var serverErr *tracertm.ServerError

        switch {
        case errors.As(lastErr, &rateLimitErr):
            // Wait as instructed
            select {
            case <-ctx.Done():
                return result, ctx.Err()
            case <-time.After(time.Duration(rateLimitErr.RetryAfter) * time.Second):
            }

        case errors.As(lastErr, &serverErr):
            // Exponential backoff for server errors
            if attempt < maxRetries {
                backoff := time.Duration(1<<attempt) * time.Second
                select {
                case <-ctx.Done():
                    return result, ctx.Err()
                case <-time.After(backoff):
                }
            }

        default:
            // Non-retryable error
            return result, lastErr
        }
    }

    return result, fmt.Errorf("max retries exceeded: %w", lastErr)
}

// Usage
item, err := withRetry(ctx, 3, func() (*tracertm.Item, error) {
    return client.Items.Get(ctx, "item_123")
})
```

### Error Wrapping

```go
func getRequirementWithContext(ctx context.Context, client *tracertm.Client, id string) (*tracertm.Item, error) {
    item, err := client.Items.Get(ctx, id)
    if err != nil {
        return nil, fmt.Errorf("failed to get requirement %s: %w", id, err)
    }
    return item, nil
}

// Caller can still check error type
item, err := getRequirementWithContext(ctx, client, "item_123")
if err != nil {
    var notFoundErr *tracertm.NotFoundError
    if errors.As(err, &notFoundErr) {
        // Original error type is preserved
    }
}
```

---

## Context Errors

### Handling Context Cancellation

```go
func fetchWithTimeout(ctx context.Context, client *tracertm.Client, id string) (*tracertm.Item, error) {
    ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
    defer cancel()

    item, err := client.Items.Get(ctx, id)
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            return nil, fmt.Errorf("request timed out")
        }
        if errors.Is(err, context.Canceled) {
            return nil, fmt.Errorf("request canceled")
        }
        return nil, err
    }
    return item, nil
}
```

### Combining Error Checks

```go
func processItem(ctx context.Context, client *tracertm.Client, id string) error {
    item, err := client.Items.Get(ctx, id)
    if err != nil {
        // Context errors first
        if errors.Is(err, context.DeadlineExceeded) {
            return fmt.Errorf("timeout getting item: %w", err)
        }
        if errors.Is(err, context.Canceled) {
            return fmt.Errorf("canceled getting item: %w", err)
        }

        // API errors
        var notFoundErr *tracertm.NotFoundError
        if errors.As(err, &notFoundErr) {
            return fmt.Errorf("item %s not found", id)
        }

        // Other errors
        return fmt.Errorf("failed to get item: %w", err)
    }

    // Process item...
    return nil
}
```

---

## Error Response Format

### API Error Response Structure

```go
// Error response from the API
type APIError struct {
    Error struct {
        Type    string       `json:"type"`
        Message string       `json:"message"`
        Code    string       `json:"code"`
        Fields  []FieldError `json:"fields,omitempty"`
    } `json:"error"`
    RequestID string `json:"request_id"`
}
```

### Accessing Error Details

```go
item, err := client.Items.Create(ctx, projectID, &tracertm.CreateItemInput{})
if err != nil {
    var validationErr *tracertm.ValidationError
    if errors.As(err, &validationErr) {
        fmt.Printf("Message: %s\n", validationErr.Message)

        for _, field := range validationErr.Fields {
            fmt.Printf("Field: %s\n", field.Field)
            fmt.Printf("Message: %s\n", field.Message)
            fmt.Printf("Code: %s\n", field.Code)
        }
    }
}
```

---

## Best Practices

### 1. Always Check Errors

```go
// Good
item, err := client.Items.Get(ctx, id)
if err != nil {
    return nil, err
}

// Bad - ignoring error
item, _ := client.Items.Get(ctx, id)
```

### 2. Use Typed Error Checks

```go
// Good - specific error handling
var notFoundErr *tracertm.NotFoundError
if errors.As(err, &notFoundErr) {
    // Handle not found specifically
}

// Bad - string matching
if strings.Contains(err.Error(), "not found") {
    // Fragile, may break
}
```

### 3. Wrap Errors with Context

```go
// Good - adds context
if err != nil {
    return fmt.Errorf("fetching requirement %s: %w", id, err)
}

// Bad - loses original error
if err != nil {
    return fmt.Errorf("fetch failed")
}
```

### 4. Handle Retryable Errors

```go
// Good - retry on transient errors
if isRetryable(err) {
    time.Sleep(backoff)
    continue
}

func isRetryable(err error) bool {
    var rateLimitErr *tracertm.RateLimitError
    var serverErr *tracertm.ServerError
    return errors.As(err, &rateLimitErr) || errors.As(err, &serverErr)
}
```

### 5. Log Error Details

```go
// Good - log relevant details
if err != nil {
    var serverErr *tracertm.ServerError
    if errors.As(err, &serverErr) {
        log.Printf("Server error (request_id=%s, status=%d): %s",
            serverErr.RequestID, serverErr.StatusCode, serverErr.Message)
    }
}
```

---

## Related Documentation

- [API Reference](/docs/api-reference/sdks/go/api-reference) - Full API reference
- [Testing](/docs/api-reference/sdks/go/testing) - Test error scenarios
- [Logging](/docs/api-reference/sdks/go/logging) - Log errors properly
