composerr: Compose error handlers (concatenate error messages)

View source: R/composerr.R

composerrR Documentation

Compose error handlers (concatenate error messages)

Description

composerr() is used in order to create so called error or warning handlers. These are mere functions, which have a character argument msg and maybe additional arguments ... and which process this arguments (e.g. call stop(msg)). Often error handling and especially the creation of meaningful error messages takes a lot of time and often leads to a great amount of duplicate code, which is often not easy to maintain. composerr() offers an easy way out, since it simplifies the creation of error handlers with meaningful error messages, by incrementally concatenating more detailed message parts to your error handlers. The call of composerr() does two different things, depending on whether the argument err_h is used or not (in the following ... stands for the arguments before, after, collapse, and action):

  • composerr(...): Create a new error handling function from scratch. This error handling function is returned by the call composerr(...).

  • composerr(..., err_h = err_h_parent): Create a new (child) error handling function from another (parent) error handling function err_h_parent. This new error handling function enhances err_h_parent by adding additional message parts to the message parts defined in err_h_parent. The function composerr returns special composerr class objects, which are functions with the following structure function(msg, action = NULL, ...). The following arguments are available:

  • msg: A character vector passed to the error handler, holding information about one or multiple errors.

  • action: A so called ultimate error handler. This is a function with the following structure function(msg, ...). The ultimate error handler processes the error message after it was fully enriched by all message parts defined in the so called error handler cascade.

  • ...: Additional arguments passed down the error handler cascade and then passed to the ultimate error handler defined in action.

Usage

composerr(
  before = NULL,
  err_h = NULL,
  after = NULL,
  collapse = "\n",
  action = NULL
)

Arguments

before

Optional string that will be prepended to the error message msg, when the resulting error handler is called.

err_h

Optional parent error handler. It must either be NULL or a composerr class object created with the function composerr(). If err_h is omitted, then composerr() creates a new error handler from scratch. Otherwise, the error handler passed to err_h is used as parent error handler for the creation of the new error handler.

after

Optional string that will be appended to the error message msg, when the resulting error handler is called.

collapse

Optional string that will be used for collapsing the resulting error message paste0(before, msg, after), when the resulting error handler is called. If collapse is set to NULL, then the resulting error message will not be collapsed, but directly be passed on.

action

Optional default ultimate error handler. This argument can either be NULL (no default handler defined) or a function function(msg, ...). In the latter case this defines a default function that will be used as ultimate error handler if the current error handler is called without passing an action argument. The used ultimate error handler is passed down the error handler cascade until it ultimately will be called in order to process the successively extended error message (e.g. throw an error or a warning or do something else with the created error message). The argument action can be used, in order to change the default behavior of the error processing to throwing a warning (action = warning) or some other customized behavior like writing the error message to a text file (action = function(msg, ...) cat(msg, file = FILENAME, fill = TRUE)).

Value

The call composerr(...) always returns a new error handler, either created from scratch or enhancing a parent error handler err_h_parent. The resulting error handler is a function with the following arguments:

  • msg: An optional string holding the error message. This error message will be passed down the error handler cascade and get recursively concatenated with additional message parts.

  • action: An optional ultimate error processing function (function(msg = NULL, ...)), which will be passed down the error handler cascade and ultimately be executed.

  • ...: Additional argument, which will be passed down the error handler cascade and ultimately passed to action(msg_full, ...).

Cascading error handlers

If err_h_new <- composerr(...) was created without passing a parent error handler to the optional argument err_h, then err_h_new is a newly created error handler with the following structure err_h_new = function(msg = NULL, action = NULL, ...). Similarly, if err_h_new <- composerr(err_h = err_h_parent, ...) was created by passing a parent error handler err_h = err_h_parent, then err_h_new is also a new error handler, which also has the structure err_h_new = function(msg = NULL, action = NULL, ...), but now it was created from a parent error handler err_h_parent.

We now consider the latter case err_h_new <- composerr(before, err_h_parent, after, action = action_default). If the error handler gets executed with the following call err_h_new(msg, action = action_1, ...), then the following things will happen:

  • if the argument collapse in composerr(...) was set to a string, then err_h_new will first create a more detailed message, by first adding the text parts defined in the arguments before and after and then collapsing the created character vector using the string in collapse as separator msg_new <- paste(paste0(before, msg, after), collapse = collapse)

  • if the argument collapse in composerr(...) was omitted or set to NULL, then err_h_new will first create a more detailed message, by first adding the text parts defined in the arguments before and after, but without collapsing the resulting vector: msg_new <- paste0(before, msg, after)

  • if no argument action is passed to err_h_new (in this example action_1 = NULL), then action will be set to the default ultimate handler (in this example action_default) previously assigned to err_h_new with the argument action. If no default ultimate handler was previously assigned to err_h_new, then action will be set to NULL.

  • Finally, err_h_new will call err_h_parent(msg_new, action = action, ...)

