---
title: "Error Handling Strategies"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Error Handling Strategies}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 5
)
```

## Overview

SafeMapper provides multiple layers of error handling to ensure your long-running computations are robust. This guide covers both the built-in fault tolerance and the explicit error handling functions.

```{r}
library(SafeMapper)
```

## Error Handling Architecture

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    SafeMapper Error Handling Layers                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Layer 1: Built-in Fault Tolerance                                          │
│   ┌───────────────────────────────────────────────────────────────────┐     │
│   │  • Automatic checkpointing                                         │     │
│   │  • Batch-level retry (configurable attempts)                      │     │
│   │  • Session recovery on re-run                                     │     │
│   └───────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│   Layer 2: Explicit Error Wrappers                                           │
│   ┌───────────────────────────────────────────────────────────────────┐     │
│   │  s_safely()   ──► Capture errors with result/error structure      │     │
│   │  s_possibly() ──► Return default value on error                   │     │
│   │  s_quietly()  ──► Capture messages, warnings, output              │     │
│   └───────────────────────────────────────────────────────────────────┘     │
│                                                                              │
│   Layer 3: Custom Error Handling                                             │
│   ┌───────────────────────────────────────────────────────────────────┐     │
│   │  tryCatch() within your function                                  │     │
│   │  Conditional logic for expected error cases                       │     │
│   └───────────────────────────────────────────────────────────────────┘     │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

## Layer 1: Built-in Fault Tolerance

### Automatic Retry

SafeMapper automatically retries failed batches:

```{r}
# Configure retry behavior
s_configure(
  retry_attempts = 5,  # Try up to 5 times per batch
  batch_size = 50      # Each batch contains 50 items
)
```

### Retry Flow Diagram

```
┌─────────────────────────────────────────────────────────────────┐
│                    Automatic Retry Flow                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   Processing Batch [1-50]                                        │
│          │                                                       │
│          ▼                                                       │
│   ┌─────────────────────────────────────────┐                   │
│   │  Attempt 1                               │                   │
│   │  ├── Success? ─────────────► Save & Continue               │
│   │  └── Error? ───────────────┐                                │
│   └────────────────────────────┼────────────┘                   │
│                                │                                 │
│                                ▼ (wait 1s)                       │
│   ┌─────────────────────────────────────────┐                   │
│   │  Attempt 2                               │                   │
│   │  ├── Success? ─────────────► Save & Continue               │
│   │  └── Error? ───────────────┐                                │
│   └────────────────────────────┼────────────┘                   │
│                                │                                 │
│                                ▼ (wait 1s)                       │
│   ┌─────────────────────────────────────────┐                   │
│   │  Attempt 3 (final)                       │                   │
│   │  ├── Success? ─────────────► Save & Continue               │
│   │  └── Error? ───────────────► STOP with error               │
│   └─────────────────────────────────────────┘                   │
│                                                                  │
│   Note: Previous batches already saved to checkpoint            │
│   On re-run: Resume from last successful batch                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

## Layer 2: Error Wrapper Functions

### s_safely() - Capture Errors

`s_safely()` wraps a function to capture errors instead of throwing them:

```{r}
# Create a safe version of log
safe_log <- s_safely(log)

# Successful call
result <- safe_log(10)
print(result)

# Error call (returns error instead of throwing)
result <- safe_log("not a number")
print(result)
```

### Using s_safely with Mapping

```{r}
# Define function that might fail
risky_operation <- function(x) {
  if (x < 0) stop("Negative values not allowed")
  sqrt(x)
}

# Wrap with s_safely
safe_operation <- s_safely(risky_operation)

# Apply to data that includes problematic values
data <- c(4, -1, 9, -4, 16)
results <- s_map(data, safe_operation)

# Extract successful results
successes <- s_map_dbl(results, ~ .x$result %||% NA_real_)
print(successes)

# Check which failed
errors <- s_map_lgl(results, ~ !is.null(.x$error))
print(errors)
```

