MyNixOS website logo
Description

Composable Runtime Contracts for R.

Build reusable validators from small building blocks using the base pipe operator. Define runtime contracts once with 'restrict()' and enforce them anywhere in code. Validators compose naturally, support dependent rules via formulas, and produce clear, path-aware error messages. No DSL, no operator overloading, just idiomatic R.

restrictR

R-CMD-check Codecov test coverage License: MIT

Composable Runtime Contracts for R

The restrictR package lets you define reusable input contracts from small building blocks using the base pipe |>. Define a validator once, enforce it anywhere. Validators compose naturally, support dependent rules via formulas, and produce clear, path-aware error messages. No DSL, no operator overloading, just idiomatic R.

Quick Start

library(restrictR)

# Define once
require_positive_scalar <- restrict("x") |>
  require_numeric(no_na = TRUE) |>
  require_length(1L) |>
  require_between(lower = 0, exclusive_lower = TRUE)

# Enforce anywhere
require_positive_scalar(3.14)   # passes silently
require_positive_scalar(-1)     # Error: x: must be in (0, Inf]
                                #   Found: -1
                                #   At: 1

Statement of Need

R has no built-in way to define reusable input contracts. Developers copy-paste the same stopifnot() / if (!is.numeric(...)) stop(...) blocks across functions. When the contract changes, you hunt for every validation site. Error messages are inconsistent: one function says "x must be numeric", another says "expected numeric input".

restrictR replaces that with composable, pipe-friendly validators that:

  • are defined once and called like functions,
  • produce structured, grep-friendly error messages,
  • support dependent rules via explicit context,
  • are self-documenting via print() and as_contract_text().

Features

Schema Validation

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)

Dependent Rules

require_pred <- restrict("pred") |>
  require_numeric(no_na = TRUE) |>
  require_length_matches(~ nrow(newdata))

# Context is explicit, never magic
require_pred(predictions, newdata = df)

Path-Aware Error Messages

newdata$x2: must be numeric, got character
pred: length must match nrow(newdata) (100)
  Found: length 50
x: must not contain NA
  At: 2, 5, 9

Self-Documenting

print(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 row

as_contract_text(require_newdata)
#> "Must be a data.frame. Must have columns: \"x1\", \"x2\". ..."

Installation

# Install development version from GitHub
# install.packages("pak")
pak::pak("gcol33/restrictR")

Usage Examples

In Functions

predict2 <- function(object, newdata, ...) {
  require_newdata(newdata)
  out <- predict(object, newdata = newdata)
  require_pred(out, newdata = newdata)
  out
}

Enum Validation

require_method <- restrict("method") |>
  require_character(no_na = TRUE) |>
  require_length(1L) |>
  require_one_of(c("euclidean", "manhattan", "cosine"))

compute_distance <- function(x, y, method = "euclidean") {
  require_method(method)
  # ...
}

Column-Level Checks

require_survey <- restrict("survey") |>
  require_df() |>
  require_has_cols(c("age", "income", "status")) |>
  require_col_numeric("age", no_na = TRUE) |>
  require_col_between("age", lower = 0, upper = 150) |>
  require_col_numeric("income", no_na = TRUE, finite = TRUE) |>
  require_col_one_of("status", c("active", "inactive", "pending"))

Roxygen Integration

#' @param newdata `r as_contract_text(require_newdata)`

Custom Steps

For domain-specific invariants:

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)
      }
    }
  )

Built-In Steps

CategorySteps
Type checksrequire_df(), require_numeric(), require_integer(), require_character(), require_logical()
Missingnessrequire_no_na(), require_finite()
Structurerequire_length(), require_length_min(), require_length_max(), require_length_matches(), require_nrow_min(), require_nrow_matches(), require_has_cols()
Valuesrequire_between(), require_one_of()
Columnsrequire_col_numeric(), require_col_character(), require_col_between(), require_col_one_of()
Extensionrequire_custom()

Documentation

Support

"Software is like sex: it's better when it's free." -- Linus Torvalds

I'm a PhD student who builds R packages in my free time because I believe good tools should be free and open. I started these projects for my own work and figured others might find them useful too.

If this package saved you some time, buying me a coffee is a nice way to say thanks. It helps with my coffee addiction.

Buy Me A Coffee

License

MIT (see the LICENSE file)

Citation

@software{restrictR,
  author = {Colling, Gilles},
  title = {restrictR: Composable Runtime Contracts for R},
  year = {2026},
  url = {https://github.com/gcol33/restrictR}
}
Metadata

Version

0.1.0

License

Unknown

Platforms (78)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    uefi
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-freebsd
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64-uefi
  • aarch64-windows
  • aarch64_be-none
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-linux
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-uefi
  • x86_64-windows