@@ -7,28 +7,26 @@ model_fields <- function(model_fn) {
77# ' Create a model field
88# ' @param fn A type check function.
99# ' @param default A default value for the field.
10+ # ' @param optional Whether the field is optional.
1011# ' @param alias alias that can be used in [model_dump()]
11- # ' @param error_msg,... **not used** at the moment
12+ # ' @param error_msg A custom error message.
13+ # ' @param ... **not used** at the moment
1214# ' @returns A model field.
1315# ' @export
14- model_field <- function (fn , default = NA , alias = NULL , error_msg = NULL , ... ) {
15- # l <- as.list(environment())
16- # return(structure(c(l, list(...)), class = CLASS_MODEL_FIELD))
16+ model_field <- function (fn , default = NA , optional = FALSE , alias = NULL , error_msg = NULL , ... ) {
1717 obj <- c(as.list(environment()), list (... ))
1818 base_class <- class(obj )
1919 structure(obj , class = c(base_class , CLASS_MODEL_FIELD ))
2020}
2121
2222# ---
23- # ' Create a model config object
24- # ' @param extra Whether to allow extra fields without type check.
25- # ' @param str_to_lower Convert all strings to lower case.
26- # ' @param ... **not used** at the moment
27- # ' @returns A model config object that can be used in [base_model()].
28- # ' @example examples/api/model-config.R
29- # ' @export
30- model_config <- function (extra = c(" ignore" , " allow" , " forbid" ),
31- str_to_lower = FALSE , ... ) {
23+ # #' Create a model config object
24+ # #' DEPRECATED
25+ # #' @param extra Whether to allow extra fields without type check.
26+ # #' @param ... **not used** at the moment
27+ # #' @returns A model config object that can be used in [base_model()].
28+ # #' @example examples/api/model-config.R
29+ model_config <- function (extra = c(" ignore" , " allow" , " forbid" ), ... ) {
3230 obj <- c(as.list(environment()), list (... ))
3331 obj $ extra <- match.arg(extra )
3432 base_class <- class(obj )
@@ -40,7 +38,6 @@ model_config <- function(extra = c("ignore", "allow", "forbid"),
4038# ' @param fields A named list of field definitions.
4139# ' @param ... Named arguments of field definitions.
4240# ' Normally either `fields` or `...` is supplied.
43- # ' @param .model_config See [model_config()].
4441# ' @param .model_pre_init A callback function that is executed before the type checks.
4542# ' @param .model_post_init A callback function that is executed after the type checks.
4643# ' @param .validators_before A named list of field validators
@@ -50,17 +47,21 @@ model_config <- function(extra = c("ignore", "allow", "forbid"),
5047# ' @param .strict_args_order If set to `TRUE`, the `.x` parameter
5148# ' of the returned model factory function will be the last function argument.
5249# ' This is useful if you want to pass the arguments unnamed.
50+ # ' @param .allow_na Whether to allow `NA` values for all fields.
51+ # ' @param .extra Whether to allow extra fields without type check.
5352# ' @returns A model factory function.
5453# ' @example examples/api/base-model.R
5554# ' @importFrom utils modifyList
5655# ' @export
5756base_model <- function (fields = list (), ... ,
58- .model_config = model_config(),
5957 .model_pre_init = NULL ,
6058 .model_post_init = NULL ,
6159 .validators_before = list (),
6260 .validators_after = list (),
63- .strict_args_order = FALSE ) {
61+ .strict_args_order = FALSE ,
62+ .allow_na = FALSE ,
63+ .extra = c(" ignore" , " allow" , " forbid" )) {
64+ .extra <- match.arg(.extra )
6465 fields <- modifyList(fields , list (... ), keep.null = TRUE )
6566 fields <- Map(function (.x ) {
6667 if (inherits(.x , CLASS_MODEL_FUNCTION )) {
@@ -72,17 +73,12 @@ base_model <- function(fields = list(), ...,
7273 }
7374
7475 if (! inherits(.x , CLASS_MODEL_FIELD )) {
75- .x <- model_field(fn = .x )
76- }
77-
78- if (is.character(.x $ fn )) {
79- .x $ fn <- type_check_fn_from_str(.x $ fn )
76+ .x <- as_model_field(.x )
8077 }
8178
8279 return (.x )
8380 }, fields )
8481
85- # model_args <- purrr::map(fields, ~ .x$default)
8682 model_args <- Map(function (x ) x $ default , fields )
8783 fn_args <- c(alist(.x = NULL ), model_args , alist(... = ))
8884 if (.strict_args_order ) {
@@ -108,25 +104,25 @@ base_model <- function(fields = list(), ...,
108104 }
109105
110106 for (name in names(fields )) {
111- type_check_fn <- rlang :: as_function(fields [[name ]]$ fn )
112- obj_value <- obj [[name ]]
107+ field <- fields [[name ]]
108+ check_type <- rlang :: as_function(field $ fn )
109+ value <- obj [[name ]]
110+ if (.allow_na | field $ optional ) {
111+ if (length(value ) == 1L && is.na(value )) next ()
112+ }
113113
114- if (! all(type_check_fn( obj_value ))) {
114+ if (! all(check_type( value ))) {
115115 errors [[name ]] <- list (
116116 name = name ,
117- value = obj_value ,
118- type_check_fn = type_check_fn
117+ value = value ,
118+ type_check_fn = check_type ,
119+ msg = field $ error_msg
119120 )
120121 }
121122 }
122123
123124 obj <- validate_fields(obj , .validators_after )
124125
125- if (.model_config $ str_to_lower ) {
126- # obj <- purrr::map_depth(obj, -1, str_to_lower)
127- obj <- map_depth_base(obj , - 1 , str_to_lower )
128- }
129-
130126 if (length(errors ) > 0 ) {
131127 msg <- paste0(map_type_check_errors(errors ), collapse = " \n " )
132128 stop(" Type check(s) failed\n " , msg , domain = NA )
@@ -136,11 +132,11 @@ base_model <- function(fields = list(), ...,
136132 return (invisible (obj ))
137133 }
138134
139- if (.model_config $ extra == " ignore" ) {
135+ if (.extra == " ignore" ) {
140136 obj <- obj [names(fields )]
141137 }
142138
143- if (.model_config $ extra == " forbid" ) {
139+ if (.extra == " forbid" ) {
144140 extra_fields <- ! names(obj ) %in% names(fields )
145141 if (any(extra_fields )) {
146142 stop(" Forbidden field(s): " , paste(names(obj )[extra_fields ], collapse = " , " ))
@@ -221,11 +217,11 @@ print.typewriter <- function(x, ...) {
221217
222218# ---
223219check_assignment <- function (x , name , value ) {
224- fields <- model_fields(x )
225- type_check_fn <- rlang :: as_function(fields [[ name ]] $ fn )
226- fn_text <- get_fn_text(type_check_fn )
227- if (isFALSE(type_check_fn( value ) )) {
228- stop(paste0(" Type check failed.\n " , fn_text ))
220+ field <- model_fields(x )[[ name ]]
221+ check_type <- rlang :: as_function(field $ fn )
222+ error_msg <- ifelse(is_not_null( field $ error_msg ), glue :: glue( field $ error_msg ), get_fn_text(check_type ) )
223+ if (! check_type( value )) {
224+ stop(paste0(" Type check failed.\n " , error_msg ))
229225 }
230226}
231227
0 commit comments