HyperbasisHyperbasisDocs
Home

Error Handling

Handle Hyperbasis errors gracefully.

Error Types

HBStorageError

The main error type for storage operations:

public enum HBStorageError: LocalizedError {
    case notFound(type: String, id: UUID)
    case encodingFailed(underlying: Error)
    case decodingFailed(underlying: Error)
    case fileSystemError(underlying: Error)
    case compressionFailed(underlying: Error)
    case decompressionFailed(underlying: Error)
    case cloudNotConfigured
    case cloudSyncFailed(underlying: Error)
    case networkError(underlying: Error)
    case unauthorized
    case serverError(statusCode: Int, message: String?)

    // Versioning errors
    case versionNotFound(anchorId: UUID, version: Int)
    case reconstructionFailed(anchorId: UUID)
    case eventLogCorrupted(spaceId: UUID)
}

HBSpaceError

Errors specific to space operations:

public enum HBSpaceError: LocalizedError {
    case serializationFailed(underlying: Error?)
    case deserializationFailed(underlying: Error?)
    case invalidWorldMapData
}

HBAnchorError

Errors specific to anchor operations:

public enum HBAnchorError: LocalizedError {
    case invalidTransform(count: Int)
    case alreadyDeleted
    case metadataKeyNotFound(key: String)
}

Handling Patterns

Basic Error Handling

do {
    try await storage.save(space)
} catch {
    print("Failed to save: \(error.localizedDescription)")
}

Pattern Matching

do {
    let space = try await storage.loadSpace(id: spaceId)
} catch HBStorageError.notFound(let type, let id) {
    print("\(type) with id \(id) not found")
} catch HBStorageError.decompressionFailed {
    print("World map data is corrupted")
} catch {
    print("Unexpected error: \(error)")
}

Versioning Errors

Handle errors when working with timeline and rollback:

do {
    try await storage.rollback(anchorId: anchorId, toVersion: 5)
} catch HBStorageError.versionNotFound(let id, let version) {
    print("Version \(version) doesn't exist for anchor \(id)")
} catch HBStorageError.reconstructionFailed(let id) {
    print("Could not reconstruct anchor \(id) from events")
} catch HBStorageError.eventLogCorrupted(let spaceId) {
    print("Event log for space \(spaceId) is corrupted")
}

Cloud Sync Errors

Cloud sync errors don't prevent local operations:

do {
    try await storage.save(space)
} catch HBStorageError.cloudSyncFailed(let underlying) {
    // Local save succeeded, cloud sync failed
    // Operation is queued for retry
    print("Will retry cloud sync: \(underlying.localizedDescription)")
} catch HBStorageError.networkError {
    // Local save succeeded, no network
    print("Saved locally, will sync when online")
} catch {
    // Local save failed - this is critical
    print("Critical error: \(error)")
}

Error Recovery

Retry Logic

func saveWithRetry(_ space: HBSpace, maxAttempts: Int = 3) async throws {
    var lastError: Error?

    for attempt in 1...maxAttempts {
        do {
            try await storage.save(space)
            return
        } catch HBStorageError.networkError {
            lastError = error
            // Wait before retry
            try await Task.sleep(nanoseconds: UInt64(attempt) * 1_000_000_000)
        } catch {
            throw error // Don't retry non-network errors
        }
    }

    throw lastError!
}

Fallback Strategies

func loadSpaceWithFallback() async throws -> HBSpace? {
    do {
        return try await storage.loadSpace(id: preferredSpaceId)
    } catch HBStorageError.notFound {
        // Try loading any available space
        let spaces = try await storage.loadAllSpaces()
        return spaces.first
    } catch HBStorageError.decompressionFailed {
        // Corrupted space, delete and start fresh
        try? await storage.deleteSpace(id: preferredSpaceId)
        return nil
    }
}

SwiftUI Integration

Error State

@MainActor
class ARViewModel: ObservableObject {
    @Published var error: AppError?

    enum AppError: LocalizedError {
        case loadFailed(Error)
        case saveFailed(Error)
        case relocalizationFailed

        var errorDescription: String? {
            switch self {
            case .loadFailed(let error):
                return "Failed to load: \(error.localizedDescription)"
            case .saveFailed(let error):
                return "Failed to save: \(error.localizedDescription)"
            case .relocalizationFailed:
                return "Could not relocalize. Try moving to a different area."
            }
        }

        var recoverySuggestion: String? {
            switch self {
            case .loadFailed:
                return "Try starting a new session."
            case .saveFailed:
                return "Check your storage space and try again."
            case .relocalizationFailed:
                return "Look around the room to help find matching features."
            }
        }
    }
}

Error Alert

struct ContentView: View {
    @StateObject var viewModel = ARViewModel()

    var body: some View {
        ARViewContainer()
            .alert(
                "Error",
                isPresented: Binding(
                    get: { viewModel.error != nil },
                    set: { if !$0 { viewModel.error = nil } }
                ),
                presenting: viewModel.error
            ) { error in
                Button("OK") { viewModel.error = nil }
                if let suggestion = error.recoverySuggestion {
                    Button("Try Again") {
                        viewModel.error = nil
                        Task { await viewModel.retry() }
                    }
                }
            } message: { error in
                VStack {
                    Text(error.localizedDescription)
                    if let suggestion = error.recoverySuggestion {
                        Text(suggestion)
                            .font(.caption)
                    }
                }
            }
    }
}

Logging

Structured Logging

import os.log

extension Logger {
    static let hyperbasis = Logger(subsystem: "com.yourapp", category: "hyperbasis")
}

func saveSpace(_ space: HBSpace) async {
    Logger.hyperbasis.info("Saving space: \(space.id)")

    do {
        try await storage.save(space)
        Logger.hyperbasis.info("Space saved successfully")
    } catch {
        Logger.hyperbasis.error("Failed to save space: \(error.localizedDescription)")
    }
}

Debug Information

extension HBStorageError {
    var debugInfo: String {
        switch self {
        case .notFound(let type, let id):
            return "NotFound: \(type) \(id)"
        case .encodingFailed(let error):
            return "Encoding: \(error)"
        case .decodingFailed(let error):
            return "Decoding: \(error)"
        case .fileSystemError(let error):
            return "FileSystem: \(error)"
        case .cloudSyncFailed(let error):
            return "CloudSync: \(error)"
        case .networkError(let error):
            return "Network: \(error)"
        case .serverError(let code, let message):
            return "Server(\(code)): \(message ?? "No message")"
        default:
            return String(describing: self)
        }
    }
}

Best Practices

  1. Always handle errors - Don't ignore errors, especially for save operations
  2. Distinguish critical vs non-critical - Cloud sync failures are recoverable; local failures are critical
  3. Provide user feedback - Show appropriate error messages and recovery options
  4. Log for debugging - Log errors with context for troubleshooting
  5. Test error paths - Use mock storage to test error handling

Next Steps

  • Examples - See error handling in context
  • Patterns - Integration patterns