1use crate::JsonStreamEvent;
2use crate::error::{Result, ToonError};
3
4#[derive(Debug, Clone)]
5enum JsonContext {
6 Object {
7 needs_comma: bool,
8 expect_value: bool,
9 },
10 Array {
11 needs_comma: bool,
12 },
13}
14
15#[allow(clippy::too_many_lines)]
22pub fn json_stream_from_events(
23 events: impl IntoIterator<Item = JsonStreamEvent>,
24 indent: usize,
25) -> Result<Vec<String>> {
26 let mut stack: Vec<JsonContext> = Vec::new();
27 let mut depth = 0usize;
28 let mut out = Vec::new();
29
30 for event in events {
31 let parent = stack.last_mut();
32 match event {
33 JsonStreamEvent::StartObject => {
34 if let Some(parent) = parent {
35 match parent {
36 JsonContext::Array { needs_comma } => {
37 if *needs_comma {
38 out.push(",".to_string());
39 }
40 if indent > 0 {
41 out.push("\n".to_string());
42 out.push(" ".repeat(depth * indent));
43 }
44 }
45 JsonContext::Object { .. } => {}
46 }
47 }
48
49 out.push("{".to_string());
50 stack.push(JsonContext::Object {
51 needs_comma: false,
52 expect_value: false,
53 });
54 depth += 1;
55 }
56 JsonStreamEvent::EndObject => {
57 let Some(context) = stack.pop() else {
58 return Err(ToonError::message("Mismatched endObject event"));
59 };
60 if !matches!(context, JsonContext::Object { .. }) {
61 return Err(ToonError::message("Mismatched endObject event"));
62 }
63 depth = depth.saturating_sub(1);
64 if indent > 0 {
65 if let JsonContext::Object { needs_comma, .. } = context {
66 if needs_comma {
67 out.push("\n".to_string());
68 out.push(" ".repeat(depth * indent));
69 }
70 }
71 }
72 out.push("}".to_string());
73
74 if let Some(parent) = stack.last_mut() {
75 match parent {
76 JsonContext::Object {
77 needs_comma,
78 expect_value,
79 } => {
80 *expect_value = false;
81 *needs_comma = true;
82 }
83 JsonContext::Array { needs_comma } => {
84 *needs_comma = true;
85 }
86 }
87 }
88 }
89 JsonStreamEvent::StartArray { .. } => {
90 if let Some(parent) = parent {
91 match parent {
92 JsonContext::Array { needs_comma } => {
93 if *needs_comma {
94 out.push(",".to_string());
95 }
96 if indent > 0 {
97 out.push("\n".to_string());
98 out.push(" ".repeat(depth * indent));
99 }
100 }
101 JsonContext::Object { .. } => {}
102 }
103 }
104
105 out.push("[".to_string());
106 stack.push(JsonContext::Array { needs_comma: false });
107 depth += 1;
108 }
109 JsonStreamEvent::EndArray => {
110 let Some(context) = stack.pop() else {
111 return Err(ToonError::message("Mismatched endArray event"));
112 };
113 if !matches!(context, JsonContext::Array { .. }) {
114 return Err(ToonError::message("Mismatched endArray event"));
115 }
116 depth = depth.saturating_sub(1);
117 if indent > 0 {
118 if let JsonContext::Array { needs_comma } = context {
119 if needs_comma {
120 out.push("\n".to_string());
121 out.push(" ".repeat(depth * indent));
122 }
123 }
124 }
125 out.push("]".to_string());
126
127 if let Some(parent) = stack.last_mut() {
128 match parent {
129 JsonContext::Object {
130 needs_comma,
131 expect_value,
132 } => {
133 *expect_value = false;
134 *needs_comma = true;
135 }
136 JsonContext::Array { needs_comma } => {
137 *needs_comma = true;
138 }
139 }
140 }
141 }
142 JsonStreamEvent::Key { key, .. } => {
143 let Some(JsonContext::Object {
144 needs_comma,
145 expect_value,
146 }) = stack.last_mut()
147 else {
148 return Err(ToonError::message("Key event outside of object context"));
149 };
150
151 if *needs_comma {
152 out.push(",".to_string());
153 }
154 if indent > 0 {
155 out.push("\n".to_string());
156 out.push(" ".repeat(depth * indent));
157 }
158
159 out.push(serde_json::to_string(&key).unwrap_or_else(|_| "\"\"".to_string()));
160 out.push(if indent > 0 { ": " } else { ":" }.to_string());
161
162 *expect_value = true;
163 *needs_comma = true;
164 }
165 JsonStreamEvent::Primitive { value } => {
166 if let Some(parent) = stack.last_mut() {
167 match parent {
168 JsonContext::Array { needs_comma } => {
169 if *needs_comma {
170 out.push(",".to_string());
171 }
172 if indent > 0 {
173 out.push("\n".to_string());
174 out.push(" ".repeat(depth * indent));
175 }
176 }
177 JsonContext::Object { expect_value, .. } => {
178 if !*expect_value {
179 return Err(ToonError::message(
180 "Primitive event in object without preceding key",
181 ));
182 }
183 }
184 }
185 }
186
187 out.push(stringify_primitive(&value));
188
189 if let Some(parent) = stack.last_mut() {
190 match parent {
191 JsonContext::Object { expect_value, .. } => {
192 *expect_value = false;
193 }
194 JsonContext::Array { needs_comma } => {
195 *needs_comma = true;
196 }
197 }
198 }
199 }
200 }
201 }
202
203 if !stack.is_empty() {
204 return Err(ToonError::message(
205 "Incomplete event stream: unclosed objects or arrays",
206 ));
207 }
208
209 Ok(out)
210}
211
212fn stringify_primitive(value: &crate::JsonPrimitive) -> String {
213 match value {
214 crate::StringOrNumberOrBoolOrNull::Null => "null".to_string(),
215 crate::StringOrNumberOrBoolOrNull::Bool(value) => value.to_string(),
216 crate::StringOrNumberOrBoolOrNull::Number(value) => serde_json::Number::from_f64(*value)
217 .map_or_else(|| "null".to_string(), |num| num.to_string()),
218 crate::StringOrNumberOrBoolOrNull::String(value) => {
219 serde_json::to_string(value).unwrap_or_else(|_| "\"\"".to_string())
220 }
221 }
222}