Skip to main content

ferrin_google/
json_accumulator.rs

1//! Incremental reconstruction of function call arguments streamed as
2//! `partialArgs` (JSON path plus scalar value) into JSON text deltas.
3
4use ferrin_spec::JsonObject;
5use ferrin_spec::JsonValue;
6use serde::Deserialize;
7use serde::Deserializer;
8
9/// One streamed argument fragment (`functionCall.partialArgs[]`).
10#[derive(Debug, Clone, PartialEq, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct PartialArg {
13    /// JSON path of the value (`$.location`, `$.items[0].name`).
14    pub json_path: String,
15    /// String value or string continuation.
16    #[serde(default)]
17    pub string_value: Option<String>,
18    /// Number value.
19    #[serde(default)]
20    pub number_value: Option<serde_json::Number>,
21    /// Boolean value.
22    #[serde(default)]
23    pub bool_value: Option<bool>,
24    /// Whether the `nullValue` key was present.
25    #[serde(default, deserialize_with = "present")]
26    pub null_value: bool,
27    /// Whether the string value continues in a later fragment.
28    #[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/// Accumulates `partialArgs` into an argument object while producing the
50/// JSON text deltas that spell out the same object.
51#[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    /// Creates an empty accumulator.
61    #[must_use]
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Current argument object.
67    #[must_use]
68    pub fn current(&self) -> &JsonObject {
69        &self.args
70    }
71
72    /// Applies `partial_args` and returns the JSON text delta they produce.
73    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    /// Finishes the object: returns the final JSON text and the delta that
101    /// completes the text emitted so far.
102    #[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}