The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
restrictR lets you define reusable input contracts from
small building blocks using the base pipe |>. A contract
is defined once and called like a function to validate data at
runtime.
| Section | What you’ll learn |
|---|---|
| Reusable schemas | Define and reuse data.frame contracts |
| Dependent validation | Constraints that reference other arguments |
| Enum arguments | Restrict string arguments to a fixed set |
| Custom steps | Domain-specific invariants |
| Self-documentation | Print, as_contract_text(),
as_contract_block() |
| Using contracts in packages | The recommended pattern for R packages |
The most common use case: validating a newdata argument
in a predict-like function. Instead of scattering
if/stop() blocks, define the contract
once:
require_newdata <- restrict("newdata") |>
require_df() |>
require_has_cols(c("x1", "x2")) |>
require_col_numeric("x1", no_na = TRUE, finite = TRUE) |>
require_col_numeric("x2", no_na = TRUE, finite = TRUE) |>
require_nrow_min(1L)The result is a callable function. Valid input passes silently:
Invalid input produces a structured error with the exact path and position:
require_newdata(data.frame(x1 = c(1, NA), x2 = c(3, 4)))
#> Error:
#> ! newdata$x1: must not contain NA
#> At: 2require_newdata(data.frame(x1 = c(1, 2), x2 = c("a", "b")))
#> Error:
#> ! newdata$x2: must be numeric, got characterEvery error follows the same format: path: message,
optionally followed by Found: and At: lines.
This makes errors instantly recognizable and grep-friendly.
Some contracts depend on context. A prediction vector must have the
same length as the rows in newdata:
require_pred <- restrict("pred") |>
require_numeric(no_na = TRUE, finite = TRUE) |>
require_length_matches(~ nrow(newdata))The formula ~ nrow(newdata) declares a dependency on
newdata. Pass it explicitly when calling the validator:
newdata <- data.frame(x1 = 1:5, x2 = 6:10)
require_pred(c(0.1, 0.2, 0.3, 0.4, 0.5), newdata = newdata)Mismatched lengths produce a precise diagnostic:
require_pred(c(0.1, 0.2, 0.3), newdata = newdata)
#> Error:
#> ! pred: length must match nrow(newdata) (5)
#> Found: length 3Missing context is caught before any checks run:
require_pred(c(0.1, 0.2, 0.3))
#> Error:
#> ! `pred` depends on: newdata. Pass newdata = ... when calling the validator.Context can also be passed as a named list via .ctx:
For string arguments that must be one of a fixed set:
For domain-specific invariants that don’t belong in the built-in set,
use require_custom(). The step function receives
(value, name, ctx) and should call stop() on
failure:
require_weights <- restrict("weights") |>
require_numeric(no_na = TRUE) |>
require_between(lower = 0, upper = 1) |>
require_custom(
label = "must sum to 1",
fn = function(value, name, ctx) {
if (abs(sum(value) - 1) > 1e-8) {
stop(sprintf("%s: must sum to 1, sums to %g", name, sum(value)),
call. = FALSE)
}
}
)Custom steps can also declare dependencies:
require_probs <- restrict("probs") |>
require_numeric(no_na = TRUE) |>
require_custom(
label = "length must match number of classes",
deps = "n_classes",
fn = function(value, name, ctx) {
if (length(value) != ctx$n_classes) {
stop(sprintf("%s: expected %d probabilities, got %d",
name, ctx$n_classes, length(value)), call. = FALSE)
}
}
)
require_probs(c(0.3, 0.7), n_classes = 2L)Print a validator to see its full contract:
require_newdata
#> <restriction newdata>
#> 1. must be a data.frame
#> 2. must have columns: "x1", "x2"
#> 3. $x1 must be numeric (no NA, finite)
#> 4. $x2 must be numeric (no NA, finite)
#> 5. must have at least 1 rowUse as_contract_text() to generate a one-line summary
for roxygen @param:
as_contract_text(require_newdata)
#> [1] "Must be a data.frame. must have columns: \"x1\", \"x2\". $x1 must be numeric (no NA, finite). $x2 must be numeric (no NA, finite). must have at least 1 row."Use as_contract_block() for multi-line output suitable
for @details:
The recommended pattern: define contracts in
R/contracts.R, call them at the top of exported
functions.
# R/contracts.R
require_newdata <- restrict("newdata") |>
require_df() |>
require_has_cols(c("x1", "x2")) |>
require_col_numeric("x1", no_na = TRUE, finite = TRUE) |>
require_col_numeric("x2", no_na = TRUE, finite = TRUE)
require_pred <- restrict("pred") |>
require_numeric(no_na = TRUE, finite = TRUE) |>
require_length_matches(~ nrow(newdata))# R/predict.R
#' Predict from a fitted model
#'
#' @param newdata Must be a data.frame. must have columns: "x1", "x2". $x1 must be numeric (no NA, finite). $x2 must be numeric (no NA, finite). must have at least 1 row.
#' @param ... additional arguments passed to the underlying model.
#'
#' @export
my_predict <- function(object, newdata, ...) {
require_newdata(newdata)
pred <- do_prediction(object, newdata)
require_pred(pred, newdata = newdata)
pred
}Contracts compose naturally with the pipe and branch safely (each
|> creates a new validator):
base <- restrict("x") |> require_numeric()
v1 <- base |> require_length(1L)
v2 <- base |> require_between(lower = 0)
# base is unchanged
length(environment(base)$steps)
#> [1] 1
length(environment(v1)$steps)
#> [1] 2
length(environment(v2)$steps)
#> [1] 2sessionInfo()
#> R version 4.5.2 (2025-10-31 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26200)
#>
#> Matrix products: default
#> LAPACK version 3.12.1
#>
#> locale:
#> [1] LC_COLLATE=C
#> [2] LC_CTYPE=English_United States.utf8
#> [3] LC_MONETARY=English_United States.utf8
#> [4] LC_NUMERIC=C
#> [5] LC_TIME=English_United States.utf8
#>
#> time zone: Europe/Luxembourg
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] restrictR_0.1.0
#>
#> loaded via a namespace (and not attached):
#> [1] digest_0.6.39 R6_2.6.1 fastmap_1.2.0 xfun_0.55
#> [5] glue_1.8.0 cachem_1.1.0 knitr_1.51 htmltools_0.5.9
#> [9] rmarkdown_2.30 lifecycle_1.0.5 cli_3.6.5 vctrs_0.7.1
#> [13] svglite_2.2.2 sass_0.4.10 textshaping_1.0.4 jquerylib_0.1.4
#> [17] systemfonts_1.3.1 compiler_4.5.2 tools_4.5.2 pillar_1.11.1
#> [21] evaluate_1.0.5 bslib_0.9.0 yaml_2.3.12 otel_0.2.0
#> [25] rlang_1.1.7 jsonlite_2.0.0These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.