Skip to content

Commit 6313164

Browse files
committed
Refactor dynamic component parsing
1 parent 30d4abb commit 6313164

File tree

1 file changed

+127
-26
lines changed

1 file changed

+127
-26
lines changed

src/dynamic_component.rs

Lines changed: 127 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,55 @@ use serde_json::Value as JsonValue;
33

44
use 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]
3755
fn 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

Comments
 (0)