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
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ Collate:
'assert_null.R'
'assert_numerical.R'
'assert_packages.R'
'has.R'
'assert_regex.R'
'assert_set.R'
'coverage_testing.R'
'export_testing.R'
'has.R'
BugReports: https://github.com/selkamand/assertions/issues
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export(assert_all_greater_than)
export(assert_all_greater_than_or_equal_to)
export(assert_all_less_than)
export(assert_all_less_than_or_equal_to)
export(assert_all_strings_contain)
export(assert_between)
export(assert_character)
export(assert_character_vector)
Expand Down Expand Up @@ -62,6 +63,7 @@ export(assert_reactive)
export(assert_scalar)
export(assert_set_equal)
export(assert_string)
export(assert_string_contains)
export(assert_subset)
export(assert_vector)
export(assert_whole_number)
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# assertions 0.2.0

* Added `assert_all_strings_contain()` and `assert_string_contains()` for checking character inputs against regular expressions

* Added `assert_packages_installed()`

* Added `assert_all_between()` and `assert_between()` assertions
Expand Down
106 changes: 106 additions & 0 deletions R/assert_regex.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#' Check if strings contain a regex pattern
#'
#' This helper checks whether all elements of a character vector match a regex pattern.
#'
#' @param x A character vector to check
#' @param pattern A regular expression pattern (string)
#' @param ignore.case,perl,fixed,useBytes Logical flags passed to [grepl()]
#'
#' @return TRUE if all strings in `x` match `pattern`, otherwise FALSE or a string error message.
#'
#' @concept assert_regex
contains_pattern <- function(x, pattern, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE) {
if (!is_string(pattern))
return("'pattern' must be a string (length 1 character vector)")
Comment on lines +12 to +14

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject missing regex patterns before grepl

When pattern is NA_character_, is_string(pattern) returns TRUE, so this helper proceeds to grepl() and R throws invalid 'pattern' argument instead of the assertion’s normal error formatting. That means callers who pass a missing pattern (e.g., pattern = NA) get a base error rather than the expected assertion message. Consider explicitly rejecting is.na(pattern) (or reusing assert_no_missing for pattern) so missing patterns fail with a consistent assertion error.

Useful? React with 👍 / 👎.

if (!is_flag(ignore.case))
return("'ignore.case' must be a logical flag")
if (!is_flag(perl))
return("'perl' must be a logical flag")
if (!is_flag(fixed))
return("'fixed' must be a logical flag")
if (!is_flag(useBytes))
return("'useBytes' must be a logical flag")

matches <- grepl(pattern, x, ignore.case = ignore.case, perl = perl, fixed = fixed, useBytes = useBytes)

if (all(matches))
return(TRUE)

total <- length(x)
failed <- sum(!matches)

if (total == 1) {
return("'{.strong {arg_name}}' must match regex `{pattern}`")
}

paste0(
"'{.strong {arg_name}}' must all match regex `{pattern}`. ",
failed,
"/",
total,
" elements did not match pattern"
)
}

#' Assert all strings contain a regex pattern
#'
#' Assert all elements of a character vector match a regex pattern.
#'
#' @include assert_create.R
#' @include assert_type.R
#' @include has.R
#' @include is_functions.R
#' @param x An object to check
#' @param pattern A regular expression pattern (string)
#' @param ignore.case,perl,fixed,useBytes Logical flags passed to [grepl()]
#' @param msg A character string containing the error message to display if `x` does not match `pattern`
#' @inheritParams common_roxygen_params
#'
#' @return invisible(TRUE) if `x` matches `pattern`, otherwise aborts with the error message specified by `msg`
#'
#' @examples
#' try({
#' assert_all_strings_contain(c("abc", "a1"), "^a") # Passes
#' assert_all_strings_contain(c("abc", "b1"), "^a") # Throws default error
#' assert_all_strings_contain(c("abc", "b1"), "^a", msg = "Custom error message") # Throws custom error
#' })
#'
#' @concept assert_regex
#' @export
assert_all_strings_contain <- assert_create_chain(
assert_character_vector,
assert_no_missing,
assert_create(
func = contains_pattern
)
)

#' Assert string contains a regex pattern
#'
#' Assert a string matches a regex pattern.
#'
#' @include assert_create.R
#' @include assert_type.R
#' @include has.R
#' @include is_functions.R
#' @param x An object to check
#' @param pattern A regular expression pattern (string)
#' @param ignore.case,perl,fixed,useBytes Logical flags passed to [grepl()]
#' @param msg A character string containing the error message to display if `x` does not match `pattern`
#' @inheritParams common_roxygen_params
#'
#' @return invisible(TRUE) if `x` matches `pattern`, otherwise aborts with the error message specified by `msg`
#'
#' @examples
#' try({
#' assert_string_contains("abc", "^a") # Passes
#' assert_string_contains("abc", "^b") # Throws default error
#' assert_string_contains("abc", "^b", msg = "Custom error message") # Throws custom error
#' })
#'
#' @concept assert_regex
#' @export
assert_string_contains <- assert_create_chain(
assert_string,
assert_all_strings_contain
)
46 changes: 46 additions & 0 deletions man/assert_all_strings_contain.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions man/assert_string_contains.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions man/contains_pattern.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions tests/testthat/test-assert_regex.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
cli::test_that_cli("assert_all_strings_contain() works", configs = "plain", {
# Works for matching strings
expect_identical(assert_all_strings_contain(c("abc", "a1"), "^a"), TRUE)

# Aborts for non-matching strings
expect_error(
assert_all_strings_contain(c("abc", "b1"), "^a"),
"must all match regex `\\^a`.*1/2 elements did not match pattern",
fixed = FALSE
)

# Single element does not include count
expect_error(
assert_all_strings_contain("abc", "^b"),
"must match regex `\\^b`$",
fixed = FALSE
)

# Aborts for non-character inputs
expect_error(assert_all_strings_contain(1, "^a"), "character vector", fixed = FALSE)

# Aborts for invalid pattern inputs
expect_error(assert_all_strings_contain("abc", 1), "pattern.*string", fixed = FALSE)

# Error messages use variable name of passed arguments
x <- c("abc", "b1")
expect_error(assert_all_strings_contain(x, "^a"), "'x'.*match regex", fixed = FALSE)

# Custom error messages work
expect_error(assert_all_strings_contain(c("abc", "b1"), "^a", msg = "Custom error message"), "Custom error message")
})

cli::test_that_cli("assert_string_contains() works", configs = "plain", {
# Works for matching string
expect_identical(assert_string_contains("abc", "^a"), TRUE)
expect_identical(assert_string_contains("Abc", "^a", ignore.case = TRUE), TRUE)

# Aborts for non-matching string
expect_error(assert_string_contains("abc", "^b"), "must match regex `\\^b`$", fixed = FALSE)

# Aborts for non-string inputs
expect_error(assert_string_contains(c("abc", "b1"), "^a"), "string", fixed = FALSE)

# Custom error messages work
expect_error(assert_string_contains("abc", "^b", msg = "Custom error message"), "Custom error message")
})