Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# standalone (development version)

* Added standalone function for `cards::round5()` (#28)

* `check_*()` functions now error on empty input when `allow_empty = FALSE` (previously empty but correctly-typed input silently passed the class/type checks) (#30)
90 changes: 60 additions & 30 deletions R/standalone-checks.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ---
# repo: insightsengineering/standalone
# file: standalone-checks.R
# last-updated: 2025-05-08
# last-updated: 2026-07-01
# license: https://unlicense.org
# dependencies: standalone-cli_call_env.R
# imports: [rlang, cli]
Expand All @@ -12,6 +12,12 @@
#
# ## Changelog
#
# 2026-07-01
# - `check_*()` functions now error on empty input when `allow_empty = FALSE`
# (previously empty input silently passed class/type checks) (#30)
# - `check_scalar_integerish()` failures now use the `check_scalar_integerish`
# condition class (previously `check_integerish`) (#30)
#
# 2025-05-08
# - Added `check_identical()` and `check_identical_length()`
# 2025-04-27
Expand Down Expand Up @@ -57,9 +63,12 @@ check_class <- function(x,
class = "check_class",
call = get_cli_abort_call(),
envir = rlang::current_env()) {
# if empty, skip test
if (isTRUE(allow_empty) && rlang::is_empty(x)) {
return(invisible(x))
# if empty: return invisibly when allowed, otherwise error
if (rlang::is_empty(x)) {
if (isTRUE(allow_empty)) {
return(invisible(x))
}
cli::cli_abort(message, class = c(class, "standalone-checks"), call = call, .envir = envir)
Comment thread
Melkiades marked this conversation as resolved.
}

if (!inherits(x, cls)) {
Expand Down Expand Up @@ -221,9 +230,12 @@ check_length <- function(x,
class = "check_length",
call = get_cli_abort_call(),
envir = rlang::current_env()) {
# if empty, skip test
if (isTRUE(allow_empty) && rlang::is_empty(x)) {
return(invisible(x))
# if empty: return invisibly when allowed, otherwise error
if (rlang::is_empty(x)) {
if (isTRUE(allow_empty)) {
return(invisible(x))
}
cli::cli_abort(message, class = c(class, "standalone-checks"), call = call, .envir = envir)
}

# check length
Expand Down Expand Up @@ -303,9 +315,12 @@ check_range <- function(x,
class = "check_range",
call = get_cli_abort_call(),
envir = rlang::current_env()) {
# if empty, skip test
if (isTRUE(allow_empty) && rlang::is_empty(x)) {
return(invisible(x))
# if empty: return invisibly when allowed, otherwise error
if (rlang::is_empty(x)) {
if (isTRUE(allow_empty)) {
return(invisible(x))
}
cli::cli_abort(message, class = c(class, "standalone-checks"), call = call, .envir = envir)
}

print_error <- FALSE
Expand Down Expand Up @@ -396,9 +411,12 @@ check_binary <- function(x,
class = "check_binary",
call = get_cli_abort_call(),
envir = rlang::current_env()) {
# if empty, skip test
if (isTRUE(allow_empty) && rlang::is_empty(x)) {
return(invisible(x))
# if empty: return invisibly when allowed, otherwise error
if (rlang::is_empty(x)) {
if (isTRUE(allow_empty)) {
return(invisible(x))
}
cli::cli_abort(message, class = c(class, "standalone-checks"), call = call, .envir = envir)
}

# first check x is either logical or numeric
Expand Down Expand Up @@ -441,9 +459,12 @@ check_formula_list_selector <- function(x,
class = "check_formula_list_selector",
call = get_cli_abort_call(),
envir = rlang::current_env()) {
# if empty, skip test
if (isTRUE(allow_empty) && rlang::is_empty(x)) {
return(invisible(x))
# if empty: return invisibly when allowed, otherwise error
if (rlang::is_empty(x)) {
if (isTRUE(allow_empty)) {
return(invisible(x))
}
cli::cli_abort(message, class = c(class, "standalone-checks"), call = call, .envir = envir)
}

# first check the general structure; must be a list or formula
Expand Down Expand Up @@ -474,16 +495,19 @@ check_integerish <- function(x,
message =
ifelse(
allow_empty,
"The {.arg {arg_name}} argument must an integer vector or empty.",
"The {.arg {arg_name}} argument must an integer vector."
"The {.arg {arg_name}} argument must be an integer vector or empty.",
"The {.arg {arg_name}} argument must be an integer vector."
),
arg_name = rlang::caller_arg(x),
class = "check_integerish",
call = get_cli_abort_call(),
envir = rlang::current_env()) {
# if empty, skip test
if (isTRUE(allow_empty) && rlang::is_empty(x)) {
return(invisible(x))
# if empty: return invisibly when allowed, otherwise error
if (rlang::is_empty(x)) {
if (isTRUE(allow_empty)) {
return(invisible(x))
}
Comment thread
Melkiades marked this conversation as resolved.
cli::cli_abort(message, class = c(class, "standalone-checks"), call = call, .envir = envir)
}

if (!rlang::is_integerish(x)) {
Expand All @@ -503,16 +527,19 @@ check_scalar_integerish <- function(x,
message =
ifelse(
allow_empty,
"The {.arg {arg_name}} argument must an scalar integer or empty.",
"The {.arg {arg_name}} argument must an scalar integer."
"The {.arg {arg_name}} argument must be a scalar integer or empty.",
"The {.arg {arg_name}} argument must be a scalar integer."
),
arg_name = rlang::caller_arg(x),
class = "check_integerish",
class = "check_scalar_integerish",
call = get_cli_abort_call(),
envir = rlang::current_env()) {
# if empty, skip test
if (isTRUE(allow_empty) && rlang::is_empty(x)) {
return(invisible(x))
# if empty: return invisibly when allowed, otherwise error
if (rlang::is_empty(x)) {
if (isTRUE(allow_empty)) {
return(invisible(x))
}
cli::cli_abort(message, class = c(class, "standalone-checks"), call = call, .envir = envir)
Comment thread
Melkiades marked this conversation as resolved.
}

if (!rlang::is_scalar_integerish(x)) {
Expand Down Expand Up @@ -593,9 +620,12 @@ check_numeric <- function(x,
class = "check_numeric",
call = get_cli_abort_call(),
envir = rlang::current_env()) {
# if empty, skip test
if (isTRUE(allow_empty) && rlang::is_empty(x)) {
return(invisible(x))
# if empty: return invisibly when allowed, otherwise error
if (rlang::is_empty(x)) {
if (isTRUE(allow_empty)) {
return(invisible(x))
}
cli::cli_abort(message, class = c(class, "standalone-checks"), call = call, .envir = envir)
}

if (!is.numeric(x)) {
Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/_snaps/standalone-checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,15 @@
myfunc(pi)
Condition
Error in `myfunc()`:
! The `x` argument must an integer vector.
! The `x` argument must be an integer vector.

---

Code
myfunc(pi)
Condition
Error in `myfunc()`:
! The `x` argument must an scalar integer.
! The `x` argument must be a scalar integer.

---

Expand Down
39 changes: 38 additions & 1 deletion tests/testthat/test-standalone-checks.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ test_that("check functions work", {
expect_snapshot(myfunc(1), error = TRUE)

# check_data_frame()
expect_silent(check_data_frame(data.frame()))
expect_silent(check_data_frame(data.frame(a = 1)))
expect_silent(check_data_frame(data.frame(), allow_empty = TRUE))

expect_error(
check_data_frame("mystring"),
Expand Down Expand Up @@ -250,3 +251,39 @@ test_that("check functions work", {
expect_silent(check_identical_length(letters, letters))
expect_snapshot(check_identical_length(letters[1], letters), error = TRUE)
})

test_that("check functions error on empty input when allow_empty = FALSE", {
# empty input must error by default (allow_empty = FALSE) and pass when allowed
expect_error(check_class(character(0), "character"))
expect_silent(check_class(character(0), "character", allow_empty = TRUE))

expect_error(check_data_frame(data.frame()))
expect_silent(check_data_frame(data.frame(), allow_empty = TRUE))

expect_error(check_logical(logical(0)))
expect_silent(check_logical(logical(0), allow_empty = TRUE))

expect_error(check_range(numeric(0), c(0, 1)))
expect_silent(check_range(numeric(0), c(0, 1), allow_empty = TRUE))
Comment thread
Melkiades marked this conversation as resolved.

expect_error(check_binary(integer(0)))
expect_silent(check_binary(integer(0), allow_empty = TRUE))

expect_error(check_integerish(integer(0)))
expect_silent(check_integerish(integer(0), allow_empty = TRUE))

expect_error(check_scalar_integerish(integer(0)))
expect_silent(check_scalar_integerish(integer(0), allow_empty = TRUE))

expect_error(check_length(character(0), length = 1))
expect_silent(check_length(character(0), length = 1, allow_empty = TRUE))

expect_error(check_numeric(numeric(0)))
expect_silent(check_numeric(numeric(0), allow_empty = TRUE))

expect_error(check_formula_list_selector(list()))
expect_silent(check_formula_list_selector(list(), allow_empty = TRUE))

# empty string is not empty input and must still pass check_string()
expect_silent(check_string(""))
})
Loading