checkmate icon indicating copy to clipboard operation
checkmate copied to clipboard

support shiny::validate(need()) type of validations

Open maxheld83 opened this issue 6 years ago • 4 comments

I think checkmate is a great standard for input validation, and I'd love to be able to also use it easily inside shiny.

Shiny has validate() for the purpose of validating inputs. It accepts an arbitrary number of expressions which must evaluate to:

  1. NULL if the validation is successful,
  2. the error message as a string, if the object is invalid,
  3. FALSE if the validation itself fails (I'm a bit unclear on this one).

None of the checkmate family members fit right in here, but perhaps you guys could consider need_x() as a new family member.

My (bootstrapped) makeNeedFunction() looks like this:

makeNeedFunction <- function(check.fun) {
  function(x, ...) {
    if (is.null(x)) {
      return(FALSE)
    } else if (isTRUE(check.fun(x, ...))) {
      return(NULL)
    } else {
      return(check.fun(x, ...))
    }
  }
}

It works:

check_character(x = 1)
need_character <- makeNeedFunction(check.fun = check_character)
need_character(x = "a", min.chars = 2)

I'm aware that checkmate has a much superior design, but I got stuck trying to replicate it and don't know C, so I am worried a PR might be beyond my abilities.

Anyway, would you be be interested in such an extension?

I think it could make life a lot easier for shiny devs.

maxheld83 avatar Aug 15 '17 19:08 maxheld83

Here is an example how to turn check functions into test functions: https://github.com/mllg/checkmate/blob/master/R/makeTest.R

No C required. A "need function" constructor would look something like this (w/o the third case which I don't get):

makeNeedFunction = function(check.fun, env = parent.frame()) {
  fn.name = if (!is.character(check.fun)) deparse(substitute(check.fun)) else check.fun
  check.fun = match.fun(check.fun)

  new.fun = function() TRUE
  formals(new.fun) = formals(check.fun)
  tmpl = "{ res = %s(%s); if (identical(res, TRUE)) NULL else res }"
  body(new.fun) = parse(text = sprintf(tmpl, fn.name, paste0(names(formals(check.fun)), collapse = ", ")))
  environment(new.fun) = env
  return(new.fun)
}

print(makeNeedFunction(checkCharacter))

I'll have a closer look if I find some spare time. But should be pretty straightforward to support.

mllg avatar Aug 18 '17 09:08 mllg

The third case would cover situations where the check* function fails because you've passed invalid arguments, like in

checkNames(letters, type = "notype")

Thus, the template should look like this:

tmpl = "{ 
  res = try(%s(%s), silent = TRUE); 
  if (inherits(res, "try-error")) return(FALSE); 
  if (identical(res, TRUE)) return(NULL); 
  return(res) 
}"

mllg avatar Sep 07 '17 09:09 mllg

This kind of validate*() functions would also be very helpful when working with S4 class validations.

danielinteractive avatar Oct 28 '21 11:10 danielinteractive

@mllg would you be open to PRs on the makeNeedFunction topic?

danielinteractive avatar Nov 19 '21 14:11 danielinteractive