Skip to main content

statespace_tool_runtime/
protocol.rs

1//! Tool execution request/response protocol.
2
3use crate::validate_env_map;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Deserialize)]
8pub struct ActionRequest {
9    pub command: Vec<String>,
10    #[serde(default)]
11    pub args: HashMap<String, String>,
12    #[serde(default)]
13    pub env: HashMap<String, String>,
14}
15
16impl ActionRequest {
17    /// # Errors
18    ///
19    /// Returns an error when the command is empty.
20    pub fn validate(&self) -> Result<(), String> {
21        if self.command.is_empty() {
22            return Err("command cannot be empty".to_string());
23        }
24        validate_env_map(&self.env).map_err(|e| e.to_string())?;
25        Ok(())
26    }
27}
28
29#[derive(Debug, Serialize)]
30pub struct ActionResponse {
31    pub stdout: String,
32    pub stderr: String,
33    pub returncode: i32,
34}
35
36impl ActionResponse {
37    #[must_use]
38    pub const fn success(output: String) -> Self {
39        Self {
40            stdout: output,
41            stderr: String::new(),
42            returncode: 0,
43        }
44    }
45
46    #[must_use]
47    pub const fn error(message: String) -> Self {
48        Self {
49            stdout: String::new(),
50            stderr: message,
51            returncode: 1,
52        }
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn test_action_request_validation() {
62        let valid = ActionRequest {
63            command: vec!["ls".to_string()],
64            args: HashMap::new(),
65            env: HashMap::new(),
66        };
67        assert!(valid.validate().is_ok());
68
69        let invalid_command = ActionRequest {
70            command: vec![],
71            args: HashMap::new(),
72            env: HashMap::new(),
73        };
74        assert!(invalid_command.validate().is_err());
75
76        let invalid_env = ActionRequest {
77            command: vec!["ls".to_string()],
78            args: HashMap::new(),
79            env: HashMap::from([("USER-ID".to_string(), "42".to_string())]),
80        };
81        assert!(invalid_env.validate().is_err());
82    }
83
84    #[test]
85    fn test_action_response() {
86        let success = ActionResponse::success("file1.md\nfile2.md".to_string());
87        assert_eq!(success.returncode, 0);
88        assert_eq!(success.stdout, "file1.md\nfile2.md");
89        assert_eq!(success.stderr, "");
90
91        let error = ActionResponse::error("command not found".to_string());
92        assert_eq!(error.returncode, 1);
93        assert_eq!(error.stdout, "");
94        assert_eq!(error.stderr, "command not found");
95    }
96}