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    /// Binary.
11    pub binary: String,
12    /// Command-line arguments for this subcommand.
13    pub args: Vec<String>,
14}
15
16impl ErrorPropagator {
17    /// Create a new instance.
18    pub fn new(binary: impl Into<String>, args: Vec<String>) -> Self {
19        Self {
20            binary: binary.into(),
21            args,
22        }
23    }
24
25    /// Convert a non-zero exit into a descriptive AppError including stderr.
26    pub fn propagate(&self, output: &Output) -> Result<(), AppError> {
27        if output.status.success() {
28            return Ok(());
29        }
30        let stderr = String::from_utf8_lossy(&output.stderr);
31        let stdout = String::from_utf8_lossy(&output.stdout);
32        let code = output.status.code().unwrap_or(-1);
33        let mut msg = format!("{} exited with code {}", self.binary, code);
34        if !stderr.trim().is_empty() {
35            msg.push_str(&format!("\nstderr: {}", stderr.trim()));
36        }
37        if !stdout.trim().is_empty() {
38            msg.push_str(&format!("\nstdout: {}", stdout.trim()));
39        }
40        msg.push_str(&format!("\nargs: {}", self.args.join(" ")));
41        Err(AppError::Internal(anyhow::anyhow!(msg)))
42    }
43
44    /// Returns the parsed stdout if exit code is 0, else propagates.
45    pub fn require_success(&self, output: &Output) -> Result<String, AppError> {
46        self.propagate(output)?;
47        Ok(String::from_utf8_lossy(&output.stdout).into_owned())
48    }
49}