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
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}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::StringOrNumberOrBoolOrNull;
215
216 fn prim(v: &str) -> JsonStreamEvent {
217 JsonStreamEvent::Primitive {
218 value: StringOrNumberOrBoolOrNull::String(v.to_string()),
219 }
220 }
221
222 fn prim_num(n: f64) -> JsonStreamEvent {
223 JsonStreamEvent::Primitive {
224 value: StringOrNumberOrBoolOrNull::Number(n),
225 }
226 }
227
228 fn key(k: &str) -> JsonStreamEvent {
229 JsonStreamEvent::Key {
230 key: k.to_string(),
231 was_quoted: false,
232 }
233 }
234
235 fn quoted_key(k: &str) -> JsonStreamEvent {
236 JsonStreamEvent::Key {
237 key: k.to_string(),
238 was_quoted: true,
239 }
240 }
241
242 #[test]
243 fn build_root_primitive() {
244 let root = build_node_from_events([prim_num(42.0)]).unwrap();
245 assert!(matches!(
246 root,
247 NodeValue::Primitive(StringOrNumberOrBoolOrNull::Number(n)) if (n - 42.0).abs() < f64::EPSILON
248 ));
249 }
250
251 #[test]
252 fn build_empty_object() {
253 let root =
254 build_node_from_events([JsonStreamEvent::StartObject, JsonStreamEvent::EndObject])
255 .unwrap();
256 if let NodeValue::Object(obj) = root {
257 assert!(obj.entries.is_empty());
258 } else {
259 panic!("expected object");
260 }
261 }
262
263 #[test]
264 fn build_empty_array() {
265 let root = build_node_from_events([
266 JsonStreamEvent::StartArray { length: 0 },
267 JsonStreamEvent::EndArray,
268 ])
269 .unwrap();
270 assert!(matches!(root, NodeValue::Array(items) if items.is_empty()));
271 }
272
273 #[test]
274 fn build_object_with_primitive_entry() {
275 let root = build_node_from_events([
276 JsonStreamEvent::StartObject,
277 key("name"),
278 prim("Alice"),
279 JsonStreamEvent::EndObject,
280 ])
281 .unwrap();
282 if let NodeValue::Object(obj) = root {
283 assert_eq!(obj.entries.len(), 1);
284 assert_eq!(obj.entries[0].0, "name");
285 assert!(!obj.quoted_keys.contains("name"));
286 } else {
287 panic!("expected object");
288 }
289 }
290
291 #[test]
292 fn quoted_keys_are_tracked() {
293 let root = build_node_from_events([
294 JsonStreamEvent::StartObject,
295 quoted_key("weird key"),
296 prim("x"),
297 JsonStreamEvent::EndObject,
298 ])
299 .unwrap();
300 if let NodeValue::Object(obj) = root {
301 assert!(obj.quoted_keys.contains("weird key"));
302 } else {
303 panic!("expected object");
304 }
305 }
306
307 #[test]
308 fn nested_object_round_trips() {
309 let root = build_node_from_events([
310 JsonStreamEvent::StartObject,
311 key("outer"),
312 JsonStreamEvent::StartObject,
313 key("inner"),
314 prim_num(1.0),
315 JsonStreamEvent::EndObject,
316 JsonStreamEvent::EndObject,
317 ])
318 .unwrap();
319 if let NodeValue::Object(obj) = root {
320 let (k, v) = &obj.entries[0];
321 assert_eq!(k, "outer");
322 assert!(matches!(v, NodeValue::Object(_)));
323 } else {
324 panic!("expected object");
325 }
326 }
327
328 #[test]
329 fn array_in_object() {
330 let root = build_node_from_events([
331 JsonStreamEvent::StartObject,
332 key("nums"),
333 JsonStreamEvent::StartArray { length: 2 },
334 prim_num(1.0),
335 prim_num(2.0),
336 JsonStreamEvent::EndArray,
337 JsonStreamEvent::EndObject,
338 ])
339 .unwrap();
340 if let NodeValue::Object(obj) = root {
341 let (_, v) = &obj.entries[0];
342 assert!(matches!(v, NodeValue::Array(items) if items.len() == 2));
343 } else {
344 panic!("expected object");
345 }
346 }
347
348 #[test]
349 fn object_in_array() {
350 let root = build_node_from_events([
351 JsonStreamEvent::StartArray { length: 1 },
352 JsonStreamEvent::StartObject,
353 key("k"),
354 prim_num(1.0),
355 JsonStreamEvent::EndObject,
356 JsonStreamEvent::EndArray,
357 ])
358 .unwrap();
359 if let NodeValue::Array(items) = root {
360 assert!(matches!(&items[0], NodeValue::Object(_)));
361 } else {
362 panic!("expected array");
363 }
364 }
365
366 #[test]
367 fn unbalanced_end_object_errors() {
368 let result = build_node_from_events([JsonStreamEvent::EndObject]);
369 assert!(result.is_err());
370 }
371
372 #[test]
373 fn unbalanced_end_array_errors() {
374 let result = build_node_from_events([JsonStreamEvent::EndArray]);
375 assert!(result.is_err());
376 }
377
378 #[test]
379 fn mismatched_end_errors() {
380 let result =
381 build_node_from_events([JsonStreamEvent::StartObject, JsonStreamEvent::EndArray]);
382 assert!(result.is_err());
383 }
384
385 #[test]
386 fn key_outside_object_errors() {
387 let result = build_node_from_events([key("dangling"), prim("x")]);
388 assert!(result.is_err());
389 }
390
391 #[test]
392 fn incomplete_stream_errors() {
393 let result = build_node_from_events([JsonStreamEvent::StartObject]);
394 assert!(result.is_err());
395 }
396
397 #[test]
398 fn empty_stream_errors() {
399 let result = build_node_from_events([]);
400 assert!(result.is_err());
401 }
402
403 #[test]
404 fn primitive_in_object_without_key_errors() {
405 let result = build_node_from_events([
406 JsonStreamEvent::StartObject,
407 prim("no-key"),
408 JsonStreamEvent::EndObject,
409 ]);
410 assert!(result.is_err());
411 }
412
413 #[test]
414 fn node_to_json_preserves_structure() {
415 let root = NodeValue::Object(ObjectNode {
416 entries: vec![(
417 "a".to_string(),
418 NodeValue::Array(vec![NodeValue::Primitive(
419 StringOrNumberOrBoolOrNull::Number(1.0),
420 )]),
421 )],
422 quoted_keys: HashSet::new(),
423 });
424 let json = node_to_json(root);
425 if let JsonValue::Object(entries) = json {
426 let (k, v) = &entries[0];
427 assert_eq!(k, "a");
428 assert!(matches!(v, JsonValue::Array(items) if items.len() == 1));
429 } else {
430 panic!("expected object");
431 }
432 }
433}