vibetunnel/mac/VibeTunnel/Core/Utilities/ErrorHandling.swift
Peter Steinberger 288a3197d2 new auth logic
2025-06-24 01:47:45 +02:00

165 lines
4.2 KiB
Swift

import SwiftUI
// MARK: - Error Alert Modifier
/// A view modifier that presents errors using SwiftUI's built-in alert system
struct ErrorAlertModifier: ViewModifier {
@Binding var error: Error?
let title: String
let onDismiss: (() -> Void)?
func body(content: Content) -> some View {
content
.alert(
title,
isPresented: .constant(error != nil),
presenting: error
) { _ in
Button("OK") {
error = nil
onDismiss?()
}
} message: { error in
Text(error.localizedDescription)
}
}
}
extension View {
/// Presents an error alert when an error is present
func errorAlert(
_ title: String = "Error",
error: Binding<Error?>,
onDismiss: (() -> Void)? = nil
)
-> some View
{
modifier(ErrorAlertModifier(error: error, title: title, onDismiss: onDismiss))
}
}
// MARK: - Task Error Handling
extension Task where Failure == Error {
/// Executes an async operation with error handling on the MainActor
@MainActor
@discardableResult
static func withErrorHandling<T>(
priority: TaskPriority? = nil,
errorBinding: Binding<Error?>,
operation: @escaping () async throws -> T
)
-> Task<T, Error>
{
Task<T, Error>(priority: priority) {
do {
return try await operation()
} catch {
errorBinding.wrappedValue = error
throw error
}
}
}
}
// MARK: - Error Recovery Protocol
/// Protocol for errors that can provide recovery actions
protocol RecoverableError: Error {
var recoverySuggestion: String? { get }
var recoveryActions: [ErrorRecoveryAction]? { get }
}
struct ErrorRecoveryAction {
let title: String
let action: () async throws -> Void
}
// MARK: - Error Toast View
/// A toast-style error notification
struct ErrorToast: View {
let error: Error
let onDismiss: () -> Void
@State private var opacity: Double = 0
var body: some View {
HStack(spacing: 12) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.red)
VStack(alignment: .leading, spacing: 4) {
Text("Error")
.font(.headline)
Text(error.localizedDescription)
.font(.subheadline)
.foregroundColor(.secondary)
.lineLimit(2)
}
Spacer()
Button(action: onDismiss) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
}
.buttonStyle(PlainButtonStyle())
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color(NSColor.controlBackgroundColor))
.shadow(radius: 10)
)
.padding()
.opacity(opacity)
.onAppear {
withAnimation(.easeIn(duration: 0.3)) {
opacity = 1
}
}
}
}
// MARK: - Error State Management
// AsyncState property wrapper removed as it's not used in the codebase
// MARK: - Async Error Boundary
/// A view that catches and displays errors from async operations
struct AsyncErrorBoundary<Content: View>: View {
@State private var error: Error?
let content: () -> Content
var body: some View {
content()
.environment(\.asyncErrorHandler, AsyncErrorHandler { error in
self.error = error
})
.errorAlert(error: $error)
}
}
// MARK: - Environment Values
private struct AsyncErrorHandlerKey: EnvironmentKey {
nonisolated(unsafe) static let defaultValue = AsyncErrorHandler { _ in }
}
extension EnvironmentValues {
var asyncErrorHandler: AsyncErrorHandler {
get { self[AsyncErrorHandlerKey.self] }
set { self[AsyncErrorHandlerKey.self] = newValue }
}
}
struct AsyncErrorHandler {
let handler: (Error) -> Void
func handle(_ error: Error) {
handler(error)
}
}