capsula_command_context/
lib.rs

1mod config;
2mod error;
3
4use crate::config::CommandContextFactory;
5use crate::error::CommandContextError;
6use capsula_core::captured::Captured;
7use capsula_core::context::{Context, ContextFactory, RuntimeParams};
8use capsula_core::error::CoreResult;
9
10pub const KEY: &str = "command";
11
12#[derive(Debug)]
13pub struct CommandContext {
14    pub command: Vec<String>,
15    pub abort_on_failure: bool,
16}
17
18#[derive(Debug)]
19pub struct CommandCaptured {
20    pub command: Vec<String>,
21    pub stdout: String,
22    pub stderr: String,
23    pub status: i32,
24    pub abort_requested: bool,
25}
26
27impl Context for CommandContext {
28    type Output = CommandCaptured;
29
30    fn run(&self, _params: &RuntimeParams) -> CoreResult<Self::Output> {
31        use std::process::Command;
32
33        if self.command.is_empty() {
34            return Err(CommandContextError::EmptyCommand.into());
35        }
36
37        let mut cmd = Command::new(&self.command[0]);
38        if self.command.len() > 1 {
39            cmd.args(&self.command[1..]);
40        }
41
42        let output = cmd.output().map_err(|source| CommandContextError::ExecutionFailed {
43            command: self.command.join(" "),
44            source,
45        })?;
46
47        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
48        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
49        let status = output.status.code().unwrap_or(-1);
50
51        Ok(CommandCaptured {
52            command: self.command.clone(),
53            stdout,
54            stderr,
55            status,
56            abort_requested: self.abort_on_failure && status != 0,
57        })
58    }
59}
60
61impl Captured for CommandCaptured {
62    fn to_json(&self) -> serde_json::Value {
63        serde_json::json!({
64            "type": KEY,
65            "command": self.command,
66            "stdout": self.stdout,
67            "stderr": self.stderr,
68            "status": self.status,
69            "abort_requested": self.abort_requested,
70        })
71    }
72
73    fn abort_requested(&self) -> bool {
74        self.abort_requested
75    }
76}
77
78pub fn create_factory() -> Box<dyn ContextFactory> {
79    Box::new(CommandContextFactory)
80}