rsigma_runtime/sources/
command.rs1use std::time::Instant;
4
5use rsigma_eval::pipeline::sources::{DataFormat, ExtractExpr};
6
7use super::extract::apply_extract;
8use super::file::parse_data;
9use super::{ResolvedValue, SourceError, SourceErrorKind};
10
11pub async fn resolve_command(
13 command: &[String],
14 format: DataFormat,
15 extract_expr: Option<&ExtractExpr>,
16) -> Result<ResolvedValue, SourceError> {
17 if command.is_empty() {
18 return Err(SourceError {
19 source_id: String::new(),
20 kind: SourceErrorKind::Fetch("command is empty".into()),
21 });
22 }
23
24 let output = tokio::process::Command::new(&command[0])
25 .args(&command[1..])
26 .stdout(std::process::Stdio::piped())
27 .stderr(std::process::Stdio::piped())
28 .spawn()
29 .map_err(|e| SourceError {
30 source_id: String::new(),
31 kind: SourceErrorKind::Fetch(format!("failed to spawn '{}': {e}", command[0])),
32 })?
33 .wait_with_output()
34 .await
35 .map_err(|e| SourceError {
36 source_id: String::new(),
37 kind: SourceErrorKind::Fetch(format!("command execution failed: {e}")),
38 })?;
39
40 if !output.status.success() {
41 let stderr = String::from_utf8_lossy(&output.stderr);
42 return Err(SourceError {
43 source_id: String::new(),
44 kind: SourceErrorKind::Fetch(format!(
45 "command exited with {}: {}",
46 output.status,
47 stderr.trim()
48 )),
49 });
50 }
51
52 let stdout = String::from_utf8(output.stdout).map_err(|e| SourceError {
53 source_id: String::new(),
54 kind: SourceErrorKind::Parse(format!("command output is not valid UTF-8: {e}")),
55 })?;
56
57 let parsed = parse_data(&stdout, format)?;
58
59 let data = if let Some(expr) = extract_expr {
60 apply_extract(&parsed, expr)?
61 } else {
62 parsed
63 };
64
65 Ok(ResolvedValue {
66 data,
67 resolved_at: Instant::now(),
68 from_cache: false,
69 })
70}