Skip to content

Commit 295fe84

Browse files
committed
quarto_render changes the class of vectors containing NA values
Fixes #168 We now catch any NA in params from R side to ask user to manually change them as they won't be supported and will be lost on Quarto side, as NA is not YAML compatible.
1 parent d06e627 commit 295fe84

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed

R/render.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ quarto_render <- function(
164164
args <- c(args, ifelse(isTRUE(execute), "--execute", "--no-execute"))
165165
}
166166
if (!missing(execute_params)) {
167+
# Check for NA values in execute_params and error with helpful guidance
168+
# This is because R's NA is not valid in YAML, and it would not be loaded
169+
# correctly by quarto (using YAML 1.2).
170+
check_params_for_na(execute_params)
171+
167172
params_file <- tempfile(pattern = "quarto-params", fileext = ".yml")
168173
write_yaml(execute_params, params_file)
169174
args <- c(args, "--execute-params", params_file)

R/utils.R

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,35 @@ yaml_handlers <- list(
1414
}
1515
)
1616

17+
check_params_for_na <- function(params) {
18+
# Recursively check for NA values
19+
check_na_recursive <- function(x, path = "") {
20+
if (is.list(x)) {
21+
for (i in seq_along(x)) {
22+
name <- names(x)[i] %||% as.character(i)
23+
new_path <- if (path == "") name else paste0(path, "$", name)
24+
check_na_recursive(x[[i]], new_path)
25+
}
26+
} else if (any(is.na(x) & !is.nan(x))) {
27+
# Found NA values (excluding NaN which is mathematically valid)
28+
na_positions <- which(is.na(x) & !is.nan(x))
29+
n_na <- length(na_positions)
30+
31+
cli::cli_abort(c(
32+
"{.code NA} values detected in parameter {.field {path}}",
33+
"x" = "Found NA at position{if (n_na > 1) 's' else ''}: {.val {na_positions}}",
34+
"i" = "Quarto parameters cannot contain NA values",
35+
" " = "Consider these alternatives:",
36+
"*" = "Remove NA values from your data before passing to Quarto",
37+
"*" = "Use {.code NULL} instead of {.code NA} for missing optional parameters",
38+
"*" = "Handle missing values within your document code using conditional logic"
39+
))
40+
}
41+
}
42+
43+
check_na_recursive(params)
44+
}
45+
1746
#' @importFrom yaml as.yaml
1847
as_yaml <- function(x) {
1948
yaml::as.yaml(x, handlers = yaml_handlers)
@@ -38,7 +67,7 @@ merge_list <- function(x, y) {
3867
}
3968

4069
`%||%` <- function(x, y) {
41-
if (is_null(x)) y else x
70+
if (rlang::is_null(x)) y else x
4271
}
4372

4473
in_positron <- function() {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# check_params_for_na detects NA in simple vectors
2+
3+
Code
4+
check_params_for_na(bad_params)
5+
Condition
6+
Error in `check_na_recursive()`:
7+
! `NA` values detected in parameter values
8+
x Found NA at position: 2
9+
i Quarto parameters cannot contain NA values
10+
Consider these alternatives:
11+
* Remove NA values from your data before passing to Quarto
12+
* Use `NULL` instead of `NA` for missing optional parameters
13+
* Handle missing values within your document code using conditional logic
14+
15+
# check_params_for_na detects NA in nested structures
16+
17+
Code
18+
check_params_for_na(nested_params)
19+
Condition
20+
Error in `check_na_recursive()`:
21+
! `NA` values detected in parameter data$subset
22+
x Found NA at position: 2
23+
i Quarto parameters cannot contain NA values
24+
Consider these alternatives:
25+
* Remove NA values from your data before passing to Quarto
26+
* Use `NULL` instead of `NA` for missing optional parameters
27+
* Handle missing values within your document code using conditional logic
28+
29+
# check_params_for_na shows correct NA positions
30+
31+
Code
32+
check_params_for_na(multi_na_params)
33+
Condition
34+
Error in `check_na_recursive()`:
35+
! `NA` values detected in parameter x
36+
x Found NA at positions: 2 and 4
37+
i Quarto parameters cannot contain NA values
38+
Consider these alternatives:
39+
* Remove NA values from your data before passing to Quarto
40+
* Use `NULL` instead of `NA` for missing optional parameters
41+
* Handle missing values within your document code using conditional logic
42+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
test_that("check_params_for_na allows clean parameters", {
2+
# Should pass without error
3+
good_params <- list(
4+
a = 1:5,
5+
b = c("hello", "world"),
6+
nested = list(x = 10, y = 20)
7+
)
8+
9+
expect_silent(check_params_for_na(good_params))
10+
})
11+
12+
test_that("check_params_for_na detects NA in simple vectors", {
13+
bad_params <- list(values = c(1, NA, 3))
14+
15+
expect_snapshot(
16+
error = TRUE,
17+
check_params_for_na(bad_params),
18+
)
19+
})
20+
21+
test_that("check_params_for_na detects NA in nested structures", {
22+
nested_params <- list(
23+
data = list(
24+
subset = c(1, NA, 3)
25+
)
26+
)
27+
28+
expect_snapshot(
29+
error = TRUE,
30+
check_params_for_na(nested_params),
31+
)
32+
})
33+
34+
test_that("check_params_for_na shows correct NA positions", {
35+
multi_na_params <- list(x = c(1, NA, 3, NA, 5))
36+
37+
expect_snapshot(
38+
error = TRUE,
39+
check_params_for_na(multi_na_params),
40+
)
41+
})
42+
43+
test_that("check_params_for_na handles different NA types", {
44+
# Test different NA types
45+
expect_error(
46+
check_params_for_na(list(x = NA_real_)),
47+
"NA.*values detected"
48+
)
49+
50+
expect_error(
51+
check_params_for_na(list(x = NA_integer_)),
52+
"NA.*values detected"
53+
)
54+
55+
expect_error(
56+
check_params_for_na(list(x = NA_character_)),
57+
"NA.*values detected"
58+
)
59+
60+
expect_error(
61+
check_params_for_na(list(x = c(TRUE, NA))),
62+
"NA.*values detected"
63+
)
64+
})

0 commit comments

Comments
 (0)