favr

CRAN status R-CMD-check

Function Argument Validation for R (favr) provides tools for the succinct validation of function arguments with clear error messaging.

Overview

Numerous other strongly typed check_*() functions are provided for specific types of validation, including:

Validate specific types:

Validate specific S3 types:

Validate OOP types:

Modify check behaviour:

Validate specific scalar values:

Validate the lack of forbidden values:

Validate object properties:

Validate file and directory existence:

Build checks in the style of favr:

Installation

Install the latest version of favr from CRAN.

install.packages("favr")

Development Version

To get a bug fix or to use a feature from the development version, you can install the development version of favr from GitHub.

# install.packages("pak")
pak::pak("LJ-Jenkins/favr")

Usage

General validation:

library(favr, warn.conflicts = FALSE)

x <- c(1, 2, 3)
y <- c("a", "b", "c")

abortifnot(x < 4, nchar(y) > 1)
#> Error:
#> ! `nchar(y) > 1` is not TRUE.

abortifnot(
  "{.var x} must be length {.val {5}}, but is length {.val {length(x)}}." = length(x) == 5,
  is.character(y)
)
#> Error:
#> ! `x` must be length 5, but is length 3.

abortifnot(
  is.numeric(x),
  is.numeric(y),
  message = "{.var x} and {.var y} must be {.cls numeric}."
)
#> Error:
#> ! `x` and `y` must be <numeric>.

General validation with tidy evaluation:

inject_msg <- "{.var x} must contain negative values."

check(is.character(y), {{ inject_msg }} := x < 0)
#> Error:
#> ! `x` must contain negative values.
check(is.character(y), !!inject_msg := x < 0)
#> Error:
#> ! `x` must contain negative values.

inject_args <- list("{.var y} must all have 2 nchars." = nchar(y) == 2)

check(is.numeric(x), !!!inject_args)
#> Error:
#> ! `y` must all have 2 nchars.

Data-masked validation:

data <- list(a = c("a", "b", "c"), b = 1:3)

# `check_with()` user-supplied messages are eval'd in the data mask context.
check_with(data,
  "{.var a} must all have 1 nchars." = nchar(a) == 1,
  "{.var b} must be length {.val 5}, but is length {.val {length(b)}}." = length(b) == 5
)
#> Error:
#> ! `b` must be length "5", but is length 3.

b <- c("a", "b", "c")

check_with(data, is.numeric(.data$b), is.numeric(.env$b))
#> Error:
#> ! `is.numeric(.env$b)` is not TRUE.

Walking a check over a vector:

x <- list(1, 2, my_el = "3", 4)
walk_check(x, is.numeric)
#> Error:
#> ! Check result for `.x[['my_el']]` (index: 3) is not TRUE.

Class validation:

x <- structure(1:3, class = "a_class")
check_class(x, "my_class")
#> Error:
#> ! `x` must be class <my_class>, but is class <a_class>.
class(x) <- c("b_class", class(x))
check_inherits(x, "my_class")
#> Error:
#> ! `x` must inherit from <my_class>, but is class <b_class/a_class>.

Specific type validation:

x <- c(1, 2, 3)
check_integer(x)
#> Error:
#> ! `x` must be an <integer> vector, not a <double> vector.
check_scalar_double(x)
#> Error:
#> ! `x` must be a scalar <double>, but it is of length 3.
check_s3(x)
#> Error:
#> ! `x` must be an <S3> object, not <numeric>.

df <- data.frame(x = 1:3, y = 1:3)
check_s3(df)
check_tibble(df)
#> Error:
#> ! `df` must inherit from <tbl_df>, but is class <data.frame>.

# the `bare()` modifier can be used to ensure bare objects.
check_integer(factor(1))
check_integer(bare(factor(1)))
#> Error:
#> ! `factor(1)` must be a bare <integer>, but it is of class <factor>.

class(df) <- c("my_class", "tbl_df", "tbl", class(df))
check_tibble(df)
check_tibble(bare(df))
#> Error:
#> ! `df` must be a bare <tbl_df>, but it is of class <my_class>.

# length modifiers can be used on `n` to specify length ranges.
check_double(x, n = 2)
#> Error:
#> ! `x` must be a <double> vector of length 2, not 3.
check_double(x, n = at_least(4))
#> Error:
#> ! `x` must be a <double> vector of at least length 4, but it is of
#>   length 3.
check_double(x, n = at_most(2))
#> Error:
#> ! `x` must be a <double> vector of at most length 2, but it is of length
#>   3.
check_double(x, n = in_range(1, 2))
#> Error:
#> ! `x` must be a <double> vector of a length between 1 and 2, but it is
#>   of length 3.

