Best way to catch rlang_errors consistently?

I am trying to write a function that would be able to catch any type of error, but I am a having some trouble with dplyr::select() (but also others). For instance, sometimes the error message can be found under $parent$message but sometimes under $message:

rlang::catch_cnd(dplyr::filter(mtcars, bm == 1))$parent$message
#> [1] "object 'bm' not found"
rlang::catch_cnd(dplyr::arrange(mtcars, bm))$parent$message
#> [1] "object 'bm' not found"

# But sometimes it's $message and not $parent$message
rlang::catch_cnd(dplyr::group_by(mtcars, bm))$message
#>                                             
#> "Must group by variables found in `.data`."
rlang::catch_cnd(dplyr::count(mtcars, bm))$message
#>                                             
#> "Must group by variables found in `.data`."

And sometimes I have no idea where to look for, for instance in the case of select():

rlang::catch_cnd(dplyr::select(mtcars, bm))$message
#> [1] ""
rlang::catch_cnd(dplyr::select(mtcars, bm))$parent$message
#> NULL

Interestingly, the message under $message is the empty string and not NULL. Looking at the structure of a failed select call does not help:

str(
  rlang::catch_cnd(dplyr::select(mtcars, bm))
)
#> List of 9
#>  $ message         : chr ""
#>  $ trace           :Classes 'rlang_trace', 'rlib_trace', 'tbl' and 'data.frame': 35 obs. of  5 variables:
#>   ..$ call     :List of 35
#>   .. ..$ : language str(rlang::catch_cnd(dplyr::select(mtcars, bm)))
#>   .. ..$ : language rlang::catch_cnd(dplyr::select(mtcars, bm))
#>   .. ..$ : language eval_bare(rlang::expr(tryCatch(!!!handlers, {     force(expr) ...
#>   .. ..$ : language tryCatch(condition = `<fn>`, {     force(expr) ...
#>   .. ..$ : language tryCatchList(expr, classes, parentenv, handlers)
#>   .. ..$ : language tryCatchOne(expr, names, parentenv, handlers[[1L]])
#>   .. ..$ : language doTryCatch(return(expr), name, parentenv, handler)
#>   .. ..$ : language force(expr)
#>   .. ..$ : language dplyr::select(mtcars, bm)
#>   .. ..$ : language select.data.frame(mtcars, bm)
#>   .. ..$ : language tidyselect_fix_call(tidyselect::eval_select(expr(c(...)), .data), call = error_call)
#>   .. ..$ : language withCallingHandlers(expr, error = function(cnd) {     cnd$call <- call ...
#>   .. ..$ : language tidyselect::eval_select(expr(c(...)), .data)
#>   .. ..$ : language eval_select_impl(data, names(data), as_quosure(expr, env), include = include,      exclude = exclude, strict = st| __truncated__ ...
#>   .. ..$ : language with_subscript_errors(vars_select_eval(vars, expr, strict = strict, data = x,      name_spec = name_spec, uniquel| __truncated__ ...
#>   .. ..$ : language tryCatch(with_entraced_errors(expr), vctrs_error_subscript = function(cnd) {     cnd$subscript_action <- subscript_action(type) ...
#>   .. ..$ : language tryCatchList(expr, classes, parentenv, handlers)
#>   .. ..$ : language tryCatchOne(expr, names, parentenv, handlers[[1L]])
#>   .. ..$ : language doTryCatch(return(expr), name, parentenv, handler)
#>   .. ..$ : language with_entraced_errors(expr)
#>   .. ..$ : language try_fetch(expr, simpleError = function(cnd) {     abort(conditionMessage(cnd), call = conditionCall(cnd)) ...
#>   .. ..$ : language withCallingHandlers(expr, simpleError = function(cnd) {     { ...
#>   .. ..$ : language vars_select_eval(vars, expr, strict = strict, data = x, name_spec = name_spec,      uniquely_named = uniquely_nam| __truncated__ ...
#>   .. ..$ : language walk_data_tree(expr, data_mask, context_mask, error_call)
#>   .. ..$ : language eval_c(expr, data_mask, context_mask)
#>   .. ..$ : language reduce_sels(node, data_mask, context_mask, init = init)
#>   .. ..$ : language walk_data_tree(new, data_mask, context_mask)
#>   .. ..$ : language as_indices_sel_impl(out, vars = vars, strict = strict, data = data, call = error_call)
#>   .. ..$ : language as_indices_impl(x, vars, call = call, strict = strict)
#>   .. ..$ : language chr_as_locations(x, vars, call = call)
#>   .. ..$ : language vctrs::vec_as_location(x, n = length(vars), names = vars)
#>   .. ..$ : language `<fn>`()
#>   .. ..$ : language stop_subscript_oob(i = i, subscript_type = subscript_type, names = names,      subscript_action = subscript_actio| __truncated__
#>   .. ..$ : language stop_subscript(class = "vctrs_error_subscript_oob", i = i, subscript_type = subscript_type,      ...)
#>   .. ..$ : language abort(class = c(class, "vctrs_error_subscript"), i = i, ...)
#>   ..$ parent   : int [1:35] 0 0 2 2 4 5 6 2 0 0 ...
#>   ..$ visible  : logi [1:35] TRUE TRUE TRUE TRUE TRUE TRUE ...
#>   ..$ namespace: chr [1:35] "utils" "rlang" "rlang" "base" ...
#>   ..$ scope    : chr [1:35] "::" "::" "::" "::" ...
#>   ..- attr(*, "version")= int 2
#>  $ i               : chr "bm"
#>  $ subscript_type  : chr "character"
#>  $ names           : chr [1:11] "mpg" "cyl" "disp" "hp" ...
#>  $ subscript_action: chr "subset"
#>  $ subscript_arg   : chr ""
#>  $ call            : language dplyr::select(mtcars, bm)
#>  $ subscript_elt   : chr "column"
#>  - attr(*, "class")= chr [1:5] "vctrs_error_subscript_oob" "vctrs_error_subscript" "rlang_error" "error" ...

