@@ -7,23 +7,32 @@ 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 alias,... **not used** at the moment
10+ # ' @param alias alias that can be used in [model_dump()]
11+ # ' @param error_msg,... **not used** at the moment
12+ # ' @returns A model field.
1113# ' @export
12- model_field <- function (fn , default = NA , alias = NULL , ... ) {
13- l <- as.list(environment())
14- return (structure(c(l , list (... )), class = CLASS_RDANTIC_MODEL_FIELD ))
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))
17+ obj <- c(as.list(environment()), list (... ))
18+ base_class <- class(obj )
19+ structure(obj , class = c(base_class , CLASS_MODEL_FIELD ))
1520}
1621
1722# ---
1823# ' Create a model config object
19- # ' @param allow_extra Whether to allow extra fields without type check.
24+ # ' @param extra Whether to allow extra fields without type check.
2025# ' @param str_to_lower Convert all strings to lower case.
2126# ' @param ... **not used** at the moment
2227# ' @returns A model config object that can be used in [base_model()].
28+ # ' @example examples/api/model-config.R
2329# ' @export
24- model_config <- function (allow_extra = FALSE ,
30+ model_config <- function (extra = c( " ignore " , " allow " , " forbid " ) ,
2531 str_to_lower = FALSE , ... ) {
26- return (structure(c(as.list(environment()), list (... )), class = CLASS_MODEL_CONFIG ))
32+ obj <- c(as.list(environment()), list (... ))
33+ obj $ extra <- match.arg(extra )
34+ base_class <- class(obj )
35+ return (structure(obj , class = c(base_class , CLASS_MODEL_CONFIG )))
2736}
2837
2938# ---
@@ -38,42 +47,58 @@ model_config <- function(allow_extra = FALSE,
3847# ' that are executed before the type checks.
3948# ' @param .validators_after A named list of field validators
4049# ' that are executed after the type checks.
50+ # ' @param .strict_args_order If set to `TRUE`, the `.x` parameter
51+ # ' of the returned model factory function will be the last function argument.
52+ # ' This is useful if you want to pass the arguments unnamed.
4153# ' @returns A model factory function.
4254# ' @example examples/api/base-model.R
55+ # ' @importFrom utils modifyList
4356# ' @export
4457base_model <- function (fields = list (), ... ,
4558 .model_config = model_config(),
4659 .model_pre_init = NULL ,
4760 .model_post_init = NULL ,
4861 .validators_before = list (),
49- .validators_after = list ()) {
50- fields <- utils :: modifyList(fields , list (... ), keep.null = TRUE )
51- fields <- purrr :: map(fields , ~ {
52- if (inherits(.x , c(" function" , " formula" ))) {
53- return (model_field(fn = .x ))
54- }
55-
56- if (inherits(.x , CLASS_RDANTIC_MODEL )) {
62+ .validators_after = list (),
63+ .strict_args_order = FALSE ) {
64+ fields <- modifyList(fields , list (... ), keep.null = TRUE )
65+ fields <- Map(function (.x ) {
66+ if (inherits(.x , CLASS_MODEL_FUNCTION )) {
5767 model_fn <- .x
5868 fn <- function (x ) {
5969 is.list(model_validate(x , model_fn ))
6070 }
6171 return (model_field(fn = fn ))
6272 }
6373
74+ 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 )
80+ }
81+
6482 return (.x )
65- })
83+ }, fields )
6684
67- model_args <- purrr :: map(fields , ~ .x $ default )
85+ # model_args <- purrr::map(fields, ~ .x$default)
86+ model_args <- Map(function (x ) x $ default , fields )
87+ fn_args <- c(alist(.x = NULL ), model_args , alist(... = ))
88+ if (.strict_args_order ) {
89+ fn_args <- c(model_args , alist(... = , .x = NULL ))
90+ }
6891
92+ # ---
6993 # Create model factory function
70- model_fn <- rlang :: new_function(c( model_args , alist( ... = , .x = NULL )) , quote({
94+ model_fn <- rlang :: new_function(fn_args , quote({
7195 if (is_not_null(.x )) {
7296 obj <- .x
7397 } else {
7498 obj <- c(as.list(environment()), list (... ))
7599 }
76100
101+ caller_fn_name <- as.character(match.call()[[1 ]])
77102 errors <- list ()
78103
79104 obj <- validate_fields(obj , .validators_before )
@@ -83,23 +108,23 @@ base_model <- function(fields = list(), ...,
83108 }
84109
85110 for (name in names(fields )) {
86- check_type_fn <- rlang :: as_function(fields [[name ]]$ fn )
111+ type_check_fn <- rlang :: as_function(fields [[name ]]$ fn )
87112 obj_value <- obj [[name ]]
88- if (isFALSE(check_type_fn(obj_value ))) {
113+
114+ if (! all(type_check_fn(obj_value ))) {
89115 errors [[name ]] <- list (
90116 name = name ,
91117 value = obj_value ,
92- type = typeof(obj_value ),
93- len = length(obj_value ),
94- type_check_failed = check_type_fn
118+ type_check_fn = type_check_fn
95119 )
96120 }
97121 }
98122
99123 obj <- validate_fields(obj , .validators_after )
100124
101- if (isTRUE(.model_config $ str_to_lower )) {
102- obj <- purrr :: map_depth(obj , - 1 , str_to_lower )
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 )
103128 }
104129
105130 if (length(errors ) > 0 ) {
@@ -111,8 +136,15 @@ base_model <- function(fields = list(), ...,
111136 return (invisible (obj ))
112137 }
113138
114- if (isFALSE(.model_config $ allow_extra )) {
115- obj <- purrr :: keep_at(obj , names(fields ))
139+ if (.model_config $ extra == " ignore" ) {
140+ obj <- obj [names(fields )]
141+ }
142+
143+ if (.model_config $ extra == " forbid" ) {
144+ extra_fields <- ! names(obj ) %in% names(fields )
145+ if (any(extra_fields )) {
146+ stop(" Forbidden field(s): " , paste(names(obj )[extra_fields ], collapse = " , " ))
147+ }
116148 }
117149
118150 if (is_not_null(.model_post_init )) {
@@ -123,14 +155,14 @@ base_model <- function(fields = list(), ...,
123155 return (obj )
124156 }
125157
126- return (structure(obj , fields = fields , class = c(class(obj ), CLASS_RDANTIC )))
158+ return (structure(obj , fields = fields , class = c(class(obj ), CLASS_MODEL , caller_fn_name )))
127159 }))
128160
129161 return (
130162 structure(
131163 model_fn ,
132164 fields = fields ,
133- class = CLASS_RDANTIC_MODEL
165+ class = CLASS_MODEL_FUNCTION
134166 )
135167 )
136168}
@@ -157,18 +189,18 @@ check_args <- function(...) {
157189 if (length(fields ) == 0 ) {
158190 fn <- rlang :: caller_fn()
159191 fmls <- rlang :: fn_fmls(fn )
160- fields <- purrr :: map( as.list(fmls ), eval )
192+ fields <- Map( eval , as.list(fmls ))
161193 }
162194
163- e <- rlang :: caller_env()
164- for (name in names(e )) {
165- value <- e [[name ]]
166- if (inherits(value , CLASS_RDANTIC_MODEL_FIELD )) {
167- e [[name ]] <- value $ default
195+ func_env <- rlang :: caller_env()
196+ for (name in names(func_env )) {
197+ value <- func_env [[name ]]
198+ if (inherits(value , CLASS_MODEL_FIELD )) {
199+ func_env [[name ]] <- value $ default
168200 }
169201 }
170202
171- base_model(fields )(.x = e )
203+ base_model(fields )(.x = func_env )
172204}
173205
174206# ---
@@ -182,7 +214,7 @@ model_validate <- function(obj, model_fn) {
182214
183215# ---
184216# ' @export
185- print.rdantic <- function (x , ... ) {
217+ print.typewriter <- function (x , ... ) {
186218 print(x [seq_along(x )])
187219 return (invisible (x ))
188220}
@@ -199,7 +231,7 @@ check_assignment <- function(x, name, value) {
199231
200232# ---
201233# ' @export
202- `$<-.rdantic ` <- function (x , name , value ) {
234+ `$<-.typewriter ` <- function (x , name , value ) {
203235 if (isFALSE(name %in% names(x ))) {
204236 return (x )
205237 }
@@ -210,7 +242,7 @@ check_assignment <- function(x, name, value) {
210242
211243# ---
212244# ' @export
213- `[[<-.rdantic ` <- function (x , name , value ) {
245+ `[[<-.typewriter ` <- function (x , name , value ) {
214246 if (isFALSE(name %in% names(x ))) {
215247 return (x )
216248 }
@@ -226,36 +258,34 @@ check_assignment <- function(x, name, value) {
226258# }
227259
228260# ---
229- # TODO: Deprecated?, use single functions as 'model_exclude_na'
261+ # ' Convert model to base list
262+ # ' @param obj A typewriter model object
263+ # ' @param by_alias Use aliases for names.
264+ # ' @param exclude_na Whether to exclude `NA` values.
265+ # ' @param exclude_null Whether to exclude `NULL` values.
266+ # ' @param ... **not used** at the moment.
267+ # ' @returns base list object
268+ # ' @export
230269model_dump <- function (obj ,
231- exclude = NULL ,
232- include = NULL ,
233- exclude_na = FALSE ,
270+ by_alias = FALSE ,
234271 exclude_null = FALSE ,
235- by_alias = FALSE ) {
236- fields <- model_fields(obj )
237-
238- if (is_not_null(exclude )) {
239- obj <- purrr :: discard_at(obj , exclude )
240- }
241-
242- if (is_not_null(include )) {
243- obj <- purrr :: keep_at(obj , include )
272+ exclude_na = FALSE ,
273+ ... ) {
274+ if (isTRUE(by_alias )) {
275+ # return(dump_by_alias(obj))
276+ obj <- dump_by_alias(obj )
244277 }
245278
246- if (isTRUE( exclude_na ) ) {
279+ if (exclude_na ) {
247280 obj <- discard_this(obj , rlang :: is_na )
248281 }
249282
250- if (isTRUE(exclude_null )) {
251- obj <- discard_this(obj , rlang :: is_null )
252- }
253-
254- if (isTRUE(by_alias )) {
255- obj <- dump_by_alias(obj , fields )
283+ if (exclude_null ) {
284+ obj <- discard_this(obj , is.null )
256285 }
257286
258- return (obj )
287+ return (unclass(obj ))
288+ # return(model_to_list(obj)) # dumps NULLs!
259289}
260290
261291# ---
0 commit comments