Skip to main content

toon/decode/
event_builder.rs

1use std::collections::HashSet;
2
3use crate::error::{Result, ToonError};
4use crate::{JsonPrimitive, JsonStreamEvent, JsonValue};
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum NodeValue {
8    Primitive(JsonPrimitive),
9    Array(Vec<Self>),
10    Object(ObjectNode),
11}
12
13#[derive(Debug, Clone, PartialEq)]
14pub struct ObjectNode {
15    pub entries: Vec<(String, NodeValue)>,
16    pub quoted_keys: HashSet<String>,
17}
18
19#[derive(Debug, Clone)]
20enum BuildContext {
21    Object {
22        entries: Vec<(String, NodeValue)>,
23        current_key: Option<String>,
24        quoted_keys: HashSet<String>,
25    },
26    Array {
27        items: Vec<NodeValue>,
28    },
29}
30
31#[derive(Debug, Clone)]
32struct BuildState {
33    stack: Vec<BuildContext>,
34    root: Option<NodeValue>,
35}
36
37/// Build a decoded node tree from a stream of events.
38///
39/// # Errors
40///
41/// Returns an error if the event stream is malformed (mismatched start/end
42/// events, missing keys, or incomplete stacks).
43pub fn build_node_from_events(
44    events: impl IntoIterator<Item = JsonStreamEvent>,
45) -> Result<NodeValue> {
46    let mut state = BuildState {
47        stack: Vec::new(),
48        root: None,
49    };
50
51    for event in events {
52        apply_event(&mut state, event)?;
53    }
54
55    finalize_state(state)
56}
57
58pub fn node_to_json(value: NodeValue) -> JsonValue {
59    match value {
60        NodeValue::Primitive(value) => JsonValue::Primitive(value),
61        NodeValue::Array(items) => JsonValue::Array(items.into_iter().map(node_to_json).collect()),
62        NodeValue::Object(obj) => JsonValue::Object(
63            obj.entries
64                .into_iter()
65                .map(|(key, value)| (key, node_to_json(value)))
66                .collect(),
67        ),
68    }
69}
70
71#[allow(clippy::too_many_lines)]
72fn apply_event(state: &mut BuildState, event: JsonStreamEvent) -> Result<()> {
73    match event {
74        JsonStreamEvent::StartObject => {
75            state.stack.push(BuildContext::Object {
76                entries: Vec::new(),
77                current_key: None,
78                quoted_keys: HashSet::new(),
79            });
80        }
81        JsonStreamEvent::EndObject => {
82            let Some(context) = state.stack.pop() else {
83                return Err(ToonError::unexpected_event("endObject", "with empty stack"));
84            };
85            let BuildContext::Object {
86                entries,
87                quoted_keys,
88                ..
89            } = context
90            else {
91                return Err(ToonError::mismatched_end("Object", "Array"));
92            };
93            let node = NodeValue::Object(ObjectNode {
94                entries,
95                quoted_keys,
96            });
97            if let Some(parent) = state.stack.last_mut() {
98                match parent {
99                    BuildContext::Object {
100                        entries,
101                        current_key,
102                        ..
103                    } => {
104                        let Some(key) = current_key.take() else {
105                            return Err(ToonError::message(
106                                "Object endObject event without preceding key",
107                            ));
108                        };
109                        entries.push((key, node));
110                    }
111                    BuildContext::Array { items } => {
112                        items.push(node);
113                    }
114                }
115            } else {
116                state.root = Some(node);
117            }
118        }
119        JsonStreamEvent::StartArray { .. } => {
120            state.stack.push(BuildContext::Array { items: Vec::new() });
121        }
122        JsonStreamEvent::EndArray => {
123            let Some(context) = state.stack.pop() else {
124                return Err(ToonError::unexpected_event("endArray", "with empty stack"));
125            };
126            let BuildContext::Array { items } = context else {
127                return Err(ToonError::mismatched_end("Array", "Object"));
128            };
129            let node = NodeValue::Array(items);
130            if let Some(parent) = state.stack.last_mut() {
131                match parent {
132                    BuildContext::Object {
133                        entries,
134                        current_key,
135                        ..
136                    } => {
137                        let Some(key) = current_key.take() else {
138                            return Err(ToonError::message(
139                                "Array endArray event without preceding key",
140                            ));
141                        };
142                        entries.push((key, node));
143                    }
144                    BuildContext::Array { items } => {
145                        items.push(node);
146                    }
147                }
148            } else {
149                state.root = Some(node);
150            }
151        }
152        JsonStreamEvent::Key { key, was_quoted } => {
153            let Some(BuildContext::Object {
154                current_key,
155                quoted_keys,
156                ..
157            }) = state.stack.last_mut()
158            else {
159                return Err(ToonError::unexpected_event(
160                    "Key",
161                    "outside of object context",
162                ));
163            };
164            *current_key = Some(key.clone());
165            if was_quoted {
166                quoted_keys.insert(key);
167            }
168        }
169        JsonStreamEvent::Primitive { value } => {
170            if state.stack.is_empty() {
171                state.root = Some(NodeValue::Primitive(value));
172                return Ok(());
173            }
174
175            match state.stack.last_mut() {
176                Some(BuildContext::Object {
177                    entries,
178                    current_key,
179                    ..
180                }) => {
181                    let Some(key) = current_key.take() else {
182                        return Err(ToonError::message(
183                            "Primitive event without preceding key in object",
184                        ));
185                    };
186                    entries.push((key, NodeValue::Primitive(value)));
187                }
188                Some(BuildContext::Array { items }) => {
189                    items.push(NodeValue::Primitive(value));
190                }
191                None => {}
192            }
193        }
194    }
195
196    Ok(())
197}
198
199fn finalize_state(state: BuildState) -> Result<NodeValue> {
200    if !state.stack.is_empty() {
201        return Err(ToonError::event_stream(
202            "Incomplete event stream: stack not empty at end",
203        ));
204    }
205
206    state
207        .root
208        .ok_or_else(|| ToonError::event_stream("No root value built from events"))
209}