capsula_command_context/
lib.rs1mod 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}