> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling Patterns

> Advanced error handling — classification, telemetry, and operational patterns

# Error Handling Patterns

This page covers advanced error handling — classification, telemetry, structured logging, and circuit breakers. For the basics (try/catch, retry, re-auth, user-friendly messages), see the [Error Handling guide](../guides/error-handling).

## Error Classification

`PrivacyBoostDefaults` ships an `SdkError.category` extension that buckets every variant into one of seven categories. Use it to make UI/operational decisions without exhaustively matching every case.

```swift theme={null}
import PrivacyBoost
import PrivacyBoostDefaults

extension SdkError {
    var category: ErrorCategory { /* .auth, .network, .validation, .permission, .rateLimit, .server, .internal */ }
    var isRetryable: Bool { /* network, rate-limit, retryable api errors */ }
}
```

| Category      | Meaning                          | Recommended UI                    | Retry?                          |
| ------------- | -------------------------------- | --------------------------------- | ------------------------------- |
| `.auth`       | Session/auth flow failed         | Re-prompt sign-in                 | No (but re-auth and try once)   |
| `.network`    | Connectivity / DNS / timeout     | "Check your connection"           | Yes, with backoff               |
| `.validation` | Input is malformed               | Inline form error                 | No                              |
| `.permission` | User cancelled / forbidden       | Silent dismiss or contact support | No                              |
| `.rateLimit`  | Too many requests                | Wait + auto-retry                 | Yes, after `retryAfterMs`       |
| `.server`     | Backend / proof / merkle failure | Generic error toast               | Sometimes (check `isRetryable`) |
| `.internal`   | Bug or invariant violation       | "Something went wrong" + report   | No                              |

## Structured Logging

Log SDK errors with stable fields so they're queryable in Splunk / Datadog / OS Console.

```swift theme={null}
import os.log

enum PBLogger {
    private static let log = Logger(subsystem: "com.example.app", category: "privacy-boost")

    static func error(_ context: String, _ error: Error) {
        guard let sdk = error as? SdkError else {
            log.error("[\(context, privacy: .public)] non-sdk error: \(error.localizedDescription, privacy: .public)")
            return
        }

        log.error("""
        [\(context, privacy: .public)] \
        category=\(String(describing: sdk.category), privacy: .public) \
        retryable=\(sdk.isRetryable) \
        variant=\(self.variantTag(sdk), privacy: .public) \
        message=\(self.userMessage(sdk), privacy: .public)
        """)
    }

    private static func variantTag(_ error: SdkError) -> String {
        switch error {
        case .NotConnected: return "NotConnected"
        case .NotAuthenticated: return "NotAuthenticated"
        case .AuthenticationFailed: return "AuthenticationFailed"
        case .NetworkError: return "NetworkError"
        case .WalletError: return "WalletError"
        case .SignatureRejected: return "SignatureRejected"
        case .InsufficientBalance: return "InsufficientBalance"
        case .InvalidAddress: return "InvalidAddress"
        case .InvalidAmount: return "InvalidAmount"
        case .RateLimited: return "RateLimited"
        case .ApiError(let code, _, _): return "ApiError.\(code)"
        case .ShieldError(let code, _): return "ShieldError.\(code)"
        case .TransferError(let code, _): return "TransferError.\(code)"
        default: return "Other"
        }
    }

    private static func userMessage(_ error: SdkError) -> String {
        switch error {
        case .NetworkError(let m), .WalletError(let m), .InternalError(let m),
             .Forbidden(let m), .ResourceNotFound(let m):
            return m
        case .ApiError(_, let m, _), .ShieldError(_, let m), .TransferError(_, let m):
            return m
        default:
            return ""
        }
    }
}
```

`privacy: .public` on the variant/category fields keeps them queryable in Console.app; the message field is intentionally not marked public if it could include PII — review your own usage.

## Error Reporting Integration

### Sentry

```swift theme={null}
import Sentry

func reportSDK(_ error: Error, operation: String) {
    let event = Event(level: .error)
    event.message = SentryMessage(formatted: "PrivacyBoost.\(operation) failed")

    if let sdk = error as? SdkError {
        event.tags = [
            "pb.category": String(describing: sdk.category),
            "pb.retryable": String(sdk.isRetryable),
            "pb.variant": variantTag(sdk),
        ]
        if case .ApiError(let code, _, _) = sdk {
            event.tags?["pb.api_code"] = code
        }
    }

    SentrySDK.capture(event: event)
}
```

The `pb.category` tag lets you build a dashboard that groups errors by class — "auth-class errors are spiking" is more actionable than "twelve different variants are spiking."

