---
title: "Check Arguments and Generate Readable Error Messages"
output:
  rmarkdown::html_vignette:
    toc: true
vignette: >
  %\VignetteIndexEntry{Check Arguments and Generate Readable Error Messages}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  error = TRUE
)
```

The package name "erify" is derived from "verify" and "error",
which are the package's two main themes.


## Motivation

When creating functions for other people to use, you always need some
validator functions which

1. check if arguments passed by users are valid, and if not,
2. generate informative and well-formatted error messages in a
consistent style.

erify serves the exact purpose. Specifically, erify provides

1. several handy validator functions for checking if an argument has valid
type, class, length, etc., and
2. tools with which you can create your own validator functions.


## Installation

Install erify from CRAN:

```r
install.packages("erify")
```

Or install the development version from GitHub:

```r
# install devtools if not
# install.packages("devtools")

devtools::install_github("flujoo/erify")
```


## Validator Functions

erify has some ready-to-use validator functions. Let's have a look.

```{r}
library(erify)
```

Suppose you are creating a function which prints a string several times
to emphasize it:

```{r}
# print `what` `n` times
emphasize <- function(what, n) {
  for (i in 1:n) {
    cat(what, "\n")
  }
}

# example
emphasize("You're beautiful!", 3)
```

And suppose a novice user accidentally passes a function to argument `what`,
he/she will get an error message which is not very readable:

```{r}
emphasize(c, 3)
```

You can improve this by adding erify's `check_type()` into `emphasize()`:

```{r}
emphasize <- function(what, n) {
  # check the type of `what`
  check_type(what, "character")
  
  # main
  for (i in 1:n) {
    cat(what, "\n")
  }
}

emphasize(c, 3)
```

In the above code, `check_type(what, "character")` checks if `what` has type
character, and if not, generates improved error message.

You can add more validator functions. For example, suppose you want `what`
to be a single character, which means it must have length 1. You can check
its length with `check_length()`:

```{r}
emphasize <- function(what, n) {
  # check the type of `what`
  check_type(what, "character")
  # check the length of `what`
  check_length(what, 1)
  
  # main
  for (i in 1:n) {
    cat(what, "\n")
  }
}

emphasize(c("apple", "orange"), 3)
```

In the above code, `check_length(what, 1)` checks if `what` has length
exactly 1.

Maybe this is too strict. You feel any non-empty character vector is
acceptable. You can change the second argument of `check_length()` in
the above code:

```{r}
emphasize <- function(what, n) {
  # check the type of `what`
  check_type(what, "character")
  # check the length of `what`
  check_length(what, c(0, NA))
  
  # main
  for (i in 1:n) {
    cat(what, "\n")
  }
}

emphasize(character(0), 3)
```

`check_length(what, c(0, NA))` checks if `what` has length larger than 0.

erify's validator functions return silently if the argument they
are checking is valid. For example,

```{r}
emphasize("You're beautiful again!", 3)
```


## Error Message Style

So far, you may have noticed that the error messages generated by erify's
validator functions have a consistent style.

Specifically, in this style, an error message usually has two components:

First, a general statement of the problem. For example:

```r
#> Error: `what` must have type character.
```

Second, a concise description of what went wrong. For example:

```r
#> * `what` has type builtin.
```

The second component may contain more items, as you will see.

This style is adopted from Hadley Wickham's
[The tidyverse style guide](https://style.tidyverse.org/error-messages.html).
Check the link for more details.


## Customize Error Messages

You can change the error message generated by any erify's validator function
by specify arguments `general`, `specific` and `supplement`.

For example, suppose you want an argument `arg` to take only `"yes"` or
`"no"` as input. You can put this restriction with `check_content()`:

```{r}
arg <- "I'm invalid."

# check the content of `arg`
check_content(arg, c("yes", "no"))
```

To change the default general statement of the error, you can specify
argument `general`. For example,

```{r}
check_content(arg, c("yes", "no"), general = "You are wrong.")
```

To change the default description of the error, you can specify argument
`specific`. For example,

```{r}
check_content(arg, c("yes", "no"), specific = "You are wrong.")
```

You can add more details with argument `supplement`. For example,

```{r}
supplement <- c(x = "You're wrong.", i = "But you're beautiful.")
check_content(arg, c("yes", "no"), supplement = supplement)
```

In the above code, `x` means "error", while `i` means "hint".


## Create Validator Functions

You can create your own validator functions with `throw()`. Below
is an example:

```{r}
general <- "You're beautiful."

specifics <- c(
  i = "Your eyes are big.",
  i = "Your hair is long.",
  x = "But you broke my heart."
)

throw(general, specifics)
```

You can change argument `as` to generate a message:

```{r}
throw(general, specifics, as = "message")
```

Now let's create a validator function to check if an argument is a positive
number.

```{r}
check_positive <- function(x) {
  check_type(x, c("integer", "double"))
  check_length(x, 1)
  
  if (is.na(x) || x <= 0) {
    general <- "`x` must be a positive number."
    specifics <- "`x` is `{x}`."
    throw(general, specifics, env = list(x = x))
  }
}

check_positive(-2)
```

As you might have noticed, you can insert R code into `general` and
`specifics` as `{x}` in

```r
specifics <- "`x` is `{x}`."
```

To execute the code, you need to pass the arguments involved to argument
`env`, as in

```r
throw(general, specifics, env = list(x = x))
```

See `glue::glue()` for more details.


## Other Tools

`join()` connects given words with a conjunction:

```{r}
x <- c("Pink Floyd", "Pink Freud", "Pink Florida")
join(x, "and")
```

`back_quote()` convert an R object to character and add back quotations:

```{r}
cat(back_quote(x))

back_quote(c(1, 2, NA))
```

These two functions are useful to create error messages, as in the inside
of `check_content()`:

```{r}
arg <- "Pink Florence"
check_content(arg, x)
```
