toon/decode/
event_builder.rs1use 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
37pub 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}