goosedump 0.12.43

Browse, search, compact, and learn from coding-agent sessions
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) Jarkko Sakkinen 2026

//! JSON-array parsing for wrapped model output.

use serde::de::DeserializeOwned;

#[derive(Debug)]
pub(super) enum ArrayParseError {
    Invalid(serde_json::Error),
    NotSingle,
}

pub(super) fn parse_single_array<T>(answer: &str) -> Result<T, ArrayParseError>
where
    T: DeserializeOwned,
{
    let mut parsed = None;
    let mut parse_error = None;
    let mut offset = 0;
    while let Some(relative_start) = answer[offset..].find('[') {
        let start = offset + relative_start;
        let Some(length) = json_array_length(&answer[start..]) else {
            offset = start + 1;
            continue;
        };
        let json = &answer[start..start + length];
        match serde_json::from_str(json) {
            Ok(value) => {
                if parsed.is_some() {
                    return Err(ArrayParseError::NotSingle);
                }
                parsed = Some(value);
            }
            Err(error) => {
                if serde_json::from_str::<serde_json::Value>(json).is_ok() {
                    if parse_error.is_none() {
                        parse_error = Some(error);
                    }
                } else {
                    offset = start + 1;
                    continue;
                }
            }
        }
        offset = start + length;
    }
    parsed.ok_or_else(|| parse_error.map_or(ArrayParseError::NotSingle, ArrayParseError::Invalid))
}

fn json_array_length(candidate: &str) -> Option<usize> {
    let mut depth = 0;
    let mut in_string = false;
    let mut escaped = false;
    for (index, character) in candidate.char_indices() {
        if in_string {
            if escaped {
                escaped = false;
            } else if character == '\\' {
                escaped = true;
            } else if character == '"' {
                in_string = false;
            }
            continue;
        }
        match character {
            '"' => in_string = true,
            '[' => depth += 1,
            ']' => {
                depth -= 1;
                if depth == 0 {
                    return Some(index + character.len_utf8());
                }
            }
            _ => {}
        }
    }
    None
}