check_tibble(df, nrow = 2)
#> Error:
#> ! `df` must be a <tbl_df> with 2 rows, not 3.
check_tibble(df, ncol = at_least(3))
#> Error:
#> ! `df` must be a <tbl_df> with at least 3 columns, but it has 2.
check_tibble(df, nrow = at_most(2))
#> Error:
#> ! `df` must be a <tbl_df> with at most 2 rows, but it has 3.
check_tibble(df, ncol = in_range(3, 5))
#> Error:
#> ! `df` must be a <tbl_df> with 3 to 5 columns, but it has 2.

Ensure no forbidden values:

x <- c(1, 2, 1, NA)
check_no_na(x)
#> Error:
#> ! `x` must not contain NA values.
check_finite(x)
#> Error:
#> ! `x` must not contain non-finite values.
check_unique(x)
#> Error:
#> ! `x` must have unique elements. Duplicates: 1.

x <- c("a", "b", "")
check_nzchar(x)
#> Error:
#> ! `x` must not contain empty strings.
x <- c("a", "b", " ")
check_nzchar(x, allow_all_ws = FALSE)
#> Error:
#> ! `x` must not contain all whitespace elements.

Check object properties:

x <- c(1, 2, 3)
check_length(x, 2)
#> Error:
#> ! `x` must be of length 2, not 3.
check_size(x, at_most(1))
#> Error:
#> ! `x` must be of at most size 1, but it is of size 3.
df <- data.frame(x = 1:3, y = 1:3)
check_nrow(df, 2)
#> Error:
#> ! `df` must have 2 rows, not 3.
check_ncol(df, in_range(3, 5))
#> Error:
#> ! `df` must have 3 to 5 columns, but it has 2.
x <- numeric(0)
check_non_empty(x)
#> Error:
#> ! `x` must not be empty.
x <- c(1, 2, 3)
check_named(x)
#> Error:
#> ! `x` must be named.
names(x) <- c("a", "b", "a")
check_named(x, unique = TRUE)
#> Error:
#> ! `x` must have unique names. Duplicates: "a".
names(x) <- c("a", "b", "")
check_named(x, allow_empty = FALSE)
#> Error:
#> ! `x` must not contain empty names.

File/dir existence validation:

check_dir("non_existing_dir")
#> Error:
#> ! `x` must be an existing directory, but it doesn't exist.
#> ℹ Path provided: 'non_existing_dir'.
check_file("non_existing_file")
#> Error:
#> ! `x` must be an existing file, but it doesn't exist.
#> ℹ Path provided: 'non_existing_file'.
check_ext("file.txt", ext = c(".csv", ".xlsx"))
#> Error:
#> ! `"file.txt"` must have extension ".csv" or ".xlsx".
check_file("file.txt", ext = c(".csv", ".xlsx"))
#> Error:
#> ! `"file.txt"` must have extension ".csv" or ".xlsx".

Build your own S3 type checks:

check_my_class <- function(
  x,
  n = NULL,
  ...,
  allow_null = FALSE,
  arg = rlang::caller_arg(x),
  call = rlang::caller_env()
) {
  s3_vec_check(
    x,
    n,
    type = "my_class",
    type_msg = "a {.cls my_class} vector",
    ...,
    allow_null = allow_null,
    arg = arg,
    call = call
  )
}

check_my_class(1L)
#> Error:
#> ! `1L` must inherit from <my_class>, but is class <integer>.

x <- structure(1:3, class = "my_class")
check_my_class(x)

check_my_class(NULL, allow_null = TRUE)

class(x) <- c("another_class", class(x))
check_my_class(bare(x))
#> Error:
#> ! `x` must be a bare <my_class>, but it is of class <another_class>.

check_my_class(x, n = at_most(2))
#> Error:
#> ! `x` must be a <my_class> vector of at most length 2, but it is of
#>   length 3.
check_my_class(x, n = in_range(1, 2))
#> Error:
#> ! `x` must be a <my_class> vector of a length between 1 and 2, but it is
#>   of length 3.

Notes

favr relies heavily on the imported packages rlang and cli.

For data validation using user-defined schemas, see fluffy.

Getting help

If you encounter a clear bug, please file an issue with a minimal reproducible example on GitHub.

Code of Conduct

Please note that the favr project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.