Skip to content

Commit b2c1b44

Browse files
Merge pull request #5 from eodaGmbH/feature/validate-fn
Add funct to validate func
2 parents 188c681 + 03ba7db commit b2c1b44

File tree

9 files changed

+96
-28
lines changed

9 files changed

+96
-28
lines changed

DESCRIPTION

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ Package: rdantic
22
Type: Package
33
Title: Type Safety for R
44
Version: 0.1.0
5+
Date: 2024-10-13
56
Authors@R: c(
6-
person("Stefan", "Kuethe", email = "crazycapivara@gmail.com", role = c("aut", "cre", "cph"))
7+
person("Stefan", "Kuethe", email = "crazycapivara@gmail.com", role = c("aut", "cre", "cph")),
8+
person("Nico", "Friess", email = "nico.friess@eoda.de", role = "aut")
79
)
810
Maintainer: Stefan Kuethe <crazycapivara@gmail.com>
911
Description: Adds type safety to R.
12+
URL:
13+
https://github.com/eodaGmbH/rdantic
14+
https://eodagmbh.github.io/rdantic/
15+
BugReports: https://github.com/eodaGmbH/rdantic/issues
1016
License: MIT + file LICENSE
1117
Encoding: UTF-8
1218
LazyData: true

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export(keys_to_camel_case)
88
export(keys_to_snake_case)
99
export(model_dump)
1010
export(validate_args)
11+
export(validate_fn)

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# rdantic 0.1.0
2+
3+
* Add NEWS.md to track changes.

R/base-model.R

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ validate_model_values <- function(.obj, validators) {
2323
return(.obj)
2424
}
2525

