icarus_core/
response.rs

1//! Response types for idiomatic tool return values
2//!
3//! These types provide a bridge between Rust's type system and MCP's JSON requirements.
4
5use serde::{Deserialize, Serialize};
6use serde_json::{json, Value};
7
8/// A successful tool response with optional data
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ToolSuccess<T = Value> {
11    /// The main data payload
12    #[serde(flatten)]
13    pub data: T,
14
15    /// Optional status message
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub message: Option<String>,
18}
19
20impl<T> ToolSuccess<T> {
21    /// Create a new success response with data
22    pub fn new(data: T) -> Self {
23        Self {
24            data,
25            message: None,
26        }
27    }
28
29    /// Add a message to the response
30    pub fn with_message(mut self, message: impl Into<String>) -> Self {
31        self.message = Some(message.into());
32        self
33    }
34}
35
36impl ToolSuccess<Value> {
37    /// Create an empty success response
38    pub fn empty() -> Self {
39        Self {
40            data: json!({}),
41            message: None,
42        }
43    }
44}
45
46/// A simple status response for operations without data
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ToolStatus {
49    /// Whether the operation succeeded
50    pub success: bool,
51
52    /// Status message
53    pub message: String,
54
55    /// Optional additional details
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub details: Option<Value>,
58}
59
60impl ToolStatus {
61    /// Create a success status
62    pub fn success(message: impl Into<String>) -> Self {
63        Self {
64            success: true,
65            message: message.into(),
66            details: None,
67        }
68    }
69
70    /// Create an error status
71    pub fn error(message: impl Into<String>) -> Self {
72        Self {
73            success: false,
74            message: message.into(),
75            details: None,
76        }
77    }
78
79    /// Add details to the status
80    pub fn with_details(mut self, details: Value) -> Self {
81        self.details = Some(details);
82        self
83    }
84}
85
86/// Helper function to create a successful response with data
87pub fn tool_success<T: Serialize>(data: T) -> Result<Value, crate::error::ToolError> {
88    serde_json::to_value(data)
89        .map_err(|e| crate::error::ToolError::internal(format!("Serialization failed: {}", e)))
90}
91
92/// Helper function to create a simple success status
93pub fn tool_ok(message: impl Into<String>) -> Result<Value, crate::error::ToolError> {
94    Ok(json!({
95        "success": true,
96        "message": message.into()
97    }))
98}
99
100// Removed tool_error - use Err(ToolError::...) instead for proper error handling
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use serde_json::json;
106
107    #[test]
108    fn test_tool_success_new() {
109        let data = json!({"count": 42});
110        let success = ToolSuccess::new(data.clone());
111
112        assert_eq!(success.data, data);
113        assert_eq!(success.message, None);
114    }
115
116    #[test]
117    fn test_tool_success_with_message() {
118        let data = json!({"result": "ok"});
119        let success = ToolSuccess::new(data.clone()).with_message("Operation completed");
120
121        assert_eq!(success.data, data);
122        assert_eq!(success.message, Some("Operation completed".to_string()));
123    }
124
125    #[test]
126    fn test_tool_success_empty() {
127        let success = ToolSuccess::empty();
128        assert_eq!(success.data, json!({}));
129        assert_eq!(success.message, None);
130    }
131
132    #[test]
133    fn test_tool_success_serialization() {
134        let success = ToolSuccess::new(json!({"value": 100})).with_message("Done");
135
136        let serialized = serde_json::to_value(&success).unwrap();
137        assert_eq!(serialized["value"], 100);
138        assert_eq!(serialized["message"], "Done");
139    }
140
141    #[test]
142    fn test_tool_success_no_message_serialization() {
143        let success = ToolSuccess::new(json!({"key": "value"}));
144        let serialized = serde_json::to_value(&success).unwrap();
145
146        assert_eq!(serialized["key"], "value");
147        assert!(!serialized.as_object().unwrap().contains_key("message"));
148    }
149
150    #[test]
151    fn test_tool_status_success() {
152        let status = ToolStatus::success("All good");
153
154        assert!(status.success);
155        assert_eq!(status.message, "All good");
156        assert_eq!(status.details, None);
157    }
158
159    #[test]
160    fn test_tool_status_error() {
161        let status = ToolStatus::error("Something went wrong");
162
163        assert!(!status.success);
164        assert_eq!(status.message, "Something went wrong");
165        assert_eq!(status.details, None);
166    }
167
168    #[test]
169    fn test_tool_status_with_details() {
170        let details = json!({"code": 404, "reason": "not found"});
171        let status = ToolStatus::error("Failed").with_details(details.clone());
172
173        assert!(!status.success);
174        assert_eq!(status.message, "Failed");
175        assert_eq!(status.details, Some(details));
176    }
177
178    #[test]
179    fn test_tool_status_serialization() {
180        let status = ToolStatus::success("Complete").with_details(json!({"items": 5}));
181
182        let serialized = serde_json::to_value(&status).unwrap();
183        assert_eq!(serialized["success"], true);
184        assert_eq!(serialized["message"], "Complete");
185        assert_eq!(serialized["details"]["items"], 5);
186    }
187
188    #[test]
189    fn test_tool_success_helper() {
190        #[derive(Serialize)]
191        struct TestData {
192            name: String,
193            age: u32,
194        }
195
196        let data = TestData {
197            name: "Alice".to_string(),
198            age: 30,
199        };
200
201        let result = tool_success(data).unwrap();
202        assert_eq!(result["name"], "Alice");
203        assert_eq!(result["age"], 30);
204    }
205
206    #[test]
207    fn test_tool_ok_helper() {
208        let result = tool_ok("Task completed").unwrap();
209        assert_eq!(result["success"], true);
210        assert_eq!(result["message"], "Task completed");
211    }
212
213    #[test]
214    fn test_tool_success_with_struct() {
215        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
216        struct CustomData {
217            id: u64,
218            value: String,
219        }
220
221        let data = CustomData {
222            id: 123,
223            value: "test".to_string(),
224        };
225
226        let success = ToolSuccess::new(data.clone());
227        let serialized = serde_json::to_string(&success).unwrap();
228        let deserialized: ToolSuccess<CustomData> = serde_json::from_str(&serialized).unwrap();
229
230        assert_eq!(deserialized.data, data);
231    }
232}