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 && let JsonContext::Object { needs_comma, .. } = context
66 && needs_comma
67 {
68 out.push("\n".to_string());
69 out.push(" ".repeat(depth * indent));
70 }
71 out.push("}".to_string());
72
73 if let Some(parent) = stack.last_mut() {
74 match parent {
75 JsonContext::Object {
76 needs_comma,
77 expect_value,
78 } => {
79 *expect_value = false;
80 *needs_comma = true;
81 }
82 JsonContext::Array { needs_comma } => {
83 *needs_comma = true;
84 }
85 }
86 }
87 }
88 JsonStreamEvent::StartArray { .. } => {
89 if let Some(parent) = parent {
90 match parent {
91 JsonContext::Array { needs_comma } => {
92 if *needs_comma {
93 out.push(",".to_string());
94 }
95 if indent > 0 {
96 out.push("\n".to_string());
97 out.push(" ".repeat(depth * indent));
98 }
99 }
100 JsonContext::Object { .. } => {}
101 }
102 }
103
104 out.push("[".to_string());
105 stack.push(JsonContext::Array { needs_comma: false });
106 depth += 1;
107 }
108 JsonStreamEvent::EndArray => {
109 let Some(context) = stack.pop() else {
110 return Err(ToonError::message("Mismatched endArray event"));
111 };
112 if !matches!(context, JsonContext::Array { .. }) {
113 return Err(ToonError::message("Mismatched endArray event"));
114 }
115 depth = depth.saturating_sub(1);
116 if indent > 0
117 && let JsonContext::Array { needs_comma } = context
118 && needs_comma
119 {
120 out.push("\n".to_string());
121 out.push(" ".repeat(depth * indent));
122 }
123 out.push("]".to_string());
124
125 if let Some(parent) = stack.last_mut() {
126 match parent {
127 JsonContext::Object {
128 needs_comma,
129 expect_value,
130 } => {
131 *expect_value = false;
132 *needs_comma = true;
133 }
134 JsonContext::Array { needs_comma } => {
135 *needs_comma = true;
136 }
137 }
138 }
139 }
140 JsonStreamEvent::Key { key, .. } => {
141 let Some(JsonContext::Object {
142 needs_comma,
143 expect_value,
144 }) = stack.last_mut()
145 else {
146 return Err(ToonError::message("Key event outside of object context"));
147 };
148
149 if *needs_comma {
150 out.push(",".to_string());
151 }
152 if indent > 0 {
153 out.push("\n".to_string());
154 out.push(" ".repeat(depth * indent));
155 }
156
157 out.push(serde_json::to_string(&key).unwrap_or_else(|_| "\"\"".to_string()));
158 out.push(if indent > 0 { ": " } else { ":" }.to_string());
159
160 *expect_value = true;
161 *needs_comma = true;
162 }
163 JsonStreamEvent::Primitive { value } => {
164 if let Some(parent) = stack.last_mut() {
165 match parent {
166 JsonContext::Array { needs_comma } => {
167 if *needs_comma {
168 out.push(",".to_string());
169 }
170 if indent > 0 {
171 out.push("\n".to_string());
172 out.push(" ".repeat(depth * indent));
173 }
174 }
175 JsonContext::Object { expect_value, .. } => {
176 if !*expect_value {
177 return Err(ToonError::message(
178 "Primitive event in object without preceding key",
179 ));
180 }
181 }
182 }
183 }
184
185 out.push(stringify_primitive(&value));
186
187 if let Some(parent) = stack.last_mut() {
188 match parent {
189 JsonContext::Object { expect_value, .. } => {
190 *expect_value = false;
191 }
192 JsonContext::Array { needs_comma } => {
193 *needs_comma = true;
194 }
195 }
196 }
197 }
198 }
199 }
200
201 if !stack.is_empty() {
202 return Err(ToonError::message(
203 "Incomplete event stream: unclosed objects or arrays",
204 ));
205 }
206
207 Ok(out)
208}
209
210fn stringify_primitive(value: &crate::JsonPrimitive) -> String {
211 match value {
212 crate::StringOrNumberOrBoolOrNull::Null => "null".to_string(),
213 crate::StringOrNumberOrBoolOrNull::Bool(value) => value.to_string(),
214 crate::StringOrNumberOrBoolOrNull::Number(value) => serde_json::Number::from_f64(*value)
215 .map_or_else(|| "null".to_string(), |num| num.to_string()),
216 crate::StringOrNumberOrBoolOrNull::String(value) => {
217 serde_json::to_string(value).unwrap_or_else(|_| "\"\"".to_string())
218 }
219 }
220}