rmcp_openapi/
error.rs

1//! Error handling for the OpenAPI MCP server.
2//!
3//! This module provides structured error types that distinguish between validation errors
4//! (which return as MCP protocol errors) and execution errors (which appear in tool output schemas).
5//!
6//! # Error Categories
7//!
8//! ## Validation Errors (MCP Protocol Errors)
9//! These errors occur before tool execution and are returned as MCP protocol errors (Err(ErrorData)).
10//! They do NOT have JsonSchema derive to prevent them from appearing in tool output schemas.
11//!
12//! - **ToolNotFound**: Requested tool doesn't exist
13//! - **InvalidParameters**: Parameter validation failed (unknown names, missing required, constraint violations)
14//! - **RequestConstructionError**: Failed to construct the HTTP request
15//!
16//! ## Execution Errors (Tool Output Errors)
17//! These errors occur during tool execution and are returned as structured content in the tool response.
18//! They have JsonSchema derive so they can appear in tool output schemas.
19//!
20//! - **HttpError**: HTTP error response from the API (4xx, 5xx status codes)
21//! - **NetworkError**: Network/connection failures (timeout, DNS, connection refused)
22//! - **ResponseParsingError**: Failed to parse the response
23//!
24//! # Error Type Examples
25//!
26//! ## InvalidParameter (Validation Error)
27//! ```json
28//! {
29//!   "type": "invalid-parameter",
30//!   "parameter": "pet_id",
31//!   "suggestions": ["petId"],
32//!   "valid_parameters": ["petId", "status"]
33//! }
34//! ```
35//!
36//! ## ConstraintViolation (Validation Error)
37//! ```json
38//! {
39//!   "type": "constraint-violation",
40//!   "parameter": "age",
41//!   "message": "Parameter 'age' must be between 0 and 150",
42//!   "field_path": "age",
43//!   "actual_value": 200,
44//!   "expected_type": "integer",
45//!   "constraints": [
46//!     {"type": "minimum", "value": 0, "exclusive": false},
47//!     {"type": "maximum", "value": 150, "exclusive": false}
48//!   ]
49//! }
50//! ```
51//!
52//! ## HttpError (Execution Error)
53//! ```json
54//! {
55//!   "type": "http-error",
56//!   "status": 404,
57//!   "message": "Pet not found",
58//!   "details": {"error": "NOT_FOUND", "pet_id": 123}
59//! }
60//! ```
61//!
62//! ## NetworkError (Execution Error)
63//! ```json
64//! {
65//!   "type": "network-error",
66//!   "message": "Request timeout after 30 seconds",
67//!   "category": "timeout"
68//! }
69//! ```
70//!
71//! # Structured Error Responses
72//!
73//! For tools with output schemas, execution errors are wrapped in the standard response structure:
74//! ```json
75//! {
76//!   "status": 404,
77//!   "body": {
78//!     "error": {
79//!       "type": "http-error",
80//!       "status": 404,
81//!       "message": "Pet not found"
82//!     }
83//!   }
84//! }
85//! ```
86//!
87//! Validation errors are returned as MCP protocol errors:
88//! ```json
89//! {
90//!   "code": -32602,
91//!   "message": "Validation failed with 1 error",
92//!   "data": {
93//!     "type": "validation-errors",
94//!     "violations": [
95//!       {
96//!         "type": "invalid-parameter",
97//!         "parameter": "pet_id",
98//!         "suggestions": ["petId"],
99//!         "valid_parameters": ["petId", "status"]
100//!       }
101//!     ]
102//!   }
103//! }
104//! ```
105//!
106//! This consistent structure allows clients to:
107//! - Programmatically handle different error types
108//! - Provide helpful feedback to users
109//! - Automatically fix certain errors (e.g., typos in parameter names)
110//! - Retry requests with corrected parameters
111
112use rmcp::model::{ErrorCode, ErrorData};
113use schemars::JsonSchema;
114use serde::Serialize;
115use serde_json::{Value, json};
116use std::fmt;
117use thiserror::Error;
118
119/// Individual validation constraint that was violated
120#[derive(Debug, Serialize, JsonSchema)]
121#[serde(tag = "type", rename_all = "kebab-case")]
122pub enum ValidationConstraint {
123    /// Minimum value constraint (for numbers)
124    Minimum {
125        /// The minimum value
126        value: f64,
127        /// Whether the minimum is exclusive
128        exclusive: bool,
129    },
130    /// Maximum value constraint (for numbers)
131    Maximum {
132        /// The maximum value
133        value: f64,
134        /// Whether the maximum is exclusive
135        exclusive: bool,
136    },
137    /// Minimum length constraint (for strings/arrays)
138    MinLength {
139        /// The minimum length
140        value: usize,
141    },
142    /// Maximum length constraint (for strings/arrays)
143    MaxLength {
144        /// The maximum length
145        value: usize,
146    },
147    /// Pattern constraint (for strings)
148    Pattern {
149        /// The regex pattern that must be matched
150        pattern: String,
151    },
152    /// Enum values constraint
153    EnumValues {
154        /// The allowed enum values
155        values: Vec<Value>,
156    },
157    /// Format constraint (e.g., "date-time", "email", "uri")
158    Format {
159        /// The expected format
160        format: String,
161    },
162    /// Multiple of constraint (for numbers)
163    MultipleOf {
164        /// The value that the number must be a multiple of
165        value: f64,
166    },
167    /// Minimum number of items constraint (for arrays)
168    MinItems {
169        /// The minimum number of items
170        value: usize,
171    },
172    /// Maximum number of items constraint (for arrays)
173    MaxItems {
174        /// The maximum number of items
175        value: usize,
176    },
177    /// Unique items constraint (for arrays)
178    UniqueItems,
179    /// Minimum number of properties constraint (for objects)
180    MinProperties {
181        /// The minimum number of properties
182        value: usize,
183    },
184    /// Maximum number of properties constraint (for objects)
185    MaxProperties {
186        /// The maximum number of properties
187        value: usize,
188    },
189    /// Constant value constraint
190    ConstValue {
191        /// The exact value that must match
192        value: Value,
193    },
194    /// Required properties constraint (for objects)
195    Required {
196        /// The required property names
197        properties: Vec<String>,
198    },
199}
200
201/// Individual validation error types
202#[derive(Debug, Serialize, JsonSchema)]
203#[serde(tag = "type", rename_all = "kebab-case")]
204pub enum ValidationError {
205    /// Invalid parameter error with suggestions
206    InvalidParameter {
207        /// The parameter name that was invalid
208        parameter: String,
209        /// Suggested correct parameter names
210        suggestions: Vec<String>,
211        /// All valid parameter names for this tool
212        valid_parameters: Vec<String>,
213    },
214    /// Missing required parameter
215    MissingRequiredParameter {
216        /// Name of the missing parameter
217        parameter: String,
218        /// Description of the parameter from OpenAPI
219        #[serde(skip_serializing_if = "Option::is_none")]
220        description: Option<String>,
221        /// Expected type of the parameter
222        expected_type: String,
223    },
224    /// Constraint violation (e.g., type mismatches, pattern violations)
225    ConstraintViolation {
226        /// Name of the parameter that failed validation
227        parameter: String,
228        /// Description of what validation failed
229        message: String,
230        /// Path to the field that failed validation (e.g., "address.street")
231        #[serde(skip_serializing_if = "Option::is_none")]
232        field_path: Option<String>,
233        /// The actual value that failed validation
234        #[serde(skip_serializing_if = "Option::is_none")]
235        actual_value: Option<Box<Value>>,
236        /// Expected type or format
237        #[serde(skip_serializing_if = "Option::is_none")]
238        expected_type: Option<String>,
239        /// Specific constraints that were violated
240        #[serde(skip_serializing_if = "Vec::is_empty")]
241        constraints: Vec<ValidationConstraint>,
242    },
243}
244
245impl fmt::Display for ValidationError {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        match self {
248            ValidationError::InvalidParameter {
249                parameter,
250                suggestions,
251                ..
252            } => {
253                if suggestions.is_empty() {
254                    write!(f, "'{parameter}'")
255                } else {
256                    write!(f, "'{parameter}' (suggestions: {})", suggestions.join(", "))
257                }
258            }
259            ValidationError::MissingRequiredParameter {
260                parameter,
261                expected_type,
262                ..
263            } => {
264                write!(f, "'{parameter}' is required (expected: {expected_type})")
265            }
266            ValidationError::ConstraintViolation {
267                parameter, message, ..
268            } => {
269                write!(f, "'{parameter}': {message}")
270            }
271        }
272    }
273}
274
275/// Helper function to format multiple validation errors into a single message
276fn format_validation_errors(violations: &[ValidationError]) -> String {
277    match violations.len() {
278        0 => "Validation failed".to_string(),
279        1 => {
280            // For single error, we need to add context about what type of error it is
281            let error = &violations[0];
282            match error {
283                ValidationError::InvalidParameter { .. } => {
284                    format!("Validation failed - invalid parameter {error}")
285                }
286                ValidationError::MissingRequiredParameter { .. } => {
287                    format!("Validation failed - missing required parameter: {error}")
288                }
289                ValidationError::ConstraintViolation { .. } => {
290                    format!("Validation failed - parameter {error}")
291                }
292            }
293        }
294        _ => {
295            // For multiple errors, use the new format
296            let mut invalid_params = Vec::new();
297            let mut missing_params = Vec::new();
298            let mut constraint_violations = Vec::new();
299
300            // Group errors by type
301            for error in violations {
302                match error {
303                    ValidationError::InvalidParameter { .. } => {
304                        invalid_params.push(error.to_string());
305                    }
306                    ValidationError::MissingRequiredParameter { .. } => {
307                        missing_params.push(error.to_string());
308                    }
309                    ValidationError::ConstraintViolation { .. } => {
310                        constraint_violations.push(error.to_string());
311                    }
312                }
313            }
314
315            let mut parts = Vec::new();
316
317            // Format invalid parameters
318            if !invalid_params.is_empty() {
319                let params_str = invalid_params.join(", ");
320                parts.push(format!("invalid parameters: {params_str}"));
321            }
322
323            // Format missing parameters
324            if !missing_params.is_empty() {
325                let params_str = missing_params.join(", ");
326                parts.push(format!("missing parameters: {params_str}"));
327            }
328
329            // Format constraint violations
330            if !constraint_violations.is_empty() {
331                let violations_str = constraint_violations.join("; ");
332                parts.push(format!("constraint violations: {violations_str}"));
333            }
334
335            format!("Validation failed - {}", parts.join("; "))
336        }
337    }
338}
339
340#[derive(Debug, Error)]
341pub enum OpenApiError {
342    #[error("Environment variable error: {0}")]
343    EnvVar(#[from] std::env::VarError),
344    #[error("IO error: {0}")]
345    Io(#[from] std::io::Error),
346    #[error("OpenAPI spec error: {0}")]
347    Spec(String),
348    #[error("Tool generation error: {0}")]
349    ToolGeneration(String),
350    #[error("Invalid parameter location: {0}")]
351    InvalidParameterLocation(String),
352    #[error("Invalid URL: {0}")]
353    InvalidUrl(String),
354    #[error("File not found: {0}")]
355    FileNotFound(String),
356    #[error("MCP error: {0}")]
357    McpError(String),
358    #[error("Invalid path: {0}")]
359    InvalidPath(String),
360    #[error("Validation error: {0}")]
361    Validation(String),
362    #[error("HTTP error: {0}")]
363    Http(String),
364    #[error("HTTP request error: {0}")]
365    HttpRequest(#[from] reqwest::Error),
366    #[error("JSON error: {0}")]
367    Json(#[from] serde_json::Error),
368    #[error(transparent)]
369    ToolCall(#[from] ToolCallError),
370}
371
372impl From<ToolCallValidationError> for ErrorData {
373    fn from(err: ToolCallValidationError) -> Self {
374        match err {
375            ToolCallValidationError::ToolNotFound {
376                ref tool_name,
377                ref suggestions,
378            } => {
379                let data = if suggestions.is_empty() {
380                    None
381                } else {
382                    Some(json!({
383                        "suggestions": suggestions
384                    }))
385                };
386                ErrorData::new(
387                    ErrorCode(-32601),
388                    format!("Tool '{tool_name}' not found"),
389                    data,
390                )
391            }
392            ToolCallValidationError::InvalidParameters { ref violations } => {
393                // Include the full validation error details
394                let data = Some(json!({
395                    "type": "validation-errors",
396                    "violations": violations
397                }));
398                ErrorData::new(ErrorCode(-32602), err.to_string(), data)
399            }
400            ToolCallValidationError::RequestConstructionError { ref reason } => {
401                // Include construction error details
402                let data = Some(json!({
403                    "type": "request-construction-error",
404                    "reason": reason
405                }));
406                ErrorData::new(ErrorCode(-32602), err.to_string(), data)
407            }
408        }
409    }
410}
411
412impl From<ToolCallError> for ErrorData {
413    fn from(err: ToolCallError) -> Self {
414        match err {
415            ToolCallError::Validation(validation_err) => validation_err.into(),
416            ToolCallError::Execution(execution_err) => {
417                // Execution errors should not be converted to ErrorData
418                // They should be returned as CallToolResult with is_error: true
419                // But for backward compatibility, we'll convert them
420                match execution_err {
421                    ToolCallExecutionError::HttpError {
422                        status,
423                        ref message,
424                        ..
425                    } => {
426                        let data = Some(json!({
427                            "type": "http-error",
428                            "status": status,
429                            "message": message
430                        }));
431                        ErrorData::new(ErrorCode(-32000), execution_err.to_string(), data)
432                    }
433                    ToolCallExecutionError::NetworkError {
434                        ref message,
435                        ref category,
436                    } => {
437                        let data = Some(json!({
438                            "type": "network-error",
439                            "message": message,
440                            "category": category
441                        }));
442                        ErrorData::new(ErrorCode(-32000), execution_err.to_string(), data)
443                    }
444                    ToolCallExecutionError::ResponseParsingError { ref reason, .. } => {
445                        let data = Some(json!({
446                            "type": "response-parsing-error",
447                            "reason": reason
448                        }));
449                        ErrorData::new(ErrorCode(-32700), execution_err.to_string(), data)
450                    }
451                }
452            }
453        }
454    }
455}
456
457impl From<OpenApiError> for ErrorData {
458    fn from(err: OpenApiError) -> Self {
459        match err {
460            OpenApiError::Spec(msg) => ErrorData::new(
461                ErrorCode(-32700),
462                format!("OpenAPI spec error: {msg}"),
463                None,
464            ),
465            OpenApiError::Validation(msg) => {
466                ErrorData::new(ErrorCode(-32602), format!("Validation error: {msg}"), None)
467            }
468            OpenApiError::HttpRequest(e) => {
469                ErrorData::new(ErrorCode(-32000), format!("HTTP request failed: {e}"), None)
470            }
471            OpenApiError::Http(msg) => {
472                ErrorData::new(ErrorCode(-32000), format!("HTTP error: {msg}"), None)
473            }
474            OpenApiError::Json(e) => {
475                ErrorData::new(ErrorCode(-32700), format!("JSON parsing error: {e}"), None)
476            }
477            OpenApiError::ToolCall(e) => e.into(),
478            _ => ErrorData::new(ErrorCode(-32000), err.to_string(), None),
479        }
480    }
481}
482
483/// Error that can occur during tool execution
484#[derive(Debug, Error, Serialize)]
485#[serde(untagged)]
486pub enum ToolCallError {
487    /// Validation errors that occur before tool execution
488    #[error(transparent)]
489    Validation(#[from] ToolCallValidationError),
490
491    /// Execution errors that occur during tool execution
492    #[error(transparent)]
493    Execution(#[from] ToolCallExecutionError),
494}
495
496/// Error response structure for tool execution failures
497#[derive(Debug, Serialize, JsonSchema)]
498pub struct ErrorResponse {
499    /// Error information
500    pub error: ToolCallExecutionError,
501}
502
503/// Validation errors that occur before tool execution
504/// These return as Err(ErrorData) with MCP protocol error codes
505#[derive(Debug, Error, Serialize)]
506#[serde(tag = "type", rename_all = "kebab-case")]
507pub enum ToolCallValidationError {
508    /// Tool not found
509    #[error("Tool '{tool_name}' not found")]
510    #[serde(rename = "tool-not-found")]
511    ToolNotFound {
512        /// Name of the tool that was not found
513        tool_name: String,
514        /// Suggested tool names based on similarity
515        suggestions: Vec<String>,
516    },
517
518    /// Invalid parameters (unknown names, missing required, constraints)
519    #[error("{}", format_validation_errors(violations))]
520    #[serde(rename = "validation-errors")]
521    InvalidParameters {
522        /// List of validation errors
523        violations: Vec<ValidationError>,
524    },
525
526    /// Request construction failed (JSON serialization for body)
527    #[error("Failed to construct request: {reason}")]
528    #[serde(rename = "request-construction-error")]
529    RequestConstructionError {
530        /// Description of the construction failure
531        reason: String,
532    },
533}
534
535/// Execution errors that occur during tool execution
536/// These return as Ok(CallToolResult { is_error: true })
537#[derive(Debug, Error, Serialize, JsonSchema)]
538#[serde(tag = "type", rename_all = "kebab-case")]
539#[schemars(tag = "type", rename_all = "kebab-case")]
540pub enum ToolCallExecutionError {
541    /// HTTP error response from the API
542    #[error("HTTP {status} error: {message}")]
543    #[serde(rename = "http-error")]
544    HttpError {
545        /// HTTP status code
546        status: u16,
547        /// Error message or response body
548        message: String,
549        /// Optional structured error details from API
550        #[serde(skip_serializing_if = "Option::is_none")]
551        details: Option<Value>,
552    },
553
554    /// Network/connection failures
555    #[error("Network error: {message}")]
556    #[serde(rename = "network-error")]
557    NetworkError {
558        /// Description of the network failure
559        message: String,
560        /// Error category for better handling
561        category: NetworkErrorCategory,
562    },
563
564    /// Response parsing failed
565    #[error("Failed to parse response: {reason}")]
566    #[serde(rename = "response-parsing-error")]
567    ResponseParsingError {
568        /// Description of the parsing failure
569        reason: String,
570        /// Raw response body for debugging
571        #[serde(skip_serializing_if = "Option::is_none")]
572        raw_response: Option<String>,
573    },
574}
575
576/// Network error categories for better error handling
577#[derive(Debug, Serialize, JsonSchema)]
578#[serde(rename_all = "kebab-case")]
579pub enum NetworkErrorCategory {
580    /// Request timeout
581    Timeout,
582    /// Connection error (DNS, refused, unreachable)
583    Connect,
584    /// Request construction/sending error
585    Request,
586    /// Response body error
587    Body,
588    /// Response decoding error
589    Decode,
590    /// Other network errors
591    Other,
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597    use insta::assert_json_snapshot;
598    use serde_json::json;
599
600    #[test]
601    fn test_tool_call_error_serialization_with_details() {
602        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
603            violations: vec![ValidationError::InvalidParameter {
604                parameter: "pet_id".to_string(),
605                suggestions: vec!["petId".to_string()],
606                valid_parameters: vec!["petId".to_string(), "timeout_seconds".to_string()],
607            }],
608        });
609
610        let serialized = serde_json::to_value(&error).unwrap();
611        assert_json_snapshot!(serialized);
612    }
613
614    #[test]
615    fn test_tool_call_error_serialization_without_details() {
616        let error = ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
617            tool_name: "unknownTool".to_string(),
618            suggestions: vec![],
619        });
620
621        let serialized = serde_json::to_value(&error).unwrap();
622        assert_json_snapshot!(serialized);
623    }
624
625    #[test]
626    fn test_tool_call_error_serialization_with_suggestions() {
627        let error = ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
628            tool_name: "getPetByID".to_string(),
629            suggestions: vec!["getPetById".to_string(), "getPetsByStatus".to_string()],
630        });
631
632        let serialized = serde_json::to_value(&error).unwrap();
633        assert_json_snapshot!(serialized);
634    }
635
636    #[test]
637    fn test_tool_call_error_multiple_suggestions() {
638        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
639            violations: vec![ValidationError::InvalidParameter {
640                parameter: "pet_i".to_string(),
641                suggestions: vec!["petId".to_string(), "petInfo".to_string()],
642                valid_parameters: vec![
643                    "petId".to_string(),
644                    "petInfo".to_string(),
645                    "timeout".to_string(),
646                ],
647            }],
648        });
649
650        let serialized = serde_json::to_value(&error).unwrap();
651        assert_json_snapshot!(serialized);
652    }
653
654    #[test]
655    fn test_tool_call_error_no_suggestions() {
656        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
657            violations: vec![ValidationError::InvalidParameter {
658                parameter: "completely_wrong".to_string(),
659                suggestions: vec![],
660                valid_parameters: vec!["petId".to_string(), "timeout".to_string()],
661            }],
662        });
663
664        let serialized = serde_json::to_value(&error).unwrap();
665        assert_json_snapshot!(serialized);
666    }
667
668    #[test]
669    fn test_tool_call_error_validation() {
670        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
671            violations: vec![ValidationError::MissingRequiredParameter {
672                parameter: "field".to_string(),
673                description: Some("Missing required field".to_string()),
674                expected_type: "string".to_string(),
675            }],
676        });
677        let serialized = serde_json::to_value(&error).unwrap();
678        assert_json_snapshot!(serialized);
679    }
680
681    #[test]
682    fn test_tool_call_error_validation_detailed() {
683        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
684            violations: vec![ValidationError::ConstraintViolation {
685                parameter: "age".to_string(),
686                message: "Parameter 'age' must be between 0 and 150".to_string(),
687                field_path: Some("age".to_string()),
688                actual_value: Some(Box::new(json!(200))),
689                expected_type: Some("integer".to_string()),
690                constraints: vec![
691                    ValidationConstraint::Minimum {
692                        value: 0.0,
693                        exclusive: false,
694                    },
695                    ValidationConstraint::Maximum {
696                        value: 150.0,
697                        exclusive: false,
698                    },
699                ],
700            }],
701        });
702
703        let serialized = serde_json::to_value(&error).unwrap();
704        assert_json_snapshot!(serialized);
705    }
706
707    #[test]
708    fn test_tool_call_error_validation_enum() {
709        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
710            violations: vec![ValidationError::ConstraintViolation {
711                parameter: "status".to_string(),
712                message: "Parameter 'status' must be one of: available, pending, sold".to_string(),
713                field_path: Some("status".to_string()),
714                actual_value: Some(Box::new(json!("unknown"))),
715                expected_type: Some("string".to_string()),
716                constraints: vec![ValidationConstraint::EnumValues {
717                    values: vec![json!("available"), json!("pending"), json!("sold")],
718                }],
719            }],
720        });
721
722        let serialized = serde_json::to_value(&error).unwrap();
723        assert_json_snapshot!(serialized);
724    }
725
726    #[test]
727    fn test_tool_call_error_validation_format() {
728        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
729            violations: vec![ValidationError::ConstraintViolation {
730                parameter: "email".to_string(),
731                message: "Invalid email format".to_string(),
732                field_path: Some("contact.email".to_string()),
733                actual_value: Some(Box::new(json!("not-an-email"))),
734                expected_type: Some("string".to_string()),
735                constraints: vec![ValidationConstraint::Format {
736                    format: "email".to_string(),
737                }],
738            }],
739        });
740
741        let serialized = serde_json::to_value(&error).unwrap();
742        assert_json_snapshot!(serialized);
743    }
744
745    #[test]
746    fn test_tool_call_error_http_error() {
747        let error = ToolCallError::Execution(ToolCallExecutionError::HttpError {
748            status: 404,
749            message: "Not found".to_string(),
750            details: None,
751        });
752        let serialized = serde_json::to_value(&error).unwrap();
753        assert_json_snapshot!(serialized);
754    }
755
756    #[test]
757    fn test_tool_call_error_http_request() {
758        let error = ToolCallError::Execution(ToolCallExecutionError::NetworkError {
759            message: "Connection timeout".to_string(),
760            category: NetworkErrorCategory::Timeout,
761        });
762        let serialized = serde_json::to_value(&error).unwrap();
763        assert_json_snapshot!(serialized);
764    }
765
766    #[test]
767    fn test_tool_call_error_json() {
768        let error = ToolCallError::Execution(ToolCallExecutionError::ResponseParsingError {
769            reason: "Invalid JSON".to_string(),
770            raw_response: None,
771        });
772        let serialized = serde_json::to_value(&error).unwrap();
773        assert_json_snapshot!(serialized);
774    }
775
776    #[test]
777    fn test_tool_call_error_request_construction() {
778        let error = ToolCallError::Validation(ToolCallValidationError::RequestConstructionError {
779            reason: "Invalid parameter location: body".to_string(),
780        });
781        let serialized = serde_json::to_value(&error).unwrap();
782        assert_json_snapshot!(serialized);
783    }
784
785    #[test]
786    fn test_error_response_serialization() {
787        let error = ToolCallExecutionError::HttpError {
788            status: 400,
789            message: "Bad Request".to_string(),
790            details: Some(json!({
791                "error": "Invalid parameter",
792                "parameter": "test_param"
793            })),
794        };
795
796        let response = ErrorResponse { error };
797        let serialized = serde_json::to_value(&response).unwrap();
798        assert_json_snapshot!(serialized);
799    }
800
801    #[test]
802    fn test_tool_call_error_validation_multiple_of() {
803        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
804            violations: vec![ValidationError::ConstraintViolation {
805                parameter: "price".to_string(),
806                message: "10.5 is not a multiple of 3".to_string(),
807                field_path: Some("price".to_string()),
808                actual_value: Some(Box::new(json!(10.5))),
809                expected_type: Some("number".to_string()),
810                constraints: vec![ValidationConstraint::MultipleOf { value: 3.0 }],
811            }],
812        });
813
814        let serialized = serde_json::to_value(&error).unwrap();
815        assert_json_snapshot!(serialized);
816    }
817
818    #[test]
819    fn test_tool_call_error_validation_min_items() {
820        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
821            violations: vec![ValidationError::ConstraintViolation {
822                parameter: "tags".to_string(),
823                message: "Array has 1 items but minimum is 2".to_string(),
824                field_path: Some("tags".to_string()),
825                actual_value: Some(Box::new(json!(["tag1"]))),
826                expected_type: Some("array".to_string()),
827                constraints: vec![ValidationConstraint::MinItems { value: 2 }],
828            }],
829        });
830
831        let serialized = serde_json::to_value(&error).unwrap();
832        assert_json_snapshot!(serialized);
833    }
834
835    #[test]
836    fn test_tool_call_error_validation_max_items() {
837        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
838            violations: vec![ValidationError::ConstraintViolation {
839                parameter: "categories".to_string(),
840                message: "Array has 4 items but maximum is 3".to_string(),
841                field_path: Some("categories".to_string()),
842                actual_value: Some(Box::new(json!(["a", "b", "c", "d"]))),
843                expected_type: Some("array".to_string()),
844                constraints: vec![ValidationConstraint::MaxItems { value: 3 }],
845            }],
846        });
847
848        let serialized = serde_json::to_value(&error).unwrap();
849        assert_json_snapshot!(serialized);
850    }
851
852    #[test]
853    fn test_tool_call_error_validation_unique_items() {
854        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
855            violations: vec![ValidationError::ConstraintViolation {
856                parameter: "numbers".to_string(),
857                message: "Array items [1, 2, 2, 3] are not unique".to_string(),
858                field_path: Some("numbers".to_string()),
859                actual_value: Some(Box::new(json!([1, 2, 2, 3]))),
860                expected_type: Some("array".to_string()),
861                constraints: vec![ValidationConstraint::UniqueItems],
862            }],
863        });
864
865        let serialized = serde_json::to_value(&error).unwrap();
866        assert_json_snapshot!(serialized);
867    }
868
869    #[test]
870    fn test_tool_call_error_validation_min_properties() {
871        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
872            violations: vec![ValidationError::ConstraintViolation {
873                parameter: "metadata".to_string(),
874                message: "Object has 2 properties but minimum is 3".to_string(),
875                field_path: Some("metadata".to_string()),
876                actual_value: Some(Box::new(json!({"name": "test", "version": "1.0"}))),
877                expected_type: Some("object".to_string()),
878                constraints: vec![ValidationConstraint::MinProperties { value: 3 }],
879            }],
880        });
881
882        let serialized = serde_json::to_value(&error).unwrap();
883        assert_json_snapshot!(serialized);
884    }
885
886    #[test]
887    fn test_tool_call_error_validation_max_properties() {
888        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
889            violations: vec![ValidationError::ConstraintViolation {
890                parameter: "config".to_string(),
891                message: "Object has 3 properties but maximum is 2".to_string(),
892                field_path: Some("config".to_string()),
893                actual_value: Some(Box::new(json!({"a": 1, "b": 2, "c": 3}))),
894                expected_type: Some("object".to_string()),
895                constraints: vec![ValidationConstraint::MaxProperties { value: 2 }],
896            }],
897        });
898
899        let serialized = serde_json::to_value(&error).unwrap();
900        assert_json_snapshot!(serialized);
901    }
902
903    #[test]
904    fn test_tool_call_error_validation_const() {
905        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
906            violations: vec![ValidationError::ConstraintViolation {
907                parameter: "environment".to_string(),
908                message: r#""staging" is not equal to const "production""#.to_string(),
909                field_path: Some("environment".to_string()),
910                actual_value: Some(Box::new(json!("staging"))),
911                expected_type: Some("string".to_string()),
912                constraints: vec![ValidationConstraint::ConstValue {
913                    value: json!("production"),
914                }],
915            }],
916        });
917
918        let serialized = serde_json::to_value(&error).unwrap();
919        assert_json_snapshot!(serialized);
920    }
921
922    #[test]
923    fn test_error_data_conversion_preserves_details() {
924        // Test InvalidParameter error conversion
925        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
926            violations: vec![ValidationError::InvalidParameter {
927                parameter: "page".to_string(),
928                suggestions: vec!["page_number".to_string()],
929                valid_parameters: vec!["page_number".to_string(), "page_size".to_string()],
930            }],
931        });
932
933        let error_data: ErrorData = error.into();
934        let error_json = serde_json::to_value(&error_data).unwrap();
935
936        // Check that error details are preserved
937        assert!(error_json["data"].is_object(), "Should have data field");
938        assert_eq!(
939            error_json["data"]["type"].as_str(),
940            Some("validation-errors"),
941            "Should have validation-errors type"
942        );
943
944        // Test Network error conversion
945        let network_error = ToolCallError::Execution(ToolCallExecutionError::NetworkError {
946            message: "SSL/TLS connection failed - certificate verification error".to_string(),
947            category: NetworkErrorCategory::Connect,
948        });
949
950        let error_data: ErrorData = network_error.into();
951        let error_json = serde_json::to_value(&error_data).unwrap();
952
953        assert!(error_json["data"].is_object(), "Should have data field");
954        assert_eq!(
955            error_json["data"]["type"].as_str(),
956            Some("network-error"),
957            "Should have network-error type"
958        );
959        assert!(
960            error_json["data"]["message"]
961                .as_str()
962                .unwrap()
963                .contains("SSL/TLS"),
964            "Should preserve error message"
965        );
966    }
967}