statespace_tool_runtime/
protocol.rs1use crate::validate_env_map;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct ActionRequest {
10 pub command: Vec<String>,
11 #[serde(default)]
12 pub env: HashMap<String, String>,
13}
14
15impl ActionRequest {
16 pub fn validate(&self) -> Result<(), String> {
20 if self.command.is_empty() {
21 return Err("Command cannot be empty".to_string());
22 }
23 validate_env_map(&self.env).map_err(|e| e.to_string())?;
24 Ok(())
25 }
26}
27
28#[derive(Debug, Serialize)]
29pub struct ActionResponse {
30 pub stdout: String,
31 pub stderr: String,
32 pub returncode: i32,
33}
34
35impl ActionResponse {
36 #[must_use]
37 pub const fn success(output: String) -> Self {
38 Self {
39 stdout: output,
40 stderr: String::new(),
41 returncode: 0,
42 }
43 }
44
45 #[must_use]
46 pub const fn error(message: String) -> Self {
47 Self {
48 stdout: String::new(),
49 stderr: message,
50 returncode: 1,
51 }
52 }
53}
54
55#[derive(Debug, Serialize)]
57pub struct SuccessResponse {
58 pub data: ActionResponse,
59}
60
61impl SuccessResponse {
62 #[must_use]
63 pub const fn ok(data: ActionResponse) -> Self {
64 Self { data }
65 }
66}
67
68#[derive(Debug, Serialize)]
70pub struct ErrorResponse {
71 pub error: String,
72}
73
74impl ErrorResponse {
75 #[must_use]
76 pub fn new(message: impl Into<String>) -> Self {
77 Self {
78 error: format!("{}. See / for API instructions.", message.into()),
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn test_action_request_validation() {
89 let valid = ActionRequest {
90 command: vec!["ls".to_string()],
91
92 env: HashMap::new(),
93 };
94 assert!(valid.validate().is_ok());
95
96 let invalid_command = ActionRequest {
97 command: vec![],
98
99 env: HashMap::new(),
100 };
101 assert!(invalid_command.validate().is_err());
102
103 let invalid_env = ActionRequest {
104 command: vec!["ls".to_string()],
105
106 env: HashMap::from([("USER-ID".to_string(), "42".to_string())]),
107 };
108 assert!(invalid_env.validate().is_err());
109 }
110
111 #[test]
112 fn test_action_response() {
113 let success = ActionResponse::success("file1.md\nfile2.md".to_string());
114 assert_eq!(success.returncode, 0);
115 assert_eq!(success.stdout, "file1.md\nfile2.md");
116 assert_eq!(success.stderr, "");
117
118 let error = ActionResponse::error("command not found".to_string());
119 assert_eq!(error.returncode, 1);
120 assert_eq!(error.stdout, "");
121 assert_eq!(error.stderr, "command not found");
122 }
123
124 #[test]
125 fn test_error_response_points_to_root_api_guide() {
126 let error = ErrorResponse::new("File not found");
127
128 assert_eq!(error.error, "File not found. See / for API instructions.");
129 }
130}