@@ -3,37 +3,55 @@ use serde_json::Value as JsonValue;
33
44use crate :: webserver:: database:: DbItem ;
55
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) ,
1510 }
1611}
1712
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+ } )
2539 }
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 ;
3049 }
31- } else {
32- Box :: new ( std:: iter:: once ( DbItem :: Row ( row) ) )
3350 }
3451}
3552
3653/// if row.component == 'dynamic', return Some(row.properties), otherwise return None
54+ #[ inline]
3755fn extract_dynamic_properties ( data : & mut JsonValue ) -> Option < JsonValue > {
3856 let component = data. get ( "component" ) . and_then ( |v| v. as_str ( ) ) ;
3957 if component == Some ( "dynamic" ) {
@@ -44,9 +62,22 @@ fn extract_dynamic_properties(data: &mut JsonValue) -> Option<JsonValue> {
4462 }
4563}
4664
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 (
4879 mut properties_obj : JsonValue ,
49- ) -> anyhow:: Result < Box < dyn Iterator < Item = JsonValue > > > {
80+ ) -> anyhow:: Result < Vec < JsonValue > > {
5081 if let JsonValue :: String ( s) = properties_obj {
5182 properties_obj = serde_json:: from_str :: < JsonValue > ( & s) . with_context ( || {
5283 format ! (
@@ -56,10 +87,80 @@ fn dynamic_properties_to_iter(
5687 } ) ?;
5788 }
5889 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) ,
6192 other => anyhow:: bail!(
6293 "Dynamic component expected properties of type array or object, got {other} instead."
6394 ) ,
6495 }
6596}
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