Skip to content

Commit d0005a6

Browse files
committed
doggos
1 parent 07533fc commit d0005a6

9 files changed

Lines changed: 254 additions & 0 deletions

File tree

man/figures/logo.png

20.7 KB
Loading
33.2 KB
Loading

pkgdown/favicon/favicon-96x96.png

11 KB
Loading

pkgdown/favicon/favicon.ico

14.7 KB
Binary file not shown.

pkgdown/favicon/favicon.svg

Lines changed: 1 addition & 0 deletions
Loading

pkgdown/favicon/site.webmanifest

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "",
3+
"short_name": "",
4+
"icons": [
5+
{
6+
"src": "/web-app-manifest-192x192.png",
7+
"sizes": "192x192",
8+
"type": "image/png",
9+
"purpose": "maskable"
10+
},
11+
{
12+
"src": "/web-app-manifest-512x512.png",
13+
"sizes": "512x512",
14+
"type": "image/png",
15+
"purpose": "maskable"
16+
}
17+
],
18+
"theme_color": "#ffffff",
19+
"background_color": "#ffffff",
20+
"display": "standalone"
21+
}
36.8 KB
Loading
210 KB
Loading

vignettes/custom-rules.Rmd

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
title: "Adding Custom Rules"
3+
output: rmarkdown::html_vignette
4+
description: >
5+
Add custom validation rules to validate schemas and data.
6+
vignette: >
7+
%\VignetteIndexEntry{Adding Custom Rules}
8+
%\VignetteEngine{knitr::rmarkdown}
9+
%\VignetteEncoding{UTF-8}
10+
---
11+
12+
```{r, include = FALSE}
13+
knitr::opts_chunk$set(
14+
collapse = TRUE,
15+
comment = "#>"
16+
)
17+
```
18+
19+
```{r setup}
20+
# library(fluffy)
21+
devtools::load_all()
22+
```
23+
24+
Custom rules can be used to validate schemas and data in ways that the [builtin rules](validation-rules.html) don't cover. Rules are stored in `Registry` objects and thus custom rules can be added to any instantiated fluffy class objects: `Registry`, `Schema` or `Validator`.
25+
26+
Rules added to fluffy objects can be used to flexibly validate and transform data, but they must follow the same structure as the builtin rules. This structure is what allows the `Schema` and `Validator` to apply them correctly. The following sections cover the structure of custom rules and how to add them to fluffy objects.
27+
28+
### Custom rules
29+
30+
To add custom rules, `add_rule` (and variants) are used. Each new rule requires a unique name, a transformation/validation function for the data, a validation function for the schema value, and a rule type.
31+
32+
```{r add_rule definition, eval=FALSE}
33+
add_rule(
34+
obj,
35+
name,
36+
validator_fn,
37+
schema_fn = NULL,
38+
rule_type = c("validate", "control", "transform")
39+
)
40+
```
41+
42+
See later sections for adding new type/coerce rules and cross rules.
43+
44+
#### Keywords
45+
46+
In fluffy rules, `.self`, `.schema` and `.data` are reserved keywords that refer to the fluffy object the rule is being applied to and the schema/data being validated, respectively.
47+
48+
`.self` is the fluffy object the rule is being applied to, so for schema validation it is the `Schema` object and for data transformation/validation it is the `Validator` object. This allows the rule functions to access properties of the relevant fluffy object, such as the `Registry` of rules.
49+
50+
`.schema` is used in the schema validation function to refer to the full schema being validated, and `.data` is used in the data transformation/validation function to refer to the full data being validated. This allows the rule functions to access other fields in the schema/data when operating on a particular field.
51+
52+
#### Rule function arguments
53+
54+
Schema validation functions are passed the schema field as a positional argument, and then `.schema` and `.self` as named arguments. Therefore, schema validation functions can be defined in these ways:
55+
56+
```{r schema validation function arg examples, echo=FALSE, results='asis'}
57+
knitr::kable(
58+
data.frame(
59+
" " = "`function(field, ...)`",
60+
" " = "`function(field, .schema ...)` or `function(field, .self ...)`",
61+
" " = "`function(field, .schema, .self)`",
62+
check.names = FALSE,
63+
fix.empty.names = FALSE
64+
)
65+
)
66+
```
67+
68+
Data transformation/validation functions are passed the data field and the schema field as positional arguments, respectively, and then `.data` and `.self` as named arguments. Therefore, data transformation/validation functions can be defined in these ways:
69+
70+
```{r data validation function arg examples, echo=FALSE, results='asis'}
71+
knitr::kable(
72+
data.frame(
73+
" " = "`function(field, schema_field, ...)`",
74+
" " = "`function(field, schema_field, .data ...)` or `function(field, schema_field, .self ...)`",
75+
" " = "`function(field, schema_field, .data, .self)`",
76+
check.names = FALSE,
77+
fix.empty.names = FALSE
78+
)
79+
)
80+
```
81+
82+
#### Schema validation function
83+
84+
The schema validation function checks the validity of the schema field for the rule. It should return `NULL` if the schema field is valid, and a character string (to be used as an error message) if it is invalid.
85+
86+
The following would be an example of a schema validation function that checks that the schema field is a length 1 character:
87+
88+
```{r schema validation function example, eval=FALSE}
89+
schema_validation_fn <- function(field, ...) {
90+
if (!is.character(field) || length(field) != 1L) {
91+
"Must be a length 1 character."
92+
}
93+
}
94+
```
95+
96+
Schema validation is optional. If a function is not provided, the rule will be added without any schema validation, and any schema value will be accepted for the rule. In this case, the schema validation function would simply be an empty function (this is the same as the implementation of the builtin `default` rule):
97+
98+
```{r empty schema validation function example, eval=FALSE}
99+
allow_any_schema_fn <- function(field, ...) {}
100+
```
101+
102+
#### Data transformation/validation function
103+
104+
The data transformation/validation function applies the rule to the data. Unlike the schema validation function, a named list must be returned, with the following named element(s) determining the behaviour:
105+
106+
```{r list return example, eval=FALSE}
107+
return(list(error = ..., data = ..., continue = ...))
108+
```
109+
* `error`: character string of the error message and if returned, signals that the data is invalid. If not returned or `NULL`, the data is considered valid for that rule.
110+
* `data`: the transformed data for the field. If not returned or `NULL`, the original data remains. If both `error` and `data` are returned, the data will be transformed but still be considered invalid.
111+
* `continue`: a boolean to indicate whether to continue validating the rest of the schema rules in the node. This is used in the builtin in control rules that determine whether validation should proceed or not, but can be used in any rule. If not returned or `NULL`, it defaults to `TRUE`.
112+
113+
The following would be an example of a data transformation/validation function that checks that the data field is a length 1 character, pasting the schema field onto it if so, and erroring if not:
114+
115+
```{r data validation function example, eval=FALSE}
116+
data_validation_fn <- function(data_field, schema_field, ...) {
117+
if (!is.character(data_field) || length(data_field) != 1L) {
118+
list(error = "Data must be a length 1 character.")
119+
} else {
120+
list(data = paste0(data_field, schema_field))
121+
}
122+
}
123+
```
124+
125+
An example of a builtin rule that alters control flow with `continue` is the `required` rule. See the following example where the other rules in the schema node do not error despite there being no data for the node, as `required` returns `continue = FALSE` and thus stops validation of the rest of the schema rules for that node:
126+
127+
```{r data validation function example 2}
128+
Validator(
129+
data = list(a = 1),
130+
schema = list(
131+
b = list(
132+
required = FALSE,
133+
type = "character",
134+
min_length = 5L
135+
)
136+
)
137+
)@valid
138+
```
139+
140+
#### Rule type
141+
142+
The rule type determines when the rule is applied when the `Validator` is run. Four separate passes are undertaken during data validation, with rules being applied depending on their specified type in the associated `Registry`:
143+
144+
```{r rule categories}
145+
r <- Registry()
146+
r@control_rules # first pass
147+
r@transform_rules # second pass
148+
r@validate_rules # third pass
149+
r@finalize_rules # fourth pass
150+
```
151+
152+
The `rule_type` given must match one of these categories, and determines if the custom rule is applied in the first, second, third, or fourth pass. Custom rules do not need to strictly follow these category definitions, but it is recommended.
153+
154+
The order in which rules within categories are run is determined by the individual order of the associated `Registry` property, which can be edited.
155+
156+
The 'finalize' pass behaves slightly differently to the others, in that rules in this group are only applied if there are no errors from the previous passes in that schema node.
157+
158+
### Custom type/coerce rules
159+
160+
161+
162+
### Custom cross rules
163+
164+
165+
166+
167+
168+
169+
170+
### Examples
171+
172+
Example validate rule for checking a specific attribute matches the schema.
173+
174+
```{r fluffy extending validate example, error=TRUE}
175+
mySchema <- Schema(list(check_my_attr = 1L))
176+
mySchema@errors
177+
178+
mySchema <- add_rule(
179+
obj = mySchema,
180+
name = "check_my_attr",
181+
validator_fn = function(data_field, schema_field, ...) {
182+
if (attr(data_field, "my_attr") != schema_field) {
183+
list(error = "Data doesn't match schema 'my_attr'.")
184+
}
185+
},
186+
schema_fn = function(schema_field, ...) {
187+
if (!is.character(schema_field) || length(schema_field) != 1L) {
188+
"Must be length 1 character"
189+
}
190+
},
191+
rule_type = "validate"
192+
)
193+
194+
mySchema@errors
195+
mySchema@schema$check_my_attr <- "Hi"
196+
197+
Validator(structure(1L, my_attr = "Hi"), mySchema)@valid
198+
Validator(structure(1L, my_attr = 1L), mySchema, error = TRUE)
199+
```
200+
201+
Example transform rule which doubles the data value if it is 5.
202+
203+
```{r fluffy extending transform example, error=TRUE}
204+
s <- Schema(list(double_if_five_else_error = TRUE))
205+
s@valid
206+
207+
s <- add_rule(
208+
s,
209+
name = "double_if_five_else_error",
210+
validator_fn = function(field, schema_field, ...) {
211+
if (schema_field) {
212+
if (field != 5) {
213+
list(error = "Does not equal 5.")
214+
} else {
215+
list(data = field * 2)
216+
}
217+
}
218+
},
219+
schema_fn = function(schema_field, ...) {
220+
if (!isTRUE(schema_field) && !isFALSE(schema_field)) {
221+
"Must be a boolean."
222+
}
223+
},
224+
rule_type = "transform"
225+
)
226+
s@valid
227+
228+
v <- Validator(data = 5, schema = s)
229+
v@valid
230+
v@data
231+
Validator(data = 1, schema = s, error = TRUE)
232+
```

0 commit comments

Comments
 (0)