### s_possibly() - Default on Error

`s_possibly()` returns a default value when errors occur:

```{r}
# Create a function that returns NA on error
possible_log <- s_possibly(log, otherwise = NA_real_)

# Mix of valid and invalid inputs
inputs <- list(10, "text", 100, NULL, 1000)
results <- s_map_dbl(inputs, possible_log)
print(results)
```

### Using s_possibly for Robust Pipelines

```{r}
# Simulated data extraction that might fail
extract_value <- function(x) {
  if (is.null(x) || length(x) == 0) stop("Invalid input")
  x[[1]]
}

# Wrap with default value
safe_extract <- s_possibly(extract_value, otherwise = NA)

# Apply to mixed data
data <- list(
  list(value = 1),
  NULL,
  list(value = 3),
  list(),
  list(value = 5)
)

results <- s_map(data, safe_extract)
print(unlist(results))
```

### s_quietly() - Capture Side Effects

`s_quietly()` captures messages, warnings, and printed output:

```{r}
# Function with side effects
chatty_function <- function(x) {
  message("Processing: ", x)
  if (x > 5) warning("Large value!")
  cat("Result is:", x^2, "\n")
  x^2
}

# Wrap with s_quietly
quiet_function <- s_quietly(chatty_function)

# Call it - no output during execution
result <- quiet_function(7)

# Examine captured side effects
print(names(result))
print(result$result)
print(result$messages)
print(result$warnings)
print(result$output)
```

## Comparison of Error Handlers

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    Error Handler Comparison                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Function      │ On Error        │ Return Structure  │ Use Case            │
│   ──────────────┼─────────────────┼───────────────────┼──────────────────── │
│   s_safely()    │ Captures error  │ list(result,      │ Need to inspect     │
│                 │                 │      error)       │ what went wrong     │
│   ──────────────┼─────────────────┼───────────────────┼──────────────────── │
│   s_possibly()  │ Returns default │ Same as normal    │ Just need results,  │
│                 │                 │ function output   │ errors = NA/default │
│   ──────────────┼─────────────────┼───────────────────┼──────────────────── │
│   s_quietly()   │ Propagates      │ list(result,      │ Debug chatty        │
│                 │ error           │      output,      │ functions,          │
│                 │                 │      messages,    │ capture logs        │
│                 │                 │      warnings)    │                     │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

## Combining Strategies

### Strategy 1: s_safely + Post-Processing

```{r}
# Best for: When you need to analyze failures

process_item <- function(x) {
  if (runif(1) < 0.3) stop("Random failure")
  x^2
}

safe_process <- s_safely(process_item)

# Process with checkpointing + error capture
results <- s_map(1:10, safe_process)

# Analyze results
success_count <- sum(s_map_lgl(results, ~ is.null(.x$error)))
failure_count <- sum(s_map_lgl(results, ~ !is.null(.x$error)))

cat("Successes:", success_count, "\n")
cat("Failures:", failure_count, "\n")

# Get successful values
successful_values <- s_map_dbl(results, function(r) {
  if (is.null(r$error)) r$result else NA_real_
})
print(successful_values)
```

### Strategy 2: s_possibly for Clean Pipelines

```{r}
# Best for: When you just need results, failures = NA

robust_sqrt <- s_possibly(
  function(x) {
    if (x < 0) stop("negative")
    sqrt(x)
  },
  otherwise = NA_real_
)

# Clean pipeline
data <- c(4, -1, 9, -4, 16, -9, 25)
results <- s_map_dbl(data, robust_sqrt)
print(results)

# Easy to filter out failures
valid_results <- results[!is.na(results)]
print(valid_results)
```

### Strategy 3: Multi-Layer Protection

