prism_mcp_rs/protocol/
error_helpers.rs

1//! JSON-RPC error response helper functions
2//!
3//! This module provides convenience methods for creating JSON-RPC error responses
4//! according to the MCP protocol specification.
5
6use serde_json::Value;
7
8use crate::protocol::{
9    error_codes::*,
10    types::{
11        ErrorObject, JsonRpcError, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest,
12        JsonRpcResponse, RequestId, JSONRPC_VERSION,
13    },
14};
15
16impl JsonRpcError {
17    /// Create a new JSON-RPC error with the given parameters
18    ///
19    /// # Examples
20    ///
21    /// ```rust,no_run
22    /// use prism_mcp_rs::protocol::JsonRpcError;
23    /// use serde_json::json;
24    ///
25    /// let error = JsonRpcError::new(
26    ///     json!("req-123"),
27    ///     -32601,
28    ///     "Method not found"
29    /// );
30    /// ```
31    pub fn new<S: Into<String>>(id: RequestId, code: i32, message: S) -> Self {
32        Self {
33            jsonrpc: JSONRPC_VERSION.to_string(),
34            id,
35            error: ErrorObject {
36                code,
37                message: message.into(),
38                data: None,
39            },
40        }
41    }
42
43    /// Create a new JSON-RPC error with additional data
44    ///
45    /// # Examples
46    ///
47    /// ```rust,no_run
48    /// use prism_mcp_rs::protocol::JsonRpcError;
49    /// use serde_json::json;
50    ///
51    /// let error = JsonRpcError::with_data(
52    ///     json!("req-123"),
53    ///     -32602,
54    ///     "Invalid params",
55    ///     Some(json!({"expected": "string", "got": "number"}))
56    /// );
57    /// ```
58    pub fn with_data<S: Into<String>>(
59        id: RequestId,
60        code: i32,
61        message: S,
62        data: Option<Value>,
63    ) -> Self {
64        Self {
65            jsonrpc: JSONRPC_VERSION.to_string(),
66            id,
67            error: ErrorObject {
68                code,
69                message: message.into(),
70                data,
71            },
72        }
73    }
74
75    /// Create a "Parse error" response (-32700)
76    ///
77    /// Invalid JSON was received by the server
78    pub fn parse_error(id: RequestId) -> Self {
79        Self::new(id, PARSE_ERROR, "Parse error")
80    }
81
82    /// Create an "Invalid Request" response (-32600)
83    ///
84    /// The JSON sent is not a valid Request object
85    pub fn invalid_request(id: RequestId) -> Self {
86        Self::new(id, INVALID_REQUEST, "Invalid Request")
87    }
88
89    /// Create a "Method not found" response (-32601)
90    ///
91    /// The method does not exist or is not available
92    pub fn method_not_found(id: RequestId) -> Self {
93        Self::new(id, METHOD_NOT_FOUND, "Method not found")
94    }
95
96    /// Create a "Method not found" response with the method name
97    pub fn method_not_found_with_name<S: Into<String>>(id: RequestId, method: S) -> Self {
98        let method = method.into();
99        Self::with_data(
100            id,
101            METHOD_NOT_FOUND,
102            format!("Method '{}' not found", method),
103            Some(serde_json::json!({ "method": method })),
104        )
105    }
106
107    /// Create an "Invalid params" response (-32602)
108    ///
109    /// Invalid method parameter(s)
110    pub fn invalid_params(id: RequestId) -> Self {
111        Self::new(id, INVALID_PARAMS, "Invalid params")
112    }
113
114    /// Create an "Invalid params" response with details
115    pub fn invalid_params_with_message<S: Into<String>>(id: RequestId, details: S) -> Self {
116        Self::new(id, INVALID_PARAMS, details)
117    }
118
119    /// Create an "Internal error" response (-32603)
120    ///
121    /// Internal JSON-RPC error
122    pub fn internal_error(id: RequestId) -> Self {
123        Self::new(id, INTERNAL_ERROR, "Internal error")
124    }
125
126    /// Create an "Internal error" response with details
127    pub fn internal_error_with_message<S: Into<String>>(id: RequestId, details: S) -> Self {
128        Self::new(id, INTERNAL_ERROR, details)
129    }
130
131    /// Create a "Tool not found" error (-32000)
132    ///
133    /// MCP-specific: The requested tool does not exist
134    pub fn tool_not_found<S: Into<String>>(id: RequestId, tool_name: S) -> Self {
135        let name = tool_name.into();
136        Self::with_data(
137            id,
138            TOOL_NOT_FOUND,
139            format!("Tool '{}' not found", name),
140            Some(serde_json::json!({ "tool": name })),
141        )
142    }
143
144    /// Create a "Resource not found" error (-32001)
145    ///
146    /// MCP-specific: The requested resource does not exist
147    pub fn resource_not_found<S: Into<String>>(id: RequestId, uri: S) -> Self {
148        let uri = uri.into();
149        Self::with_data(
150            id,
151            RESOURCE_NOT_FOUND,
152            format!("Resource '{}' not found", uri),
153            Some(serde_json::json!({ "uri": uri })),
154        )
155    }
156
157    /// Create a "Prompt not found" error (-32002)
158    ///
159    /// MCP-specific: The requested prompt does not exist
160    pub fn prompt_not_found<S: Into<String>>(id: RequestId, prompt_name: S) -> Self {
161        let name = prompt_name.into();
162        Self::with_data(
163            id,
164            PROMPT_NOT_FOUND,
165            format!("Prompt '{}' not found", name),
166            Some(serde_json::json!({ "prompt": name })),
167        )
168    }
169
170    /// Create a custom error with a specific code and message
171    pub fn custom<S: Into<String>>(id: RequestId, code: i32, message: S) -> Self {
172        Self::new(id, code, message)
173    }
174
175    /// Create a custom error with a specific code, message, and data
176    pub fn custom_with_data<S: Into<String>>(
177        id: RequestId,
178        code: i32,
179        message: S,
180        data: Value,
181    ) -> Self {
182        Self::with_data(id, code, message, Some(data))
183    }
184}
185
186// Conversion from JsonRpcError to JsonRpcMessage for easier use
187impl From<JsonRpcError> for JsonRpcMessage {
188    fn from(error: JsonRpcError) -> Self {
189        JsonRpcMessage::Error(error)
190    }
191}
192
193// Helper trait for converting errors to JsonRpcMessage
194pub trait IntoJsonRpcMessage {
195    fn into_message(self) -> JsonRpcMessage;
196}
197
198impl IntoJsonRpcMessage for JsonRpcError {
199    fn into_message(self) -> JsonRpcMessage {
200        JsonRpcMessage::Error(self)
201    }
202}
203
204// Additional type conversions for ergonomic API
205impl From<JsonRpcResponse> for JsonRpcMessage {
206    fn from(response: JsonRpcResponse) -> Self {
207        JsonRpcMessage::Response(response)
208    }
209}
210
211impl From<JsonRpcNotification> for JsonRpcMessage {
212    fn from(notification: JsonRpcNotification) -> Self {
213        JsonRpcMessage::Notification(notification)
214    }
215}
216
217impl From<JsonRpcRequest> for JsonRpcMessage {
218    fn from(request: JsonRpcRequest) -> Self {
219        JsonRpcMessage::Request(request)
220    }
221}
222
223impl TryFrom<JsonRpcMessage> for JsonRpcRequest {
224    type Error = crate::core::error::McpError;
225
226    fn try_from(msg: JsonRpcMessage) -> Result<Self, Self::Error> {
227        match msg {
228            JsonRpcMessage::Request(req) => Ok(req),
229            _ => Err(crate::core::error::McpError::Protocol(
230                "Not a JSON-RPC request".to_string(),
231            )),
232        }
233    }
234}
235
236impl TryFrom<JsonRpcMessage> for JsonRpcResponse {
237    type Error = crate::core::error::McpError;
238
239    fn try_from(msg: JsonRpcMessage) -> Result<Self, Self::Error> {
240        match msg {
241            JsonRpcMessage::Response(resp) => Ok(resp),
242            _ => Err(crate::core::error::McpError::Protocol(
243                "Not a JSON-RPC response".to_string(),
244            )),
245        }
246    }
247}
248
249impl TryFrom<JsonRpcMessage> for JsonRpcError {
250    type Error = crate::core::error::McpError;
251
252    fn try_from(msg: JsonRpcMessage) -> Result<Self, Self::Error> {
253        match msg {
254            JsonRpcMessage::Error(err) => Ok(err),
255            _ => Err(crate::core::error::McpError::Protocol(
256                "Not a JSON-RPC error".to_string(),
257            )),
258        }
259    }
260}
261
262impl TryFrom<JsonRpcMessage> for JsonRpcNotification {
263    type Error = crate::core::error::McpError;
264
265    fn try_from(msg: JsonRpcMessage) -> Result<Self, Self::Error> {
266        match msg {
267            JsonRpcMessage::Notification(notif) => Ok(notif),
268            _ => Err(crate::core::error::McpError::Protocol(
269                "Not a JSON-RPC notification".to_string(),
270            )),
271        }
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use serde_json::json;
279
280    #[test]
281    fn test_error_creation() {
282        let id = json!("test-123");
283
284        // Test basic error creation
285        let error = JsonRpcError::new(id.clone(), -32601, "Method not found");
286        assert_eq!(error.jsonrpc, "2.0");
287        assert_eq!(error.id, id);
288        assert_eq!(error.error.code, -32601);
289        assert_eq!(error.error.message, "Method not found");
290        assert!(error.error.data.is_none());
291
292        // Test error with data
293        let data = json!({"detail": "extra info"});
294        let error_with_data =
295            JsonRpcError::with_data(id.clone(), -32602, "Invalid params", Some(data.clone()));
296        assert_eq!(error_with_data.error.data, Some(data));
297    }
298
299    #[test]
300    fn test_standard_errors() {
301        let id = json!(123);
302
303        // Parse error
304        let parse_err = JsonRpcError::parse_error(id.clone());
305        assert_eq!(parse_err.error.code, PARSE_ERROR);
306        assert_eq!(parse_err.error.message, "Parse error");
307
308        // Invalid request
309        let invalid_req = JsonRpcError::invalid_request(id.clone());
310        assert_eq!(invalid_req.error.code, INVALID_REQUEST);
311        assert_eq!(invalid_req.error.message, "Invalid Request");
312
313        // Method not found
314        let method_err = JsonRpcError::method_not_found(id.clone());
315        assert_eq!(method_err.error.code, METHOD_NOT_FOUND);
316        assert_eq!(method_err.error.message, "Method not found");
317
318        // Method not found with name
319        let method_err_with_name =
320            JsonRpcError::method_not_found_with_name(id.clone(), "test_method");
321        assert_eq!(method_err_with_name.error.code, METHOD_NOT_FOUND);
322        assert!(method_err_with_name.error.message.contains("test_method"));
323        assert!(method_err_with_name.error.data.is_some());
324
325        // Invalid params
326        let params_err = JsonRpcError::invalid_params(id.clone());
327        assert_eq!(params_err.error.code, INVALID_PARAMS);
328        assert_eq!(params_err.error.message, "Invalid params");
329
330        // Invalid params with message
331        let params_err_msg =
332            JsonRpcError::invalid_params_with_message(id.clone(), "Missing required field 'name'");
333        assert_eq!(params_err_msg.error.code, INVALID_PARAMS);
334        assert_eq!(
335            params_err_msg.error.message,
336            "Missing required field 'name'"
337        );
338
339        // Internal error
340        let internal_err = JsonRpcError::internal_error(id.clone());
341        assert_eq!(internal_err.error.code, INTERNAL_ERROR);
342        assert_eq!(internal_err.error.message, "Internal error");
343
344        // Internal error with message
345        let internal_err_msg =
346            JsonRpcError::internal_error_with_message(id.clone(), "Database connection failed");
347        assert_eq!(internal_err_msg.error.code, INTERNAL_ERROR);
348        assert_eq!(internal_err_msg.error.message, "Database connection failed");
349    }
350
351    #[test]
352    fn test_mcp_specific_errors() {
353        let id = json!("req-456");
354
355        // Tool not found
356        let tool_err = JsonRpcError::tool_not_found(id.clone(), "my_tool");
357        assert_eq!(tool_err.error.code, TOOL_NOT_FOUND);
358        assert!(tool_err.error.message.contains("my_tool"));
359        assert_eq!(tool_err.error.data, Some(json!({"tool": "my_tool"})));
360
361        // Resource not found
362        let resource_err = JsonRpcError::resource_not_found(id.clone(), "file:///test.txt");
363        assert_eq!(resource_err.error.code, RESOURCE_NOT_FOUND);
364        assert!(resource_err.error.message.contains("file:///test.txt"));
365        assert_eq!(
366            resource_err.error.data,
367            Some(json!({"uri": "file:///test.txt"}))
368        );
369
370        // Prompt not found
371        let prompt_err = JsonRpcError::prompt_not_found(id.clone(), "test_prompt");
372        assert_eq!(prompt_err.error.code, PROMPT_NOT_FOUND);
373        assert!(prompt_err.error.message.contains("test_prompt"));
374        assert_eq!(
375            prompt_err.error.data,
376            Some(json!({"prompt": "test_prompt"}))
377        );
378    }
379
380    #[test]
381    fn test_custom_errors() {
382        let id = json!(789);
383
384        // Custom error without data
385        let custom = JsonRpcError::custom(id.clone(), -32099, "Custom error message");
386        assert_eq!(custom.error.code, -32099);
387        assert_eq!(custom.error.message, "Custom error message");
388        assert!(custom.error.data.is_none());
389
390        // Custom error with data
391        let custom_data = json!({"field": "value", "count": 42});
392        let custom_with_data = JsonRpcError::custom_with_data(
393            id.clone(),
394            -32098,
395            "Another custom error",
396            custom_data.clone(),
397        );
398        assert_eq!(custom_with_data.error.code, -32098);
399        assert_eq!(custom_with_data.error.message, "Another custom error");
400        assert_eq!(custom_with_data.error.data, Some(custom_data));
401    }
402
403    #[test]
404    fn test_conversion_to_message() {
405        let id = json!("msg-001");
406        let error = JsonRpcError::method_not_found(id);
407
408        // Test From trait
409        let message: JsonRpcMessage = error.clone().into();
410        assert!(matches!(message, JsonRpcMessage::Error(_)));
411
412        // Test IntoJsonRpcMessage trait
413        let message2 = error.into_message();
414        assert!(matches!(message2, JsonRpcMessage::Error(_)));
415    }
416
417    #[test]
418    fn test_error_serialization() {
419        let id = json!(42);
420        let error = JsonRpcError::invalid_params_with_message(id, "Field 'name' is required");
421
422        let serialized = serde_json::to_value(&error).unwrap();
423        assert_eq!(serialized["jsonrpc"], "2.0");
424        assert_eq!(serialized["id"], 42);
425        assert_eq!(serialized["error"]["code"], INVALID_PARAMS);
426        assert_eq!(serialized["error"]["message"], "Field 'name' is required");
427
428        // Test round-trip
429        let deserialized: JsonRpcError = serde_json::from_value(serialized).unwrap();
430        assert_eq!(deserialized, error);
431    }
432}