@@ -3,37 +3,55 @@ use serde_json::Value as JsonValue;
3
3
4
4
use crate :: webserver:: database:: DbItem ;
5
5
6
- const MAX_RECURSION_DEPTH : u8 = 127 ;
7
-
8
- /// the raw query results can include (potentially nested) rows with a 'component' column that has the value 'dynamic'.
9
- /// in that case we need to parse the JSON in the 'properties' column, and emit a row for each value in the resulting json array.
10
- #[ must_use] pub fn parse_dynamic_rows ( db_item : DbItem ) -> Box < dyn Iterator < Item = DbItem > > {
11
- if let DbItem :: Row ( row) = db_item {
12
- parse_dynamic_rows_json ( row, 0 )
13
- } else {
14
- Box :: new ( std:: iter:: once ( db_item) )
6
+ pub fn parse_dynamic_rows ( row : DbItem ) -> impl Iterator < Item = DbItem > {
7
+ DynamicComponentIterator {
8
+ stack : vec ! [ ] ,
9
+ db_item : Some ( row) ,
15
10
}
16
11
}
17
12
18
- fn parse_dynamic_rows_json ( mut row : JsonValue , depth : u8 ) -> Box < dyn Iterator < Item = DbItem > > {
19
- if depth >= MAX_RECURSION_DEPTH {
20
- return Box :: new ( std:: iter:: once ( DbItem :: Error ( anyhow:: anyhow!(
21
- "Too many nested dynamic components: \n \
22
- The 'dynamic' component can be used to render another 'dynamic' component, \
23
- but the recursion cannot exceed {depth} layers."
24
- ) ) ) ) ;
13
+ struct DynamicComponentIterator {
14
+ stack : Vec < anyhow:: Result < JsonValue > > ,
15
+ db_item : Option < DbItem > ,
16
+ }
17
+
18
+ impl Iterator for DynamicComponentIterator {
19
+ type Item = DbItem ;
20
+
21
+ fn next ( & mut self ) -> Option < Self :: Item > {
22
+ if let Some ( db_item) = self . db_item . take ( ) {
23
+ if let DbItem :: Row ( mut row) = db_item {
24
+ if let Some ( properties) = extract_dynamic_properties ( & mut row) {
25
+ self . stack = dynamic_properties_to_vec ( properties) ;
26
+ } else {
27
+ // Most common case: just a regular row. We allocated nothing.
28
+ return Some ( DbItem :: Row ( row) ) ;
29
+ }
30
+ } else {
31
+ return Some ( db_item) ;
32
+ }
33
+ }
34
+ expand_dynamic_stack ( & mut self . stack ) ;
35
+ self . stack . pop ( ) . map ( |result| match result {
36
+ Ok ( row) => DbItem :: Row ( row) ,
37
+ Err ( err) => DbItem :: Error ( err) ,
38
+ } )
25
39
}
26
- if let Some ( properties) = extract_dynamic_properties ( & mut row) {
27
- match dynamic_properties_to_iter ( properties) {
28
- Ok ( iter) => Box :: new ( iter. flat_map ( move |v| parse_dynamic_rows_json ( v, depth + 1 ) ) ) ,
29
- Err ( e) => Box :: new ( std:: iter:: once ( DbItem :: Error ( e) ) ) ,
40
+ }
41
+
42
+ fn expand_dynamic_stack ( stack : & mut Vec < anyhow:: Result < JsonValue > > ) {
43
+ while let Some ( Ok ( mut next) ) = stack. pop ( ) {
44
+ if let Some ( properties) = extract_dynamic_properties ( & mut next) {
45
+ stack. extend ( dynamic_properties_to_vec ( properties) ) ;
46
+ } else {
47
+ stack. push ( Ok ( next) ) ;
48
+ return ;
30
49
}
31
- } else {
32
- Box :: new ( std:: iter:: once ( DbItem :: Row ( row) ) )
33
50
}
34
51
}
35
52
36
53
/// if row.component == 'dynamic', return Some(row.properties), otherwise return None
54
+ #[ inline]
37
55
fn extract_dynamic_properties ( data : & mut JsonValue ) -> Option < JsonValue > {
38
56
let component = data. get ( "component" ) . and_then ( |v| v. as_str ( ) ) ;
39
57
if component == Some ( "dynamic" ) {
@@ -44,9 +62,22 @@ fn extract_dynamic_properties(data: &mut JsonValue) -> Option<JsonValue> {
44
62
}
45
63
}
46
64
47
- fn dynamic_properties_to_iter (
65
+ /// reverse the order of the vec returned by `dynamic_properties_to_result_vec`,
66
+ /// and wrap each element in a Result
67
+ fn dynamic_properties_to_vec ( properties_obj : JsonValue ) -> Vec < anyhow:: Result < JsonValue > > {
68
+ dynamic_properties_to_result_vec ( properties_obj) . map_or_else (
69
+ |err| vec ! [ Err ( err) ] ,
70
+ |vec| vec. into_iter ( ) . rev ( ) . map ( Ok ) . collect :: < Vec < _ > > ( ) ,
71
+ )
72
+ }
73
+
74
+ /// if properties is a string, parse it as JSON and return a vec with the parsed value
75
+ /// if properties is an array, return it as is
76
+ /// if properties is an object, return it as a single element vec
77
+ /// otherwise, return an error
78
+ fn dynamic_properties_to_result_vec (
48
79
mut properties_obj : JsonValue ,
49
- ) -> anyhow:: Result < Box < dyn Iterator < Item = JsonValue > > > {
80
+ ) -> anyhow:: Result < Vec < JsonValue > > {
50
81
if let JsonValue :: String ( s) = properties_obj {
51
82
properties_obj = serde_json:: from_str :: < JsonValue > ( & s) . with_context ( || {
52
83
format ! (
@@ -56,10 +87,80 @@ fn dynamic_properties_to_iter(
56
87
} ) ?;
57
88
}
58
89
match properties_obj {
59
- obj @ JsonValue :: Object ( _) => Ok ( Box :: new ( std :: iter :: once ( obj) ) ) ,
60
- JsonValue :: Array ( values) => Ok ( Box :: new ( values. into_iter ( ) ) ) ,
90
+ obj @ JsonValue :: Object ( _) => Ok ( vec ! [ obj] ) ,
91
+ JsonValue :: Array ( values) => Ok ( values) ,
61
92
other => anyhow:: bail!(
62
93
"Dynamic component expected properties of type array or object, got {other} instead."
63
94
) ,
64
95
}
65
96
}
97
+
98
+ #[ cfg( test) ]
99
+ mod tests {
100
+ use super :: * ;
101
+
102
+ #[ test]
103
+ fn test_dynamic_properties_to_result_vec ( ) {
104
+ let mut properties = JsonValue :: String ( r#"{"a": 1}"# . to_string ( ) ) ;
105
+ assert_eq ! (
106
+ dynamic_properties_to_result_vec( properties. clone( ) ) . unwrap( ) ,
107
+ vec![ JsonValue :: Object (
108
+ serde_json:: from_str( r#"{"a": 1}"# ) . unwrap( )
109
+ ) ]
110
+ ) ;
111
+
112
+ properties = JsonValue :: Array ( vec ! [ JsonValue :: String ( r#"{"a": 1}"# . to_string( ) ) ] ) ;
113
+ assert_eq ! (
114
+ dynamic_properties_to_result_vec( properties. clone( ) ) . unwrap( ) ,
115
+ vec![ JsonValue :: String ( r#"{"a": 1}"# . to_string( ) ) ]
116
+ ) ;
117
+
118
+ properties = JsonValue :: Object ( serde_json:: from_str ( r#"{"a": 1}"# ) . unwrap ( ) ) ;
119
+ assert_eq ! (
120
+ dynamic_properties_to_result_vec( properties. clone( ) ) . unwrap( ) ,
121
+ vec![ JsonValue :: Object (
122
+ serde_json:: from_str( r#"{"a": 1}"# ) . unwrap( )
123
+ ) ]
124
+ ) ;
125
+
126
+ properties = JsonValue :: Null ;
127
+ assert ! ( dynamic_properties_to_result_vec( properties) . is_err( ) ) ;
128
+ }
129
+
130
+ #[ test]
131
+ fn test_dynamic_properties_to_vec ( ) {
132
+ let properties = JsonValue :: String ( r#"{"a": 1}"# . to_string ( ) ) ;
133
+ assert_eq ! (
134
+ dynamic_properties_to_vec( properties. clone( ) )
135
+ . first( )
136
+ . unwrap( )
137
+ . as_ref( )
138
+ . unwrap( ) ,
139
+ & serde_json:: json!( { "a" : 1 } )
140
+ ) ;
141
+ }
142
+
143
+ #[ test]
144
+ fn test_parse_dynamic_rows ( ) {
145
+ let row = DbItem :: Row ( serde_json:: json!( {
146
+ "component" : "dynamic" ,
147
+ "properties" : [
148
+ { "a" : 1 } ,
149
+ { "component" : "dynamic" , "properties" : { "nested" : 2 } } ,
150
+ ]
151
+ } ) ) ;
152
+ let iter = parse_dynamic_rows ( row)
153
+ . map ( |item| match item {
154
+ DbItem :: Row ( row) => row,
155
+ x => panic ! ( "Expected a row, got {x:?}" ) ,
156
+ } )
157
+ . collect :: < Vec < _ > > ( ) ;
158
+ assert_eq ! (
159
+ iter,
160
+ vec![
161
+ serde_json:: json!( { "a" : 1 } ) ,
162
+ serde_json:: json!( { "nested" : 2 } ) ,
163
+ ]
164
+ ) ;
165
+ }
166
+ }
0 commit comments