Condition Handling in R Packages
When to Use What
| Task | Use |
|---|---|
| Throw formatted error | cli_abort() with message and bullets |
| Throw formatted warning | cli_warn() with message and bullets |
| Display informative message | cli_inform() with message and bullets |
| Show correct function in error | call = caller_env() argument |
| Get argument name dynamically | arg = caller_arg(x) in input checkers |
| Catch and rethrow with context | try_fetch() with parent = cnd |
| Take ownership of low-level error | try_fetch() with parent = NA |
| Test error messages | expect_snapshot(error = TRUE, ...) |
CLI Conditions Essentials
Use cli_abort(), cli_warn(), and cli_inform() instead of base R's stop(), warning(), and message() for formatted condition messages.
Basic Usage
# Simple error
cli_abort("Column {.field name} is required")
# Simple warning
cli_warn("Column {.field {col}} has missing values")
# Simple message
cli_inform("Processing {n} file{?s}")
Structured Messages with Bullets
Use named character vectors for multi-part messages:
cli_abort(c(
"File not found",
"x" = "Cannot read {.file {path}}",
"i" = "Check that the file exists"
))
Bullet types:
"x"- Error/problem (red X)"!"- Warning (yellow !)"i"- Information (blue i)"v"- Success (green checkmark)"*"- Bullet point">"- Arrow/pointer
Essential Inline Markup
Format values in condition messages for clarity:
# Arguments
cli_abort("{.arg x} must be numeric")
# Files and paths
cli_abort("Cannot find {.file {path}}")
# Values
cli_abort("Expected {.val TRUE}, got {.val {x}}")
# Classes/types
cli_abort("Expected numeric, got {.cls {class(x)}}")
# Code
cli_abort("Use {.code na.rm = TRUE} to ignore missing values
")
# Function names
cli_abort("See {.fn mypackage::helper} for details")
Pluralization
Use {?} for automatic singular/plural handling:
cli_abort("Found {n} error{?s}")
#> Found 1 error
#> Found 3 errors
cli_abort("Found {n} director{?y/ies}")
#> Found 1 directory
#> Found 5 directories
Error Call Context
By default, abort() and cli_abort() show the function where they are called. When using error helpers, pass call = caller_env() to show the user's function instead.
The Problem
# Error helper without call context
stop_my_class <- function(message) {
abort(message, class = "my_class")
}
my_function <- function(x) {
stop_my_class("Something went wrong")
}
my_function("test")
#> Error in `stop_my_class()`:
#> ! Something went wrong
The error shows stop_my_class() but users called my_function().
The Solution
stop_my_class <- function(message, call = caller_env()) {
abort(message, class = "my_class", call = call)
}
my_function <- function(x) {
stop_my_class("Something went wrong")
}
my_function("test")
#> Error in `my_function()`:
#> ! Something went wrong
Now the error correctly shows my_function().
Pattern: Abort Wrapper
stop_my_class <- function(message, call = caller_env()) {
abort(message, class = "my_class", call = call)
}
Input Checkers
Input checkers validate function arguments. Use caller_arg() to get the argument name dynamically and caller_env() for proper error context.
Basic Input Checker
check_string <- function(x,
arg = caller_arg(x),
call = caller_env()) {
if (!is_string(x)) {
cli_abort("{.arg {arg}} must be a string.", call = call)
}
}
Using the Checker
my_function <- function(my_arg) {
check_string(my_arg)
# ... rest of function
}
my_function(NA)
#> Error in `my_function()`:
#> ! `my_arg` must be a string.
The error shows:
- The correct function (
my_function, notcheck_string) - The correct argument name (
my_arg, notx)
Pattern: Complete Input Checker
check_scalar_numeric <- function(x,
arg = caller_arg(x),
call = caller_env()) {
if (!is.numeric(x) || length(x) != 1) {
cli_abort(c(
"{.arg {arg}} must be a single number",
"x" = "You supplied {.cls {class(x)}} of length {length(x)}"
), call = call)
}
}
Built-in Checkers from rlang
rlang provides check_required() for mandatory arguments:
my_function <- function(data) {
check_required(data)
# ... rest of function
}
my_function()
#> Error in `my_function()`:
#> ! `data` is absent but must be supplied.
Error Chaining
Error chaining adds context when catching and rethrowing errors. Use try_fetch() to catch errors and parent to chain them.
Basic Error Chaining
with_chained_errors <- function(expr, call = caller_env()) {
try_fetch(
expr,
error = function(cnd) {
abort("Problem during step.", parent = cnd, call = call)
}
)
}
my_verb <- function(expr) {
with_chained_errors(expr)
}
my_verb(1 + "a")
#> Error in `my_verb()`:
#> ! Problem during step.
#> Caused by error in `1 + "a"`:
#> ! non-numeric argument to binary operator
Why try_fetch() Over tryCatch()
- Preserves full backtrace context for debugging
- Allows
options(error = recover)to work - Catches stack overflow errors (R >= 4.2.0)
Taking Ownership of Errors
Use parent = NA to completely replace a low-level error:
with_user_friendly_errors <- function(expr, call = caller_env()) {
try_fetch(
expr,
vctrs_error_scalar_type = function(cnd) {
abort(
"Must supply a vector.",
parent = NA,
error = cnd, # Store original for debugging
call = call
)
}
)
}
For advanced error chaining patterns: See references/error-chaining.md for iteration context, modifying error calls, and case studies.
Testing Error Conditions
Use testthat snapshot tests to verify error messages.
Basic Snapshot Test
test_that("validation errors are clear", {
expect_snapshot(error = TRUE, {
my_function(NULL)
})
})
This creates a snapshot file recording the exact error output.
Testing Multiple Cases
test_that("input validation catches bad inputs", {
expect_snapshot(error = TRUE, {
check_string(123)
check_string(NA)
check_string(c("a", "b"))
})
})
Enabling rlang Error Display
To see the call field in error snapshots, depend on rlang >= 1.0.0 in your package. With testthat >= 3.1.2, this enables the enhanced error display automatically.
Testing Error Classes
test_that("errors have correct class", {
expect_error(
validate_input(NULL),
class = "validation_error"
)
})
Resources
rlang Documentation
cli Documentation
Style Guide
Reference Files
- references/error-chaining.md - Advanced error chaining patterns
- references/customisation.md - Condition appearance customization