Error Handling

Error handling in WaterUI is designed to integrate seamlessly with the declarative view system, allowing you to convert standard Rust errors into renderable UI components while maintaining type safety and customization flexibility.

Core Concepts

The Error Type

The Error type is a type-erased wrapper that can hold any error implementing the standard Error trait and render it as a view:

#![allow(unused)]
fn main() {
use waterui::widget::error::Error;
use std::io;

// Convert any standard error to a renderable Error
let io_error = io::Error::new(io::ErrorKind::NotFound, "Config file not found");
let ui_error = Error::new(io_error);
}

Environment-Based Error Styling

WaterUI uses the environment system to configure how errors are displayed throughout your application:

#![allow(unused)]
fn main() {
use waterui::Environment;
use waterui::widget::error::{DefaultErrorView, BoxedStdError};

let env = Environment::new()
    .with(DefaultErrorView::new(|error: BoxedStdError| {
        VStack((
            "⚠️ Application Error",
            format!("Details: {}", error),
            "Please contact support if this persists."
                .color(Color::SECONDARY)
        ))
    }));
}

Basic Usage Patterns

Converting Results to Views

The ResultExt trait provides convenient methods for converting Result types to views:

#![allow(unused)]
fn main() {
use waterui::widget::error::ResultExt;
use waterui::prelude::*;

fn load_user_data(user_id: u32) -> Result<String, DatabaseError> {
    // Simulate database operation
    if user_id == 0 {
        Err(DatabaseError::InvalidId)
    } else {
        Ok(format!("User {}", user_id))
    }
}

fn user_profile_view(user_id: u32) -> impl View {
    match load_user_data(user_id)
        .error_view(|err| text!("Failed to load user: {}", err))
    {
        Ok(user_data) => text!(user_data),
        Err(error_view) => error_view.any_view(),
    }
}
}

Inline Error Handling

You can handle errors inline within view construction:

#![allow(unused)]
fn main() {
fn network_status_view() -> impl View {
    vstack((
        "Network Status",
        match check_connection() {
            Ok(status) => text!(status),
            Err(error) => Error::new(error).any_view(),
        }
    ))
}
}

Advanced Features

Type Downcasting

The error system preserves type information, allowing you to downcast to specific error types for specialized handling:

#![allow(unused)]
fn main() {
use waterui::widget::error::Error;
use std::io;

fn handle_file_error(error: Error) -> impl View {
    match error.downcast::<io::Error>() {
        Ok(io_error) => {
            match io_error.kind() {
                io::ErrorKind::NotFound => {
                    vstack((
                        "File Not Found",
                        "The requested file could not be located.",
                        button("Browse for File", browse_action)
                    )).any_view()
                }
                io::ErrorKind::PermissionDenied => {
                    vstack((
                        "Permission Denied",
                        "You don't have permission to access this file.",
                        button("Request Access", request_access_action)
                    )).any_view()
                }
                _ => text!("IO Error: {}", io_error).any_view()
            }
        }
        Err(original_error) => {
            // Handle as generic error
            text!("Error: {}", original_error).any_view()
        }
    }
}
}

Custom Error Views

Create errors directly from custom views for complete control over presentation:

#![allow(unused)]
fn main() {
use waterui::widget::error::Error;

fn validation_error_view(field: &str, message: &str) -> Error {
    Error::from_view(
        hstack((
            Icon::warning().foreground(Color::WARNING),
            vstack((
                text!("Validation Error: {}", field),
                text!(message).foreground(Color::SECONDARY)
            ))
        ))
        .padding(16.0)
        .background(Color::WARNING.opacity(0.1))
    )
}

// Usage in form validation
fn validate_email(email: &str) -> Result<String, Error> {
    if email.contains('@') {
        Ok(email.to_string())
    } else {
        Err(validation_error_view("Email", "Must contain @ symbol"))
    }
}
}

Error Handling Patterns

Loading States with Error Handling

Combine error handling with loading states for better user experience:

#![allow(unused)]
fn main() {
enum LoadingState<T> {
    Loading,
    Loaded(T),
    Error(Error),
}

fn data_view(state: LoadingState<UserData>) -> impl View {
    match state {
        LoadingState::Loading => {
            hstack((
                ProgressIndicator::spinning(),
                "Loading user data..."
            )).any_view()
        }
        LoadingState::Loaded(data) => {
            user_profile_component(data).any_view()
        }
        LoadingState::Error(error) => {
            error.any_view()
        }
    }
}
}

Contextual Error Information

Provide context-aware error messages based on the current view:

#![allow(unused)]
fn main() {
fn api_request_view(endpoint: &str) -> impl View {
    match make_api_request(endpoint)
        .error_view(|err| {
            vstack((
                "Network Request Failed",
                text!("Endpoint: {}", endpoint),
                text!("Error: {}", err),
                hstack((
                    button("Retry", retry_action),
                    button("Go Offline", offline_mode_action)
                ))
            ))
            .padding(20.0)
            .background(Color::ERROR.opacity(0.1))
        })
    {
        Ok(response) => response_view(response).any_view(),
        Err(error_view) => error_view.any_view(),
    }
}
}

Best Practices

1. Configure Global Error Styling

Set up a consistent error presentation style at your app's root:

#![allow(unused)]
fn main() {
fn app_root() -> impl View {
    ContentView::new()
        .with(DefaultErrorView::new(|error| {
            vstack((
                Icon::error().size(24),
                text!("{}", error),
                text!("If this problem persists, please contact support.")
                    .foreground(Color::SECONDARY)
            ))
            .padding(16.0)
        }))
        .body(main_content_view())
}
}

2. Provide Actionable Error Messages

Include relevant actions users can take to resolve errors:

#![allow(unused)]
fn main() {
fn network_error_view(error: NetworkError) -> impl View {
    vstack((
        "Connection Problem",
        text!("{}", error),
        hstack((
            button("Check Connection", check_connection_action),
            button("Work Offline", enable_offline_mode),
            button("Retry", retry_last_action)
        ))
    ))
}
}

3. Use Appropriate Error Granularity

Handle different error types at appropriate levels in your view hierarchy:

#![allow(unused)]
fn main() {
fn user_dashboard() -> impl View {
    vstack((
        // Handle critical errors at component level
        match load_user_session() {
            Ok(session) => session_header(session).any_view(),
            Err(auth_error) => login_prompt(auth_error).any_view(),
        },
        
        // Handle non-critical errors inline
        hstack((
            user_avatar().unwrap_or_else(|_| default_avatar()),
            user_stats().unwrap_or_else(|err| {
                text!("Stats unavailable").foreground(Color::SECONDARY)
            })
        ))
    ))
}
}

ErrorView

The ErrorView is an internal component that wraps views to be used as errors. It's primarily used internally by the error system but can be useful in advanced scenarios.

DefaultErrorView

DefaultErrorView is the environment-based configuration mechanism that allows you to define how errors should be rendered throughout your application. It acts as a fallback when no specific error handling is provided, ensuring consistent error presentation across your entire UI.