Skip to main content

sqlite_graphrag/spawn/
error_propagator.rs

1//! Error propagator for subprocess invocations (v1.0.75 — G22 P16/P17)
2
3use crate::errors::AppError;
4use std::process::Output;
5
6/// Captures the exit code, stdout and stderr of a subprocess and converts it
7/// into a structured `AppError`. The previous behaviour in
8/// `src/commands/codex_spawn.rs` swallowed stderr; this propagates it.
9pub struct ErrorPropagator {
10    pub binary: String,
11    pub args: Vec<String>,
12}
13
14impl ErrorPropagator {
15    pub fn new(binary: impl Into<String>, args: Vec<String>) -> Self {
16        Self {
17            binary: binary.into(),
18            args,
19        }
20    }
21
22    /// Convert a non-zero exit into a descriptive AppError including stderr.
23    pub fn propagate(&self, output: &Output) -> Result<(), AppError> {
24        if output.status.success() {
25            return Ok(());
26        }
27        let stderr = String::from_utf8_lossy(&output.stderr);
28        let stdout = String::from_utf8_lossy(&output.stdout);
29        let code = output.status.code().unwrap_or(-1);
30        let mut msg = format!("{} exited with code {}", self.binary, code);
31        if !stderr.trim().is_empty() {
32            msg.push_str(&format!("\nstderr: {}", stderr.trim()));
33        }
34        if !stdout.trim().is_empty() {
35            msg.push_str(&format!("\nstdout: {}", stdout.trim()));
36        }
37        msg.push_str(&format!("\nargs: {}", self.args.join(" ")));
38        Err(AppError::Internal(anyhow::anyhow!(msg)))
39    }
40
41    /// Returns the parsed stdout if exit code is 0, else propagates.
42    pub fn require_success(&self, output: &Output) -> Result<String, AppError> {
43        self.propagate(output)?;
44        Ok(String::from_utf8_lossy(&output.stdout).into_owned())
45    }
46}