Since err_h_parent is called at the end and err_h_parent will run through the same process, maybe calling its parent error handler, we have a so called cascade of successive error handler calls. The following arguments are passed down this error handler cascade:

  • a successively growing error message msg (incrementally adding text parts in front and at the end of the message).

  • the argument action, which can either be NULL or an ultimate error handling function used for ultimate processing of the created error message (throw an error or a warning or do something else). If the passed down action argument is NULL and at some point in the error handling cascade there was a non-null default ultimate error handler assigned to some error handler in the error handler cascade, then this default ultimate error handler is passed on instead as argument action. Once the ultimate error handler action is not NULL any more, it will remain unchanged, no matter if there were other default ultimate error handlers assigned to other error handlers in the error handler cascade. This means the default ultimate error handlers of the child error handlers overwrite the default ultimate error handlers of its ancestor error handlers.

  • additional arguments ... passed to the call err_h_new(msg, action, ...) will be directly passed down the error handler cascade and directly passed to the ultimate call of the ultimate error handler action(msg_complete, ...).

After cascading backwards the ancestry of err_h_new, we ultimately reach the primal error handler, which was created first (from scratch) and has no further parent error handler. Let us call it err_h_primal. This primal error handler will do the following things:

  • As usual err_h_primal will concatenate its additional text parts before and after to the passed in message. Also it will maybe collapse the resulting character vector, depending if collapse was set to NULL or not. This will give us at last the ultimate version of the error message. Let us call it msg_complete.

  • As usual err_h_primal checks if action is NULL. If so, action is set to the default handler that was assigned to err_h_primal.

  • If the action is still NULL, then action is replaced by the stop() function.

  • Ultimately err_h_primal calls action(msg_complete, ...).

The result of the final call action(msg_complete, ...) is silently returned by err_h_new.

Stacked error messages

Let us assume composerr_halt(err_h_new) was called before calling err_h_new(msg, ...). In this case the error message will not be cascaded down and action will not be called. Instead err_h_new will do the following things:

  • As usual err_h_new will concatenate the additional message parts: msg_new <- paste0(before, msg, after)

  • err_h_new will append the resulting error message msg_new to an internal error stack of err_h_new. err_stack <- c(err_stack, msg_new)

  • err_h_new will return the value NULL

If later on, the function composerr_flush(err_h_new, action = action4, ...) is called, then the error handler cascade will be executed as usual, but first err_h_new will do the following things:

  • If collapse != NULL was assigned to err_h_new, then the entire internal error stack (a character vector holding the stored error messages), will be collapsed to a single string: msg_new <- paste(err_stack, collapse = collapse).

  • If collapse == NULL was omitted when calling err_h_new <- composerr(...), then the internal error stack (a character vector holding the stored error messages) of err_h_new will be used as msg_new, without collapsing it first.

  • As usual, if the argument action = action4 is NULL, then it will be replaced by the default ultimate handler assigned to err_h_new (in this example action_default).

  • err_h_parent(msg_new, action = action) will be called and the error handler cascade will continue as usual.

It is also possible to halt the error execution in the middle of the error handler cascade, by calling composerr_halt(err_h_middle), where err_h_middle is an error handler, that has at least one child error handler err_h_new. If err_h_new or another offspring error handler of err_h_new gets called, then the error handling will be cascaded down as usual until it reaches err_h_middle. There it will be halted and the created message will appended to the internal error message stack of err_h_middle. Only by calling composerr_flush(err_h_middle) the error handling cascade will be continued.

It is also possible to halt the error execution at multiple points of the error handler cascade. In order to cascade the errors down till the end, each halted error handler must be flushed with composerr_flush().

See Also

composerr_flush(), composerr_halt(), composerr_counterr(), composerr_get_action() and validate_composerr()

Examples

## Not run: 
#' ##### Example-1: Create error handlers #####
# create new error handler `err_h1()` from scratch
err_h1 <- composerr(before = "B1", after = "A1")

# `err_h1` throws errors by default
err_h1("XXX")
# Error: B1XXXA1

# also a vector can be used as message
err_h1(c("XXX", "YYY"))
# Error: B1XXXA1
# B1YYYA1

# With `action = warning` a warning will be sent instead
err_h1("XXX", action = warning)
# Warning: B1XXXA1

# create a new error handler `err_h2` based on `err_h1`
err_h2 <- composerr(
  err_h = err_h1,
  before = "B2",
  after = "A2",
  action = message
)

# Because of `action = message`, the handler `err_h2` will print
# plain messages by default
err_h2("XXX")
# B1B2XXXA2A1

# The original error handler `err_h1` remained unchanged
err_h1("XXX")
# Error: B1XXXA1

##### Example-2: Halt/Flush errors #####
# create general error handler
err_h_parent <- composerr(before = "There are problems:\n")

# create a more precise error handler listing the problems
err_h_child <- composerr("  - problem-", err_h = err_h_parent)