```{r}
# Best for: Critical operations that must not fail

# Layer 1: Function-level error handling
robust_api_call <- function(x) {
  tryCatch({
    # Simulate API call that might fail
    if (runif(1) < 0.2) stop("Temporary failure")
    x * 10
  }, error = function(e) {
    NA_real_  # Return NA on error
  })
}

# Layer 2: s_possibly for unexpected errors
safe_api_call <- s_possibly(robust_api_call, otherwise = NA_real_)

# Layer 3: SafeMapper checkpointing
results <- s_map_dbl(
  1:20, 
  safe_api_call,
  .session_id = "critical_operation"
)

print(results)
cat("Success rate:", mean(!is.na(results)) * 100, "%\n")
```

## Decision Guide

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    Which Error Strategy to Use?                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Q: Do you need to know WHY items failed?                                  │
│   │                                                                          │
│   ├── YES ──► Use s_safely()                                                │
│   │           Results contain both values and error messages                │
│   │                                                                          │
│   └── NO ───► Q: Do individual failures matter?                             │
│               │                                                              │
│               ├── NO ──► Use s_possibly()                                   │
│               │          Clean results, failures become default value       │
│               │                                                              │
│               └── YES ─► Use built-in retry                                 │
│                          Configure retry_attempts for transient errors      │
│                                                                              │
│   Q: Need to capture warnings/messages too?                                 │
│   │                                                                          │
│   └── YES ──► Use s_quietly()                                               │
│               Captures all side effects for later inspection                │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

## Error Handling Patterns

### Pattern 1: Fail Fast

Let errors stop execution immediately (useful during development):

```{r eval=FALSE}
# No error wrapping - errors will stop execution
# Previous batches are saved to checkpoint
results <- s_map(data, risky_function)
```

### Pattern 2: Log and Continue

```{r}
# Create a logging wrapper
log_errors <- function(f) {
  function(...) {
    tryCatch(
      f(...),
      error = function(e) {
        message("Error: ", e$message)
        NA
      }
    )
  }
}

# Use with s_map
logged_sqrt <- log_errors(function(x) {
  if (x < 0) stop("negative input")
  sqrt(x)
})

results <- s_map_dbl(c(4, -1, 9, -4, 16), logged_sqrt)
print(results)
```

### Pattern 3: Collect Errors for Reporting

```{r}
# Process and collect all errors
process_with_tracking <- function(items) {
  safe_fn <- s_safely(function(x) {
    if (x %% 3 == 0) stop("Divisible by 3")
    x^2
  })
  
  results <- s_map(items, safe_fn)
  
  # Build report
  list(
    values = s_map(results, "result"),
    errors = s_map(results, "error"),
    success_rate = mean(s_map_lgl(results, ~ is.null(.x$error)))
  )
}

report <- process_with_tracking(1:10)
cat("Success rate:", report$success_rate * 100, "%\n")
```

## Best Practices

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    Error Handling Best Practices                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   1. Match Strategy to Need                                                  │
│      ├── Development: Let errors propagate (easier debugging)               │
│      ├── Production: Use s_safely/s_possibly (robust execution)             │
│      └── Critical: Multi-layer protection                                   │
│                                                                              │
│   2. Configure Retries Appropriately                                         │
│      ├── Network operations: 3-5 retries                                    │
│      ├── Local computation: 1 retry (errors usually persistent)             │
│      └── Rate-limited APIs: Consider exponential backoff                    │
│                                                                              │
│   3. Log Errors for Analysis                                                 │
│      ├── Use s_safely to capture error details                              │
│      ├── Store error counts/types for monitoring                            │
│      └── Alert on error rate thresholds                                     │
│                                                                              │
│   4. Test Error Handling                                                     │
│      ├── Intentionally inject failures                                      │
│      ├── Verify checkpoint/recovery works                                   │
│      └── Check that all error cases are handled                             │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

## Next Steps

- 📋 [Session Management](session-management.html) - Manage checkpoints and sessions
- 🎯 [Real-World Examples](real-world-examples.html) - See error handling in practice
- 🏆 [Best Practices](best-practices.html) - Production-ready patterns
