Skip to main content

rig_core/
json_utils.rs

1use serde::Deserialize;
2use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor};
3use std::convert::Infallible;
4use std::fmt;
5use std::marker::PhantomData;
6use std::str::FromStr;
7
8/// `skip_serializing_if` helper: serde requires a `fn(&bool) -> bool`, so the
9/// trivially-copy lint does not apply here.
10#[allow(clippy::trivially_copy_pass_by_ref)]
11pub(crate) fn is_false(value: &bool) -> bool {
12    !value
13}
14
15/// `value` with lexicographically sorted object keys at every depth,
16/// independent of serde_json's `preserve_order` feature. Array order is retained.
17pub fn canonical(value: serde_json::Value) -> serde_json::Value {
18    match value {
19        serde_json::Value::Object(map) => {
20            let mut entries: Vec<_> = map.into_iter().collect();
21            entries.sort_by(|(left, _), (right, _)| left.cmp(right));
22            let entries = entries
23                .into_iter()
24                .map(|(key, value)| (key, canonical(value)));
25            serde_json::Value::Object(entries.collect())
26        }
27        serde_json::Value::Array(items) => {
28            serde_json::Value::Array(items.into_iter().map(canonical).collect())
29        }
30        other => other,
31    }
32}
33
34/// Renders [`canonical`] JSON compactly.
35pub fn to_canonical_string(value: &serde_json::Value) -> String {
36    canonical(value.clone()).to_string()
37}
38
39pub fn merge(a: serde_json::Value, b: serde_json::Value) -> serde_json::Value {
40    match (a, b) {
41        (serde_json::Value::Object(mut a_map), serde_json::Value::Object(b_map)) => {
42            b_map.into_iter().for_each(|(key, value)| {
43                a_map.insert(key, value);
44            });
45            serde_json::Value::Object(a_map)
46        }
47        (a, _) => a,
48    }
49}
50
51/// Applies a request builder's `additional_params` call: `None` clears, and a
52/// value merges over the parameters earlier calls set. Merging combines JSON
53/// objects key by key; earlier parameters that are not an object are kept.
54pub(crate) fn merge_params(
55    existing: Option<serde_json::Value>,
56    params: Option<serde_json::Value>,
57) -> Option<serde_json::Value> {
58    Some(match (existing, params?) {
59        (Some(existing), params) => merge(existing, params),
60        (None, params) => params,
61    })
62}
63
64// Callers require the image or audio feature.
65#[cfg_attr(not(any(feature = "image", feature = "audio")), allow(dead_code))]
66pub fn merge_inplace(a: &mut serde_json::Value, b: serde_json::Value) {
67    if let (serde_json::Value::Object(a_map), serde_json::Value::Object(b_map)) = (a, b) {
68        b_map.into_iter().for_each(|(key, value)| {
69            a_map.insert(key, value);
70        });
71    }
72}
73
74/// Normalize a provider-wire field that may contain encoded JSON in a string.
75///
76/// This deliberately unwraps [`serde_json::Value::String`] and is only for
77/// provider decoding, before a value enters Rig's canonical message model.
78pub fn value_to_json_string(value: &serde_json::Value) -> String {
79    match value {
80        serde_json::Value::String(s) => s.clone(),
81        other => other.to_string(),
82    }
83}
84
85/// Parse tool arguments from a streamed string payload.
86/// Some providers emit an empty string for parameterless tool calls; normalize that to `{}`.
87pub fn parse_tool_arguments(arguments: &str) -> serde_json::Result<serde_json::Value> {
88    if arguments.trim().is_empty() {
89        return Ok(serde_json::Value::Object(serde_json::Map::new()));
90    }
91
92    serde_json::from_str(arguments)
93}
94
95/// Whether `arguments` are a whole JSON object ([`parse_tool_arguments`]).
96/// Only a text that ends in `}` (or says nothing) can be one, so no other
97/// is parsed.
98pub fn is_whole_object(arguments: &str) -> bool {
99    let end = arguments.trim_end();
100    (end.is_empty() || end.ends_with('}'))
101        && matches!(
102            parse_tool_arguments(arguments),
103            Ok(serde_json::Value::Object(_))
104        )
105}
106
107/// The longest object a cut-off JSON text `text` still states: open strings,
108/// arrays and objects are closed and a dangling key or separator dropped, as
109/// pi's streaming JSON parse does. `None` when no prefix states an object.
110pub fn parse_partial_object(text: &str) -> Option<serde_json::Map<String, serde_json::Value>> {
111    // Bounds the work on a large cut-off input: the last valid cut is
112    // almost always near its end.
113    const MAX_ATTEMPTS: usize = 256;
114    let mut stack = Vec::new();
115    let mut in_string = false;
116    let mut escaped = false;
117    // Prefix lengths a value just ended at, with the containers open there.
118    let mut cuts: Vec<(usize, Vec<u8>, bool)> = Vec::new();
119    let bytes = text.as_bytes();
120    for (at, byte) in bytes.iter().enumerate() {
121        if in_string {
122            match (escaped, *byte) {
123                (true, _) => escaped = false,
124                (false, b'\\') => escaped = true,
125                (false, b'"') => {
126                    in_string = false;
127                    cuts.push((at + 1, stack.clone(), false));
128                }
129                (false, _) => {}
130            }
131            continue;
132        }
133        match byte {
134            b'"' => in_string = true,
135            b'{' | b'[' => {
136                stack.push(*byte);
137                cuts.push((at + 1, stack.clone(), false));
138            }
139            b'}' | b']' => {
140                stack.pop();
141                cuts.push((at + 1, stack.clone(), false));
142            }
143            b if b.is_ascii_alphanumeric() || *b == b'.' || *b == b'-' || *b == b'+' => {
144                let next = bytes.get(at + 1).copied();
145                if !next.is_some_and(|n| {
146                    n.is_ascii_alphanumeric() || n == b'.' || n == b'-' || n == b'+'
147                }) {
148                    cuts.push((at + 1, stack.clone(), false));
149                }
150            }
151            _ => {}
152        }
153    }
154    if in_string {
155        let at = text.len() - usize::from(escaped);
156        cuts.push((at, stack.clone(), true));
157    }
158    cuts.iter()
159        .rev()
160        .take(MAX_ATTEMPTS)
161        .find_map(|(at, open, quote)| {
162            let mut candidate = text.get(..*at)?.to_owned();
163            if *quote {
164                candidate.push('"');
165            }
166            for container in open.iter().rev() {
167                candidate.push(if *container == b'{' { '}' } else { ']' });
168            }
169            match serde_json::from_str(&candidate) {
170                Ok(serde_json::Value::Object(object)) => Some(object),
171                _ => None,
172            }
173        })
174}
175
176/// The best object a tool call's argument text states so far, for showing a
177/// call while it streams. It never fails: complete JSON parses as is, text
178/// with raw control characters or invalid escapes is repaired first, a
179/// cut-off prefix keeps the longest object it states
180/// ([`parse_partial_object`]), and anything else is an empty object, as is
181/// a top level that is not an object.
182///
183/// It is for display only. A call's arguments are what its end states, and
184/// only those are validated and executed.
185///
186/// ```
187/// use rig_core::streaming::parse_partial_arguments;
188///
189/// let partial = parse_partial_arguments(r#"{"path": "notes.md", "content": "Hel"#);
190/// assert_eq!(partial["path"], "notes.md");
191/// assert_eq!(partial["content"], "Hel");
192/// ```
193pub fn parse_partial_arguments(text: &str) -> serde_json::Map<String, serde_json::Value> {
194    if text.trim().is_empty() {
195        return serde_json::Map::new();
196    }
197    let repaired = repair_json(text);
198    let complete = serde_json::from_str(text)
199        .ok()
200        .or_else(|| serde_json::from_str(&repaired).ok());
201    if let Some(value) = complete {
202        return match value {
203            serde_json::Value::Object(object) => object,
204            _ => serde_json::Map::new(),
205        };
206    }
207    let cut = without_partial_escape(&repaired);
208    parse_partial_object(cut)
209        .or_else(|| parse_partial_object(text))
210        .unwrap_or_default()
211}
212
213/// `text` with raw control characters inside strings escaped and each
214/// backslash that starts no valid escape doubled, as models sometimes write
215/// them.
216fn repair_json(text: &str) -> String {
217    let mut repaired = String::with_capacity(text.len());
218    let mut in_string = false;
219    let mut chars = text.chars().peekable();
220    while let Some(c) = chars.next() {
221        if !in_string {
222            in_string = c == '"';
223            repaired.push(c);
224            continue;
225        }
226        match c {
227            '"' => {
228                in_string = false;
229                repaired.push(c);
230            }
231            '\\' => match chars.peek() {
232                Some('"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' | 'u') => {
233                    repaired.push(c);
234                    if let Some(next) = chars.next() {
235                        repaired.push(next);
236                    }
237                }
238                // A trailing backslash may begin an escape the next fragment ends.
239                None => repaired.push(c),
240                Some(_) => repaired.push_str("\\\\"),
241            },
242            '\n' => repaired.push_str("\\n"),
243            '\r' => repaired.push_str("\\r"),
244            '\t' => repaired.push_str("\\t"),
245            c if c.is_control() => {
246                use std::fmt::Write as _;
247                // Writing to a `String` cannot fail.
248                let _ = write!(repaired, "\\u{:04x}", u32::from(c));
249            }
250            c => repaired.push(c),
251        }
252    }
253    repaired
254}
255
256/// `text` without a `\u` escape its end cuts short, which would otherwise
257/// cost the whole string it ends.
258fn without_partial_escape(text: &str) -> &str {
259    let bytes = text.as_bytes();
260    let tail = bytes.len().saturating_sub(5);
261    let Some(at) = (tail..bytes.len())
262        .rev()
263        .find(|at| bytes.get(*at..*at + 2) == Some(b"\\u"))
264    else {
265        return text;
266    };
267    let digits = bytes.get(at + 2..).unwrap_or_default();
268    // A preceding backslash run of odd length escapes this backslash itself.
269    let escaping = bytes
270        .get(..at)
271        .unwrap_or_default()
272        .iter()
273        .rev()
274        .take_while(|byte| **byte == b'\\')
275        .count();
276    if digits.len() < 4 && digits.iter().all(u8::is_ascii_hexdigit) && escaping % 2 == 0 {
277        text.get(..at).unwrap_or(text)
278    } else {
279        text
280    }
281}
282
283/// Deserializes a string or object as one item, a sequence as multiple items,
284/// and null as an empty list. Strings use [`FromStr`]; objects use [`Deserialize`].
285pub fn string_or_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
286where
287    T: Deserialize<'de> + FromStr<Err = Infallible>,
288    D: Deserializer<'de>,
289{
290    struct StringOrVec<T>(PhantomData<fn() -> T>);
291
292    impl<'de, T> Visitor<'de> for StringOrVec<T>
293    where
294        T: Deserialize<'de> + FromStr<Err = Infallible>,
295    {
296        type Value = Vec<T>;
297
298        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
299            formatter.write_str("a string, sequence, or null")
300        }
301
302        fn visit_str<E>(self, value: &str) -> Result<Vec<T>, E>
303        where
304            E: de::Error,
305        {
306            let item = FromStr::from_str(value).map_err(de::Error::custom)?;
307            Ok(vec![item])
308        }
309
310        fn visit_seq<A>(self, seq: A) -> Result<Vec<T>, A::Error>
311        where
312            A: SeqAccess<'de>,
313        {
314            Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))
315        }
316
317        fn visit_map<M>(self, map: M) -> Result<Vec<T>, M::Error>
318        where
319            M: MapAccess<'de>,
320        {
321            let item = Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
322            Ok(vec![item])
323        }
324
325        fn visit_none<E>(self) -> Result<Vec<T>, E>
326        where
327            E: de::Error,
328        {
329            Ok(vec![])
330        }
331
332        fn visit_unit<E>(self) -> Result<Vec<T>, E>
333        where
334            E: de::Error,
335        {
336            Ok(vec![])
337        }
338    }
339
340    deserializer.deserialize_any(StringOrVec(PhantomData))
341}
342
343/// Deserializes `T`, mapping an explicit `null` to `T::default()`.
344///
345/// Driven through `deserialize_option` rather than `deserialize_any`; the two
346/// agree only for self-describing formats, which is all rig decodes (JSON).
347pub fn null_or_default<'de, T, D>(deserializer: D) -> Result<T, D::Error>
348where
349    T: Deserialize<'de> + Default,
350    D: Deserializer<'de>,
351{
352    Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
353}
354
355#[cfg(test)]
356mod tests;
357
358/// Lenient reads of a provider reply: each accessor gives `None`, or an
359/// empty slice, for a field that is missing or of another type, and never
360/// fails. Decoders read replies through it, so a field no block or finish
361/// needs can never fail a reply.
362///
363/// ```
364/// use rig_core::json_utils::Lenient;
365/// use serde_json::json;
366///
367/// let reply = json!({"id": "r1", "usage": {"input_tokens": "7"}, "output": null});
368/// assert_eq!(reply.str("id"), Some("r1"));
369/// assert_eq!(reply.at("/usage/input_tokens").and_then(Lenient::as_u64_lenient), Some(7));
370/// assert!(reply.arr("output").is_empty());
371/// ```
372pub trait Lenient {
373    /// The string at `key`.
374    fn str(&self, key: &str) -> Option<&str>;
375    /// The unsigned integer at `key`, from a number or a numeric string.
376    fn u64(&self, key: &str) -> Option<u64>;
377    /// The integer at `key`, from a number or a numeric string.
378    fn i64(&self, key: &str) -> Option<i64>;
379    /// The number at `key`, from a number or a numeric string.
380    fn f64(&self, key: &str) -> Option<f64>;
381    /// The boolean at `key`.
382    fn bool(&self, key: &str) -> Option<bool>;
383    /// The object at `key`.
384    fn obj(&self, key: &str) -> Option<&serde_json::Map<String, serde_json::Value>>;
385    /// The array at `key`, empty when absent or of another type.
386    fn arr(&self, key: &str) -> &[serde_json::Value];
387    /// The value at a JSON pointer, `None` for `null`.
388    fn at(&self, pointer: &str) -> Option<&serde_json::Value>;
389    /// This value as an unsigned integer, from a number or a numeric string.
390    fn as_u64_lenient(&self) -> Option<u64>;
391}
392
393impl Lenient for serde_json::Value {
394    fn str(&self, key: &str) -> Option<&str> {
395        self.get(key)?.as_str()
396    }
397
398    fn u64(&self, key: &str) -> Option<u64> {
399        self.get(key)?.as_u64_lenient()
400    }
401
402    fn i64(&self, key: &str) -> Option<i64> {
403        match self.get(key)? {
404            serde_json::Value::Number(number) => number.as_i64(),
405            serde_json::Value::String(text) => text.trim().parse().ok(),
406            _ => None,
407        }
408    }
409
410    fn f64(&self, key: &str) -> Option<f64> {
411        match self.get(key)? {
412            serde_json::Value::Number(number) => number.as_f64(),
413            serde_json::Value::String(text) => text.trim().parse().ok(),
414            _ => None,
415        }
416    }
417
418    fn bool(&self, key: &str) -> Option<bool> {
419        self.get(key)?.as_bool()
420    }
421
422    fn obj(&self, key: &str) -> Option<&serde_json::Map<String, serde_json::Value>> {
423        self.get(key)?.as_object()
424    }
425
426    fn arr(&self, key: &str) -> &[serde_json::Value] {
427        self.get(key)
428            .and_then(serde_json::Value::as_array)
429            .map_or(&[], Vec::as_slice)
430    }
431
432    fn at(&self, pointer: &str) -> Option<&serde_json::Value> {
433        self.pointer(pointer).filter(|value| !value.is_null())
434    }
435
436    fn as_u64_lenient(&self) -> Option<u64> {
437        match self {
438            serde_json::Value::Number(number) => number.as_u64().or_else(|| {
439                number
440                    .as_f64()
441                    .filter(|float| *float >= 0.0 && float.fract() == 0.0)
442                    .map(|float| float as u64)
443            }),
444            serde_json::Value::String(text) => text.trim().parse().ok(),
445            _ => None,
446        }
447    }
448}