What would be the best way to consistently catch the error messages, for ANY function, regardless if it's a dplyr (or generally speaking a tidyverse) verb or a base function?

I think it would be rlang::cnd_message

Hi I’ve looked into it, and there’s still some suprising behaviour wit dplyr::select().

See:

# This gives the "correct" error message
(dplyr::select(mtcars, bm))
#> Error in `dplyr::select()`:
#> ! Can't subset columns that don't exist.
#> x Column `bm` doesn't exist.

# but if I save it and then print it, I find something else
ha <- rlang::cnd_message(
               rlang::catch_cnd(
                        dplyr::select(mtcars, bm)
                      )
               )
print(ha)
#> [1] "`scoped_bindings()` is deprecated as of rlang 0.4.2. Please use\n`local_bindings()` instead."

# pluck_recursive2() finds the same thing (you cand find this function here )
# https://gist.github.com/wch/ad8e5ba859f2968a5c2ce33dd8f692c8
source("https://gist.githubusercontent.com/wch/ad8e5ba859f2968a5c2ce33dd8f692c8/raw/646af26c58cdfb39a7aa5a33a8d510c189575561/pluck_recursive.R")
pluck_recursive2(rlang::catch_cnd(
                          dplyr::select(mtcars, bm)
                        ), "message")
#> [[1]]
#> [1] "`scoped_bindings()` is deprecated as of rlang 0.4.2.\nPlease use `local_bindings()` instead."

there seems to be something specific about select(). I could use rlang::cnd_message() to get the $message and $parent$message objects from the other conditions, but this won’t solve the issue for select().

Ok, thing I’ve got it: that message about scoped_bindings is not an error, so I can get the "correct" error message using:

ha <- rlang::cnd_message(
               rlang::catch_cnd(
                        dplyr::select(mtcars, bm),
                        class = "error"
                      )
               )
print(ha)
[1] "Can't subset columns that don't exist.\n\033[31mx\033[39m Column `bm` doesn't exist."

I think I can work with this!

1 Like

I was thinking of this pattern:

a1 <- tryCatch(dplyr::select(mtcars, bm), 
              error = function(e) e)

(a2 <- rlang::cnd_message(a1))
[1] "Can't subset columns that don't exist.\n\033[31mx\033[39m Column `bm` doesn't exist."

Very nice, that’s exactly what I need! Many thanks!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.