update types for new api

This commit is contained in:
Tom Elliott 2025-01-08 16:02:59 +13:00
parent 639a7779c9
commit 16ed412cd7
7 changed files with 194 additions and 40 deletions

View File

@ -10,6 +10,9 @@ License: MIT + file LICENSE
Encoding: UTF-8 Encoding: UTF-8
Roxygen: list(markdown = TRUE) Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.1 RoxygenNote: 7.3.1
Suggests: Suggests:
testthat (>= 3.0.0) testthat (>= 3.0.0)
Config/testthat/edition: 3 Config/testthat/edition: 3
Imports:
cli,
js

View File

@ -11,3 +11,6 @@ install: document
README.md: README.Rmd install README.md: README.Rmd install
Rscript -e "rmarkdown::render('README.Rmd')" Rscript -e "rmarkdown::render('README.Rmd')"
@rm README.html @rm README.html
test:
Rscript -e "devtools::test()"

View File

@ -2,15 +2,15 @@
#' #'
#' This is the base type for all typed objects. It is not meant to be used directly. #' This is the base type for all typed objects. It is not meant to be used directly.
#' #'
#' @param type The type of the object that Typescript expect to send to R. #' @param input_type The type of the object that Typescript expect to send to R.
#' @param type_fn The type of the object that Typescript expects to recieve from R. #' @param return_type The type of the object that Typescript expects to recieve from R.
#' @param default The default value of the object. #' @param default The default value of the object.
#' @param check A function that checks the object and returns it if it is valid. This operates on the R side and is mostly for development and debugging purposes. It is up to the developer to ensure that all functions return the correct type of object always. #' @param check A function that checks the object and returns it if it is valid. This operates on the R side and is mostly for development and debugging purposes. It is up to the developer to ensure that all functions return the correct type of object always.
#' @param generic logical, if `TRUE` then the object is a generic type. #' @param generic logical, if `TRUE` then the object is a generic type.
#' #'
#' @md #' @md
object <- function(zod_type = "any", object <- function(input_type = "any",
r_type = "any", return_type = "any",
default = NULL, default = NULL,
check = function() stop("Not implemented"), check = function() stop("Not implemented"),
generic = FALSE) { generic = FALSE) {
@ -27,8 +27,20 @@ object <- function(zod_type = "any",
#' @export #' @export
print.ts_object <- function(x, ...) { print.ts_object <- function(x, ...) {
# name <- deparse(substitute(x)) # name <- deparse(substitute(x))
cat(sprintf("Zod type: %s\n", x$zod_type)) cli::cli_ul()
cat(sprintf(" R type: %s\n", x$r_type)) cli::cli_h3("Input type: ")
if (nchar(x$input_type) > 50) {
cat(js::uglify_reformat(x$input_type, beautify = TRUE), "\n")
} else {
cat(x$input_type, "\n")
}
cli::cli_h3("Return type: ")
if (nchar(x$return_type) > 50) {
cat(js::uglify_reformat(x$return_type, beautify = TRUE), "\n")
} else {
cat(x$return_type, "\n")
}
cli::cli_end()
} }
is_object <- function(x) { is_object <- function(x) {
@ -43,7 +55,7 @@ ts_array <- function(type = c("z.number()", "z.boolean()", "z.string()")) {
if (type == "z.boolean()") { if (type == "z.boolean()") {
return("z.instanceof(Uint8Array)") return("z.instanceof(Uint8Array)")
} }
return("RTYPE.stringArray") return("Robj.character(0)")
} }
n_type <- function(n, type, pl = ts_array(type)) { n_type <- function(n, type, pl = ts_array(type)) {
@ -56,20 +68,19 @@ n_type <- function(n, type, pl = ts_array(type)) {
pl pl
} }
n_type_fun <- function(n, type) { n_type_fun <- function(n, type) {
if (n < 0) { sprintf("%s(%s)", type, ifelse(n < 0, "", n))
return(type)
}
sprintf("%s(%s)", type, n)
} }
#' @export #' @export
ts_logical <- function(n = -1L) { ts_logical <- function(n = -1L) {
object( object(
n_type(n, "z.boolean()"), n_type(n, "z.boolean()"),
n_type_fun(n, "RTYPE.logical"), n_type_fun(n, "Robj.logical"),
check = function(x) { check = function(x) {
if (!is.logical(x)) stop("Expected a boolean") if (!is.logical(x)) stop("Expected a boolean")
if (n > 0 && length(x) != n) stop("Expected a boolean of length ", n) if (n > 0 && length(x) != n) {
stop("Expected a boolean of length ", n)
}
x x
} }
) )
@ -79,10 +90,12 @@ ts_logical <- function(n = -1L) {
ts_integer <- function(n = -1L) { ts_integer <- function(n = -1L) {
object( object(
n_type(n, "z.number()"), n_type(n, "z.number()"),
n_type_fun(n, "RTYPE.integer"), n_type_fun(n, "Robj.integer"),
check = function(x) { check = function(x) {
if (!is.integer(x)) stop("Expected an integer") if (!is.integer(x)) stop("Expected an integer")
if (n > 0 && length(x) != n) stop("Expected an integer of length ", n) if (n > 0 && length(x) != n) {
stop("Expected an integer of length ", n)
}
x x
} }
) )
@ -92,7 +105,7 @@ ts_integer <- function(n = -1L) {
ts_numeric <- function(n = -1L) { ts_numeric <- function(n = -1L) {
object( object(
n_type(n, "z.number()"), n_type(n, "z.number()"),
n_type_fun(n, "RTYPE.numeric"), n_type_fun(n, "Robj.numeric"),
check = function(x) { check = function(x) {
if (!is.numeric(x)) stop("Expected a number", call. = FALSE) if (!is.numeric(x)) stop("Expected a number", call. = FALSE)
if (n > 0 && length(x) != n) { if (n > 0 && length(x) != n) {
@ -107,7 +120,7 @@ ts_numeric <- function(n = -1L) {
ts_character <- function(n = -1L) { ts_character <- function(n = -1L) {
object( object(
n_type(n, "z.string()"), n_type(n, "z.string()"),
n_type_fun(n, "RTYPE.character"), n_type_fun(n, "Robj.character"),
check = function(x) { check = function(x) {
if (!is.character(x)) stop("Expected a string") if (!is.character(x)) stop("Expected a string")
if (n > 0 && length(x) != n) stop("Expected a string of length ", n) if (n > 0 && length(x) != n) stop("Expected a string of length ", n)
@ -130,11 +143,14 @@ vector_as_ts_array <- function(x) {
#' @md #' @md
ts_factor <- function(levels = NULL) { ts_factor <- function(levels = NULL) {
object( object(
sprintf("(%s)[]", paste(levels, collapse = " | ")), ifelse(is.null(levels),
ts_array("z.string()"),
sprintf("(%s)[]", paste(levels, collapse = " | "))
),
if (is.null(levels)) { if (is.null(levels)) {
"Factor" "Robj.factor()"
} else { } else {
sprintf("Factor<%s>", vector_as_ts_array(levels)) sprintf("Robj.factor(%s)", vector_as_ts_array(levels))
}, },
check = function(x) { check = function(x) {
if (!is.factor(x)) stop("Expected a factor") if (!is.factor(x)) stop("Expected a factor")
@ -161,30 +177,43 @@ ts_list <- function(values = NULL) {
type <- "z.union([z.object({}), z.array(z.any())])" type <- "z.union([z.object({}), z.array(z.any())])"
type_fn <- "" type_fn <- ""
if (!is.null(values)) { if (!is.null(values)) {
types <- sapply(values, function(x) x$zod_type) types <- sapply(values, function(x) x$input_type)
type_funs <- sapply(values, function(x) x$r_type) type_funs <- sapply(values, function(x) x$return_type)
if (!is.null(names(values))) { if (!is.null(names(values))) {
type <- sprintf( type <- sprintf(
"{%s}", "{ %s }",
paste(names(values), types, sep = ": ", collapse = ", ") paste(names(values), types, sep = ": ", collapse = ", ")
) )
type_fn <- sprintf( type_fn <- sprintf(
"{%s}", "{ %s }",
paste(names(values), type_funs, sep = ": ", collapse = ", ") paste(names(values), type_funs, sep = ": ", collapse = ", ")
) )
} else { } else {
type <- sprintf("[%s]", paste(values, collapse = ", ")) type <- sprintf("[%s]", paste(types, collapse = ", "))
type_fn <- sprintf("[%s]", paste(type_funs, collapse = ", ")) type_fn <- sprintf("[%s]", paste(type_funs, collapse = ", "))
} }
} }
object( object(
type, type,
ifelse(type_fn == "", "RTYPE.List", ifelse(type_fn == "", "Robj.list()",
sprintf("RTYPE.List<%s>", type_fn) sprintf("Robj.list(%s)", type_fn)
), ),
check = function(x) { check = function(x) {
if (!is.list(x)) stop("Expected a list") if (!is.list(x)) stop("Expected a list")
if (!is.null(values)) {
if (!is.null(names(values))) {
if (!identical(names(x), names(values))) {
stop(
"Expected a list with names: ",
paste(names(values), collapse = ", ")
)
}
}
for (i in seq_along(values)) {
values[[i]]$check(x[[i]])
}
}
x x
} }
) )
@ -199,15 +228,15 @@ ts_list <- function(values = NULL) {
#' @md #' @md
ts_dataframe <- function(...) { ts_dataframe <- function(...) {
values <- list(...) values <- list(...)
type <- "{}" type <- "z.record(z.string(), z.any())"
type_fn <- "" type_fn <- ""
if (length(values)) { if (length(values)) {
types <- sapply(values, function(x) x$type) types <- sapply(values, function(x) x$input_type)
type_funs <- sapply(values, function(x) x$type_fn) type_funs <- sapply(values, function(x) x$return_type)
if (is.null(names(values))) stop("Expected named elements") if (is.null(names(values))) stop("Expected named elements")
type <- sprintf( type <- sprintf(
"{\n %s\n}", "z.object({\n %s\n})",
paste(names(values), types, sep = ": ", collapse = ",\n ") paste(names(values), types, sep = ": ", collapse = ",\n ")
) )
type_fn <- sprintf( type_fn <- sprintf(
@ -218,7 +247,7 @@ ts_dataframe <- function(...) {
object( object(
type, type,
sprintf("List<%s>", type), sprintf("R.dataframe(%s)", type_fn),
check = function(x) { check = function(x) {
if (!is.data.frame(x)) stop("Expected a data frame") if (!is.data.frame(x)) stop("Expected a data frame")
x x

View File

@ -5,23 +5,23 @@
\title{Typed object} \title{Typed object}
\usage{ \usage{
object( object(
zod_type = "any", input_type = "any",
r_type = "any", return_type = "any",
default = NULL, default = NULL,
check = function() stop("Not implemented"), check = function() stop("Not implemented"),
generic = FALSE generic = FALSE
) )
} }
\arguments{ \arguments{
\item{input_type}{The type of the object that Typescript expect to send to R.}
\item{return_type}{The type of the object that Typescript expects to recieve from R.}
\item{default}{The default value of the object.} \item{default}{The default value of the object.}
\item{check}{A function that checks the object and returns it if it is valid. This operates on the R side and is mostly for development and debugging purposes. It is up to the developer to ensure that all functions return the correct type of object always.} \item{check}{A function that checks the object and returns it if it is valid. This operates on the R side and is mostly for development and debugging purposes. It is up to the developer to ensure that all functions return the correct type of object always.}
\item{generic}{logical, if \code{TRUE} then the object is a generic type.} \item{generic}{logical, if \code{TRUE} then the object is a generic type.}
\item{type}{The type of the object that Typescript expect to send to R.}
\item{type_fn}{The type of the object that Typescript expects to recieve from R.}
} }
\description{ \description{
This is the base type for all typed objects. It is not meant to be used directly. This is the base type for all typed objects. It is not meant to be used directly.

View File

@ -9,7 +9,7 @@ ts_function(f, ..., result = NULL)
\arguments{ \arguments{
\item{f}{an R function} \item{f}{an R function}
\item{...}{argument definitions, OR function overloads} \item{...}{argument definitions (only required if f does not specify these in its formals)}
\item{result}{return type (ignored if overloads are provided)} \item{result}{return type (ignored if overloads are provided)}
} }

View File

@ -0,0 +1,107 @@
test_that("boolean type", {
x <- ts_logical()
expect_equal(x$check(TRUE), TRUE)
expect_error(x$check(1))
x1 <- ts_logical(1)
expect_equal(x1$check(TRUE), TRUE)
expect_error(x1$check(c(TRUE, FALSE)))
x2 <- ts_logical(3)
expect_equal(x2$check(c(TRUE, FALSE, TRUE)), c(TRUE, FALSE, TRUE))
expect_error(x2$check(FALSE))
})
test_that("integer type", {
x <- ts_integer()
expect_equal(x$check(1L), 1L)
expect_equal(x$check(1:10), 1:10)
expect_error(x$check("a"))
expect_error(x$check(1.5))
x1 <- ts_integer(1)
expect_equal(x1$check(1L), 1L)
expect_error(x1$check(c(1L, 2L)))
x2 <- ts_integer(3)
expect_equal(x2$check(c(1:3)), c(1:3))
expect_error(x2$check(1L))
})
test_that("numeric type", {
x <- ts_numeric()
expect_equal(x$check(1), 1)
expect_equal(x$check(1:10 + 0.5), 1:10 + 0.5)
expect_error(x$check("a"))
x1 <- ts_numeric(1)
expect_equal(x1$check(1), 1)
expect_error(x1$check(c(1, 2)))
x2 <- ts_numeric(3)
expect_equal(x2$check(c(1, 2, 3)), c(1, 2, 3))
expect_error(x2$check(1))
})
test_that("character type", {
x <- ts_character()
expect_equal(x$check("a"), "a")
expect_equal(x$check(c("a", "b")), c("a", "b"))
expect_error(x$check(1))
x1 <- ts_character(1)
expect_equal(x1$check("a"), "a")
expect_error(x1$check(c("a", "b")))
x2 <- ts_character(3)
expect_equal(x2$check(c("a", "b", "c")), c("a", "b", "c"))
expect_error(x2$check("a"))
})
test_that("factor type (no levels)", {
x <- ts_factor()
expect_equal(x$check(factor("a")), factor("a"))
expect_error(x$check("a"))
expect_error(x$check(1))
})
test_that("factor type (with levels)", {
x <- ts_factor(levels = c("a", "b"))
expect_equal(
x$check(factor("a", levels = c("a", "b"))),
factor("a", levels = c("a", "b"))
)
expect_error(x$check(factor("a", levels = c("a"))))
expect_error(x$check("a"))
expect_error(x$check(1))
})
test_that("list type - default", {
x <- ts_list()
expect_equal(x$check(list()), list())
expect_equal(x$check(list(a = 1, b = 2)), list(a = 1, b = 2))
expect_error(x$check(1))
})
test_that("list type - named", {
x <- ts_list(list(a = ts_integer(1), b = ts_numeric(1)))
expect_equal(x$check(list(a = 1L, b = 2)), list(a = 1L, b = 2))
expect_error(x$check(1))
expect_error(x$check(list()))
})
test_that("list type - unnamed", {
x <- ts_list(list(
ts_integer(1), ts_character(1),
ts_list(list(a = ts_integer(1)))
))
expect_equal(x$check(list(
1L, "a",
list(a = 1L)
)), list(
1L, "a",
list(a = 1L)
))
expect_error(x$check(1))
expect_error(x$check(list()))
})

View File

@ -0,0 +1,12 @@
# # optional, check arguments - useful for debugging/development
# check_args(match.call(), formals())
# test_that("function definitions", {
# add <- ts_fun(
# function(a = ts_numeric(1), b = ts_numeric(1)) a + b,
# result = ts_numeric(1)
# )
# expect_equal(add(1, 2), 3)
# expect_error(add("a", 2))
# })