26+
# TODO: Rename types to fields
27+
# TODO: Can/Should we rename '.obj' to 'obj'?
2628
check_types <- function(types, validators_before = NULL, validators_after = NULL) {
2729
function(.obj = list(), ..., .drop_null = FALSE, .force_list = FALSE) {
2830

@@ -33,6 +35,7 @@ check_types <- function(types, validators_before = NULL, validators_after = NULL
3335
.obj <- utils::modifyList(.obj, list(...), keep.null = TRUE)
3436
}
3537

38+
# TODO: Remove
3639
if (length(.obj) == 0) .obj <- rlang::caller_env()
3740

3841
if (!is.null(validators_before)) {
@@ -42,9 +45,11 @@ check_types <- function(types, validators_before = NULL, validators_after = NULL
4245
# for (k in names(.obj)) {
4346
for (k in names(types)) {
4447
if (!is.environment(.obj) & !k %in% names(.obj)) {
48+
# TODO: Do we really want this?
4549
.obj[k] <- list(NULL)
4650
}
4751

52+
# TODO: Rename to 'fn_type_check'
4853
type_check <- rlang::as_function(types[[k]])
4954
value <- .obj[[k]]
5055
if (!type_check(value)) {
@@ -105,6 +110,27 @@ model_dump <- function(.obj,
105110
return(.obj)
106111
}
107112

113+
# ---
114+
#' Validate function arguments
115+
#' @inherit base_model params return
116+
#' @export
117+
validate_args <- function(..., .validators_before = NULL, .validators_after = NULL) {
118+
base_model(
119+
...,
120+
.validators_before = .validators_before,
121+
.validators_after = .validators_after
122+
)(rlang::caller_env())
123+
}
124+
125+
# ---
126+
#' Validate function parameters
127+
#' @param fn function containing the arguments to be checked
128+
#' @export
129+
validate_fn <- function(fn) {
130+
fmls<- rlang::fn_fmls(fn)
131+
fields <- purrr::map(as.list(fmls), eval)
132+
purrr::exec(base_model, !!!fields)(rlang::caller_env())
133+
}
108134

109135
# model_dump_json <- function(.obj, ...) {
110136
# model_dump(.obj, ...) |>

R/types.R

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,3 @@ is_optional2 <- function(.f) set_attributes(.f, optional = TRUE)
1717
is_another_model <- function(.model) {
1818
function(x) is.list(.model(x))
1919
}
20-
21-
# ---
22-
#' Validate function arguments
23-
#' @inherit base_model params return
24-
#' @export
25-
validate_args <- function(..., .validators_before = NULL, .validators_after = NULL) {
26-
base_model(
27-
...,
28-
.validators_before = .validators_before,
29-
.validators_after = .validators_after
30-
)(rlang::caller_env())
31-
}

man/validate_args.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/validate_fn.Rd

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-base-model.R

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ test_that("lists", {
1111

1212
# Assert
1313
expect_equal(l, list(a = 10.5, b = 20L, txt = "Hi"))
14+
# expect_equal(names(l), c("a", "b", "txt"))
1415
})
1516

1617
test_that("validators before", {
@@ -47,3 +48,17 @@ test_that("validate func args", {
4748
# Assert
4849
expect_equal(res, 6L)
4950
})
51+
52+
test_that("validate func", {
53+
# Prepare
54+
f_with_typed_args <- function(a = is.integer, b = is.integer) {
55+
validate_fn(f_with_typed_args)
56+
a + b
57+
}
58+
59+
# Act
60+
res <- f_with_typed_args(2L, 5L)
61+
62+
# Assert
63+
expect_equal(res, 7L)
64+
})

vignettes/rdantic.Rmd

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,59 +22,59 @@ library(rlang)
2222
## Models
2323

2424
```{r}
25-
person <- base_model(
25+
person_model <- base_model(
2626
name = is_scalar_character,
2727
age = is_scalar_integer
2828
)
2929
30-
person(
30+
person_model(
3131
name = "Lee",
3232
age = 100L
3333
)
3434
35-
try(person(name = "Lee", age = 100))
35+
try(person_model(name = "Lee", age = 100))
3636
3737
# It is also possible to pass lists:
38-
response_from_rest_api <- list(name = "Morgan", age = 100L)
38+
external_data <- list(name = "Morgan", age = 100L)
3939
40-
response_from_rest_api |> person()
40+
person_model(external_data)
4141
```
4242

4343
## Validators
4444

4545
```{r}
46-
person <- base_model(
46+
person_model <- base_model(
4747
name = is_scalar_character,
4848
age = is_scalar_integer,
4949
.validators_before = list(
5050
age = as.integer
5151
)
5252
)
5353
54-
person(name = "Lee", age = 100)
54+
person_model(name = "Lee", age = 100)
5555
```
5656

5757
## Models inside models
5858

5959
```{r}
60-
address <- base_model(
60+
address_model <- base_model(
6161
city = is_scalar_character,
6262
country = is_scalar_character
6363
)
6464
65-
person <- base_model(
65+
person_model <- base_model(
6666
name = is_scalar_character,
6767
age = is_scalar_integer,
68-
address = is_another_model(address)
68+
address = is_another_model(address_model)
6969
)
7070
71-
person(
71+
person_model(
7272
name = "Peter",
7373
age = 12L,
74-
address = address(city = "Kassel", country = "Germany")
74+
address = address_model(city = "Kassel", country = "Germany")
7575
)
7676
77-
response_from_rest_api <- list(
77+
external_data <- list(
7878
statusCode = 200L,
7979
data = list(
8080
value = 10,
@@ -95,7 +95,7 @@ api_model <- base_model(
9595
data = is_another_model(data_model)
9696
)
9797
98-
response_from_rest_api |>
98+
external_data |>
9999
keys_to_snake_case() |>
100100
api_model()
101101
```
@@ -111,6 +111,21 @@ add_two_numbers(10, 20)
111111
112112
try(add_two_numbers(10, c(20, 30)))
113113
```
114+
115+
It also possible to add the type checks in this way:
116+
117+
```{r}
118+
add_two_numbers <- function(a = is_scalar_integer,
119+
b = is_scalar_integer) {
120+
validate_fn(add_two_numbers)
121+
a + b
122+
}
123+
124+
add_two_numbers(1L, 3L)
125+
126+
try(add_two_numbers(1L, 4.6))
127+
```
128+
114129
## Settings
115130

116131
```{r}

0 commit comments

Comments
 (0)