# halt `err_h_child` processing in order to collect multiple errors
composerr_halt(err_h_child)
err_h_detail(1)
err_h_detail(2:3)
err_h_detail(4)

composerr_flush(err_h_child)
# Error: There are problems:
#   - problem-1
#   - problem-2
#   - problem-3
#   - problem-4

##### Example-3: Error/Warning handling in functions #####
my_vec_mult <- function(x, y) {
  # create your error handlers
  err_h <- composerr("In `my_vec_mult()`: ")
  err_h_x <- composerr("Invalid argument `x`: ", err_h)
  err_h_y <- composerr("Invalid argument `y`: ", err_h)
  if (!is.numeric(x))
    err_h_x("Not a number.")
  if (any(is.na(x)))
    err_h_x("Has missings.", action = warning)
  if (!is.numeric(y))
    err_h_y("Not a number.")
  if (any(is.na(y)))
    err_h_y("Has missings.", action = warning)
  if (length(x) != length(y))
    err_h("Vectors `x` and `y` have different length.")
  sum(x*y, na.rm = TRUE)
}

my_vec_mult("a", 1:2)
# Error: In `my_vec_mult()`: Invalid argument `x`: Not a number.

my_vec_mult(c(1, NA), 1:2)
# 1
# Warning: In `my_vec_mult()`: Invalid argument `x`: Has missings.

my_vec_mult(1:2, "b")
# Error: In `my_vec_mult()`: Invalid argument `y`: Not a number.

my_vec_mult(1:2, c(1, NA))
# 1
# Warning: In `my_vec_mult()`: Invalid argument `y`: Has non-finite values.

my_vec_mult(1:2, 1:3)
# Error: In `my_vec_mult()`: Vectors `x` and `y` have different length.

my_vec_mult(1:2, 1:2)
# 5

##### Example-4: Error handling in sub routines #####
validate_numeric_vec <- function(obj, err_h) {
  obj_name <- deparse(substitute(obj))
  err_h <- composerr(paste0("Invalid argument `", obj_name, "`: "), err_h)
  if (!is.numeric(obj))
    err_h("Not a number.")
  err_h_list <- composerr(err_h = composerr("\n", err_h))
  composerr_halt(err_h_list)
  for (i in seq_along(obj)) {
    err_h_item <- composerr(paste0("  - Item-", i, " is "), err_h_list)
    if (is.na(obj[i]) && !is.nan(obj[i]))
      err_h_item("NA.")
    if (is.nan(obj[i]))
      err_h_item("NaN.")
    if (is.infinite(obj[i]))
      err_h_item("infinite.")
  }
  composerr_flush(err_h_list)
  invisible(obj)
}

my_vec_mult2 <- function(x, y) {
  err_h <- composerr("In `my_vec_mult2()`: ")
  validate_numeric_vec(x, err_h)
  validate_numeric_vec(y, err_h)
  if (length(x) != length(y))
    err_h("Vectors `x` and `y` have different length.")
  sum(x*y)
}

my_vec_mult2("a", 1:4)
# Error: In `my_vec_mult2()`: Invalid argument `x`: Not a number.

my_vec_mult2(c(1, NA, NaN, Inf, 5), 1:5)
# Error: In `my_vec_mult2()`: Invalid argument `x`:
#   - Item-2 is NA.
#   - Item-3 is NaN.
#   - Item-4 is infinite.

my_vec_mult2(1:5, c(NaN, 2, 3, NA, Inf))
# Error: In `my_vec_mult2()`: Invalid argument `y`:
#   - Item-1 is NA.
#   - Item-4 is NaN.
#   - Item-5 is infinite.

my_vec_mult2(1:5, 1:4)
# Error: In `my_vec_mult2()`: Vectors `x` and `y` have different length.

my_vec_mult2(1:5, 1:5)
# 55

##### Example-5: Write errors to log file #####
logfile = tempfile()
err_h <- composerr(
  "There was a problem: ",
  action = function(msg) {
    cat(msg, file = logfile, append = TRUE, fill = TRUE)
  }
)
err_h("I am hungry.")
err_h("The fridge is empty.")
cat(paste(readLines(logfile), collapse = "\n"))
# There was a problem: I am hungry.
# There was a problem: The fridge is empty.

##### Example-6: Optionally suppressing warnings #####
my_sum <- function(x, suppress_warnings = FALSE) {
  if (isFALSE(suppress_warnings)) {
    err_h <- composerr("Problem in `my_sum()`: ", action = warning)
  } else {
    # ultimate error handler does nothing
    err_h <- composerr(action = function(...) {})
  }
  if (any(is.na(x)))
    err_h("`x` has missing values.")
  sum(x, na.rm = TRUE)
}

my_sum(c(1, 2, NA))
# 3
# Warning: Problem in `my_sum()`: `x` has missing values.

my_sum(c(1, 2, NA), suppress_warnings = TRUE)
# 3

## End(Not run)

a-maldet/composerr documentation built on Oct. 29, 2022, 8:05 p.m.