R/enum.R

Defines functions enum_case `==.shinyloadtest_enum` `$.shinyloadtest_enum` enum `==.shinyloadtest_enum_value` enum_value

enum_value <- function(x, enum_id, all_val) {
  structure(
    x,
    enum_id = enum_id,
    all_val = all_val,
    class = "shinyloadtest_enum_value"
  )
}

`==.shinyloadtest_enum_value` <- function(x, y) {
  if (class(y) != "shinyloadtest_enum_value") return(FALSE)
  identical(x, y) && (attr(x, "enum_id") == attr(y, "enum_id"))
}

enum_counter <- local({
  n <- 0L
  function() (n <<- n + 1L)
})

enum <- function(...) {
  val_sym <- rlang::ensyms(...)
  val_str <- vapply(val_sym, as.character, character(1))
  structure(
    stats::setNames(lapply(val_str, enum_value, enum_counter(), val_str), val_str),
    class = "shinyloadtest_enum"
  )
}

`$.shinyloadtest_enum` <- function(x, i) {
  if (!(i %in% names(x))) stop("Unknown enum value")
  NextMethod()
}
`[[.shinyloadtest_enum` <- `$.shinyloadtest_enum`
`==.shinyloadtest_enum` <- function(x, y) {
  identical(x, y)
}

enum_case <- function(field, ...) {
  stopifnot(inherits(field, "shinyloadtest_enum_value"))
  cases <- rlang::enquos(..., .named = TRUE, .homonyms = "error")
  all_val <- attr(field, "all_val")

  unknown_values <- base::setdiff(names(cases), all_val)
  if (length(unknown_values))
    stop(paste("Unknown enum value", paste(unknown_values, collapse = ", ")), call. = FALSE)

  missing_values <- base::setdiff(all_val, names(cases))
  if (length(missing_values))
    stop(paste("Missing enum value", paste(missing_values, collapse = ", ")), call. = FALSE)

  rlang::eval_tidy(cases[[field]])
}

# The idea here is we have an object, Frobs, representing a set of named, distinct, values:
#
# Frobs <- enum(FOO, BAR, BAZ)
#
# Values are equal to themselves:
#
# Frobs$FOO == Frobs$FOO
# [1] TRUE
#
# Values are not equal to values from other enums:
#
# Blobs <- enum(FOO, BAR, BAZ)
#
# Frobs$FOO == Blobs$FOO
# [1] FALSE
#
# Partial matching of a value, or referring to an unknown value, produces an error:
#
# Frobs$FO
# Error in `$.shinyloadtest_enum`(Frobs, FO) : Unknown enum value
#
# A conditional construct, `enum_case`, is provided to dispatch
# on a value and to perform exhaustiveness checking of the other values from the enum
# at runtime:
#
# some_field <- Frobs$FOO
# enum_case(some_field,
#   FOO = "foo",
#   BAR = "bar"
# )
#
# Error: Missing enum value BAZ
#
# An error message is also produced if a field is mentioned that doesn't exist
# in the enum:
#
# some_val <- Frobs$FOO
# enum_case(some_val, FOO = 1, BAR = 2, BAZ = 3, QUX = 3)
# enum_case(some_field,
#   FOO = "foo",
#   BAR = "bar",
#   BAZ = "baz",
#   QUX = "qux"
# )
#
# Error: Unknown enum value QUX
#
# Since the exhaustiveness checking is at runtime, the use of enums doesn't provide
# any safety guarantees that couldn't be provided by e.g. assertions. However,
# if use of an enum-like construct is desirable, then using this mechanism
# provides a degree of run-time error checking without explicit assertions.
rstudio/shinyloadtest documentation built on Jan. 21, 2023, 11:14 p.m.