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