### Filter Out Noise

User cancellations (`.signatureRejected`) and validation errors (`.invalidAmount`, `.invalidAddress`) aren't bugs — don't ship them to your error tracker. Use a `beforeSend`-style filter:

```swift theme={null}
func shouldReport(_ error: SdkError) -> Bool {
    switch error.category {
    case .permission, .validation: return false
    default: return true
    }
}
```

## Operation-Scoped Wrapping

Wrap each meaningful SDK call in a function that handles logging, reporting, and retry — application code stays clean.

```swift theme={null}
@discardableResult
func runSDK<T>(
    _ operation: String,
    perform: () async throws -> T
) async throws -> T {
    do {
        return try await perform()
    } catch let error as SdkError {
        PBLogger.error(operation, error)
        if shouldReport(error) {
            reportSDK(error, operation: operation)
        }
        throw error
    } catch {
        PBLogger.error(operation, error)
        reportSDK(error, operation: operation)
        throw error
    }
}

// Usage
let result = try await runSDK("shield") {
    try await sdk.shield(tokenAddress: token, amount: amount)
}
```

## Circuit Breaker

If the backend is degraded, hammering it with retries makes things worse. Wrap operations in a circuit breaker that trips after consecutive server-class failures.

```swift theme={null}
actor CircuitBreaker {
    enum State { case closed, open(until: Date), halfOpen }

    private var state: State = .closed
    private var consecutiveFailures = 0
    private let threshold: Int
    private let cooldown: TimeInterval

    init(threshold: Int = 5, cooldown: TimeInterval = 60) {
        self.threshold = threshold
        self.cooldown = cooldown
    }

    func canPass() -> Bool {
        switch state {
        case .closed, .halfOpen:
            return true
        case .open(let until):
            if Date() >= until {
                state = .halfOpen
                return true
            }
            return false
        }
    }

    func recordSuccess() {
        consecutiveFailures = 0
        state = .closed
    }

    func recordFailure(_ error: SdkError) {
        switch error.category {
        case .server, .network:
            consecutiveFailures += 1
            if consecutiveFailures >= threshold {
                state = .open(until: Date().addingTimeInterval(cooldown))
            }
        default:
            break
        }
    }
}

let breaker = CircuitBreaker()

func guarded<T>(_ op: () async throws -> T) async throws -> T {
    guard await breaker.canPass() else {
        throw SdkError.NetworkError(message: "circuit open")
    }
    do {
        let result = try await op()
        await breaker.recordSuccess()
        return result
    } catch let error as SdkError {
        await breaker.recordFailure(error)
        throw error
    }
}
```

Auth/validation/permission failures should not trip the breaker — they're user-class problems, not service degradation. The classification by `category` makes that distinction trivial.

## Telemetry: Operation Latency by Outcome

Pair errors with timings — slow successes are interesting too.

```swift theme={null}
struct OpMetric {
    let operation: String
    let durationMs: Double
    let outcome: String   // "success" | category name
}

func instrument<T>(
    _ operation: String,
    perform: () async throws -> T,
    sink: (OpMetric) -> Void
) async throws -> T {
    let start = ContinuousClock.now
    do {
        let result = try await perform()
        let elapsed = ContinuousClock.now - start
        sink(OpMetric(
            operation: operation,
            durationMs: elapsed.milliseconds,
            outcome: "success"
        ))
        return result
    } catch let error as SdkError {
        let elapsed = ContinuousClock.now - start
        sink(OpMetric(
            operation: operation,
            durationMs: elapsed.milliseconds,
            outcome: String(describing: error.category)
        ))
        throw error
    }
}

extension Duration {
    var milliseconds: Double {
        let comps = components
        return Double(comps.seconds) * 1_000 + Double(comps.attoseconds) / 1e15
    }
}
```

Forward `OpMetric` to MetricKit, Firebase Performance, or your own analytics pipeline.

## Best Practices

1. **Branch on `.category`, not on individual variants** — UI logic stays small and stable across SDK upgrades.
2. **Filter user-class errors out of error reporters** — `.permission` and `.validation` are noise.
3. **Tag every report with `pb.category` and `pb.variant`** — makes dashboards actually useful.
4. **Trip circuit breakers only on `.server` and `.network`** — never on auth or validation.
5. **Pair errors with timings** — a slow success that turns into a timeout is worth catching upstream.

## Next Steps

* [Performance](./performance)
* [Error Handling guide](../guides/error-handling) — basics
* [Error Handling Concepts](/sdk/concepts/error-handling) — cross-platform error model
