1use ferrin_spec::JsonObject;
5use ferrin_spec::JsonValue;
6use serde::Deserialize;
7use serde::Deserializer;
8
9#[derive(Debug, Clone, PartialEq, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct PartialArg {
13 pub json_path: String,
15 #[serde(default)]
17 pub string_value: Option<String>,
18 #[serde(default)]
20 pub number_value: Option<serde_json::Number>,
21 #[serde(default)]
23 pub bool_value: Option<bool>,
24 #[serde(default, deserialize_with = "present")]
26 pub null_value: bool,
27 #[serde(default)]
29 pub will_continue: Option<bool>,
30}
31
32fn present<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
33 JsonValue::deserialize(deserializer).map(|_| true)
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37enum PathSegment {
38 Key(String),
39 Index(usize),
40}
41
42#[derive(Debug)]
43struct StackEntry {
44 segment: PathSegment,
45 is_array: bool,
46 child_count: usize,
47}
48
49#[derive(Debug, Default)]
52pub struct JsonAccumulator {
53 args: JsonObject,
54 json_text: String,
55 stack: Vec<StackEntry>,
56 string_open: bool,
57}
58
59impl JsonAccumulator {
60 #[must_use]
62 pub fn new() -> Self {
63 Self::default()
64 }
65
66 #[must_use]
68 pub fn current(&self) -> &JsonObject {
69 &self.args
70 }
71
72 pub fn process(&mut self, partial_args: &[PartialArg]) -> String {
74 let mut delta = String::new();
75 for arg in partial_args {
76 let raw_path = arg.json_path.strip_prefix("$.").unwrap_or(&arg.json_path);
77 if raw_path.is_empty() {
78 continue;
79 }
80 let segments = parse_path(raw_path);
81 let existing = get_nested(&self.args, &segments).cloned();
82 if let (Some(string_value), Some(existing)) = (&arg.string_value, &existing) {
83 let escaped = escape_json_string(string_value);
84 let mut joined = existing.as_str().unwrap_or_default().to_owned();
85 joined.push_str(string_value);
86 set_nested(&mut self.args, &segments, JsonValue::from(joined));
87 delta.push_str(&escaped);
88 continue;
89 }
90 let Some((value, json)) = resolve_value(arg) else {
91 continue;
92 };
93 set_nested(&mut self.args, &segments, value);
94 delta.push_str(&self.emit_navigation(&segments, arg, &json));
95 }
96 self.json_text.push_str(&delta);
97 delta
98 }
99
100 #[must_use]
103 pub fn finalize(self) -> (String, String) {
104 let final_json = JsonValue::Object(self.args).to_string();
105 let closing = final_json
106 .get(self.json_text.len()..)
107 .unwrap_or_default()
108 .to_owned();
109 (final_json, closing)
110 }
111
112 fn ensure_root(&mut self) -> &'static str {
113 if self.stack.is_empty() {
114 self.stack.push(StackEntry {
115 segment: PathSegment::Key(String::new()),
116 is_array: false,
117 child_count: 0,
118 });
119 "{"
120 } else {
121 ""
122 }
123 }
124
125 fn emit_navigation(
126 &mut self,
127 segments: &[PathSegment],
128 arg: &PartialArg,
129 json: &str,
130 ) -> String {
131 let mut fragment = String::new();
132 if self.string_open {
133 fragment.push('"');
134 self.string_open = false;
135 }
136 fragment.push_str(self.ensure_root());
137 let Some((leaf, container)) = segments.split_last() else {
138 return fragment;
139 };
140 let common_depth = self.common_stack_depth(container);
141 fragment.push_str(&self.close_down_to(common_depth));
142 fragment.push_str(&self.open_down_to(container, leaf));
143 fragment.push_str(&self.emit_leaf(leaf, arg, json));
144 fragment
145 }
146
147 fn common_stack_depth(&self, container: &[PathSegment]) -> usize {
148 let max_depth = (self.stack.len().saturating_sub(1)).min(container.len());
149 let common = self
150 .stack
151 .iter()
152 .skip(1)
153 .zip(container.iter().take(max_depth))
154 .take_while(|(entry, segment)| entry.segment == **segment)
155 .count();
156 common + 1
157 }
158
159 fn close_down_to(&mut self, depth: usize) -> String {
160 let mut fragment = String::new();
161 while self.stack.len() > depth {
162 if let Some(entry) = self.stack.pop() {
163 fragment.push(if entry.is_array { ']' } else { '}' });
164 }
165 }
166 fragment
167 }
168
169 fn open_down_to(&mut self, container: &[PathSegment], leaf: &PathSegment) -> String {
170 let mut fragment = String::new();
171 let start = self.stack.len().saturating_sub(1);
172 for index in start..container.len() {
173 let segment = &container[index];
174 if let Some(parent) = self.stack.last_mut() {
175 if parent.child_count > 0 {
176 fragment.push(',');
177 }
178 parent.child_count += 1;
179 }
180 if let PathSegment::Key(key) = segment {
181 fragment.push_str(&JsonValue::from(key.as_str()).to_string());
182 fragment.push(':');
183 }
184 let child = container.get(index + 1).unwrap_or(leaf);
185 let is_array = matches!(child, PathSegment::Index(_));
186 fragment.push(if is_array { '[' } else { '{' });
187 self.stack.push(StackEntry {
188 segment: segment.clone(),
189 is_array,
190 child_count: 0,
191 });
192 }
193 fragment
194 }
195
196 fn emit_leaf(&mut self, leaf: &PathSegment, arg: &PartialArg, json: &str) -> String {
197 let mut fragment = String::new();
198 if let Some(container) = self.stack.last_mut() {
199 if container.child_count > 0 {
200 fragment.push(',');
201 }
202 container.child_count += 1;
203 }
204 if let PathSegment::Key(key) = leaf {
205 fragment.push_str(&JsonValue::from(key.as_str()).to_string());
206 fragment.push(':');
207 }
208 if arg.string_value.is_some() && arg.will_continue == Some(true) {
209 fragment.push_str(&json[..json.len().saturating_sub(1)]);
210 self.string_open = true;
211 } else {
212 fragment.push_str(json);
213 }
214 fragment
215 }
216}
217
218fn escape_json_string(text: &str) -> String {
219 let quoted = JsonValue::from(text).to_string();
220 quoted[1..quoted.len() - 1].to_owned()
221}
222
223fn resolve_value(arg: &PartialArg) -> Option<(JsonValue, String)> {
224 if let Some(text) = &arg.string_value {
225 let value = JsonValue::from(text.as_str());
226 let json = value.to_string();
227 return Some((value, json));
228 }
229 if let Some(number) = &arg.number_value {
230 let value = JsonValue::Number(number.clone());
231 let json = value.to_string();
232 return Some((value, json));
233 }
234 if let Some(flag) = arg.bool_value {
235 return Some((JsonValue::Bool(flag), flag.to_string()));
236 }
237 if arg.null_value {
238 return Some((JsonValue::Null, "null".to_owned()));
239 }
240 None
241}
242
243fn parse_path(raw_path: &str) -> Vec<PathSegment> {
244 let mut segments = Vec::new();
245 for part in raw_path.split('.') {
246 match part.find('[') {
247 None => segments.push(PathSegment::Key(part.to_owned())),
248 Some(bracket) => {
249 if bracket > 0 {
250 segments.push(PathSegment::Key(part[..bracket].to_owned()));
251 }
252 let mut rest = &part[bracket..];
253 while let Some(start) = rest.find('[') {
254 let Some(end) = rest[start..].find(']') else {
255 break;
256 };
257 if let Ok(index) = rest[start + 1..start + end].parse::<usize>() {
258 segments.push(PathSegment::Index(index));
259 }
260 rest = &rest[start + end + 1..];
261 }
262 }
263 }
264 }
265 segments
266}
267
268fn get_nested<'a>(object: &'a JsonObject, segments: &[PathSegment]) -> Option<&'a JsonValue> {
269 let (first, rest) = segments.split_first()?;
270 let PathSegment::Key(key) = first else {
271 return None;
272 };
273 let mut current = object.get(key)?;
274 for segment in rest {
275 current = match (segment, current) {
276 (PathSegment::Key(key), JsonValue::Object(object)) => object.get(key)?,
277 (PathSegment::Index(index), JsonValue::Array(items)) => items.get(*index)?,
278 _ => return None,
279 };
280 }
281 Some(current)
282}
283
284fn set_nested(object: &mut JsonObject, segments: &[PathSegment], value: JsonValue) {
285 let Some((first, rest)) = segments.split_first() else {
286 return;
287 };
288 let PathSegment::Key(key) = first else {
289 return;
290 };
291 let Some((last, middle)) = rest.split_last() else {
292 object.insert(key.clone(), value);
293 return;
294 };
295 let mut current = object
296 .entry(key.clone())
297 .or_insert_with(|| container_for(rest.first()));
298 for (index, segment) in middle.iter().enumerate() {
299 let next = rest.get(index + 1);
300 current = child_slot(current, segment, next);
301 }
302 match (last, current) {
303 (PathSegment::Key(key), JsonValue::Object(target)) => {
304 target.insert(key.clone(), value);
305 }
306 (PathSegment::Index(index), JsonValue::Array(items)) => {
307 while items.len() <= *index {
308 items.push(JsonValue::Null);
309 }
310 items[*index] = value;
311 }
312 (segment, slot) => {
313 *slot = container_for(Some(segment));
314 set_nested_into(slot, segment, value);
315 }
316 }
317}
318
319fn set_nested_into(slot: &mut JsonValue, segment: &PathSegment, value: JsonValue) {
320 match (segment, slot) {
321 (PathSegment::Key(key), JsonValue::Object(target)) => {
322 target.insert(key.clone(), value);
323 }
324 (PathSegment::Index(index), JsonValue::Array(items)) => {
325 while items.len() <= *index {
326 items.push(JsonValue::Null);
327 }
328 items[*index] = value;
329 }
330 _ => {}
331 }
332}
333
334fn container_for(segment: Option<&PathSegment>) -> JsonValue {
335 match segment {
336 Some(PathSegment::Index(_)) => JsonValue::Array(Vec::new()),
337 _ => JsonValue::Object(JsonObject::new()),
338 }
339}
340
341fn child_slot<'a>(
342 current: &'a mut JsonValue,
343 segment: &PathSegment,
344 next: Option<&PathSegment>,
345) -> &'a mut JsonValue {
346 let wanted_container = container_for(next);
347 match segment {
348 PathSegment::Key(key) => {
349 if !current.is_object() {
350 *current = JsonValue::Object(JsonObject::new());
351 }
352 match current {
353 JsonValue::Object(object) => {
354 let slot = object.entry(key.clone()).or_insert(JsonValue::Null);
355 if slot.is_null() {
356 *slot = wanted_container;
357 }
358 slot
359 }
360 other => other,
361 }
362 }
363 PathSegment::Index(index) => {
364 if !current.is_array() {
365 *current = JsonValue::Array(Vec::new());
366 }
367 match current {
368 JsonValue::Array(items) => {
369 while items.len() <= *index {
370 items.push(JsonValue::Null);
371 }
372 if items[*index].is_null() {
373 items[*index] = wanted_container;
374 }
375 &mut items[*index]
376 }
377 other => other,
378 }
379 }
380 }
381}