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/// CLI-specific errors for command-line argument parsing and validation
341#[derive(Debug, Error)]
342pub enum CliError {
343    #[error("Invalid header format in '{header}': expected 'name: value' format")]
344    InvalidHeaderFormat { header: String },
345
346    #[error("Invalid header name in '{header}': {source}")]
347    InvalidHeaderName {
348        header: String,
349        #[source]
350        source: http::header::InvalidHeaderName,
351    },
352
353    #[error("Invalid header value in '{header}': {source}")]
354    InvalidHeaderValue {
355        header: String,
356        #[source]
357        source: http::header::InvalidHeaderValue,
358    },
359}
360
361#[derive(Debug, Error)]
362pub enum OpenApiError {
363    #[error("CLI error: {0}")]
364    Cli(#[from] CliError),
365    #[error("Environment variable error: {0}")]
366    EnvVar(#[from] std::env::VarError),
367    #[error("IO error: {0}")]
368    Io(#[from] std::io::Error),
369    #[error("OpenAPI spec error: {0}")]
370    Spec(String),
371    #[error("Tool generation error: {0}")]
372    ToolGeneration(String),
373    #[error("Invalid parameter location: {0}")]
374    InvalidParameterLocation(String),
375    #[error("Invalid URL: {0}")]
376    InvalidUrl(String),
377    #[error("File not found: {0}")]
378    FileNotFound(String),
379    #[error("MCP error: {0}")]
380    McpError(String),
381    #[error("Invalid path: {0}")]
382    InvalidPath(String),
383    #[error("Validation error: {0}")]
384    Validation(String),
385    #[error("HTTP error: {0}")]
386    Http(String),
387    #[error("HTTP request error: {0}")]
388    HttpRequest(#[from] reqwest::Error),
389    #[error("JSON error: {0}")]
390    Json(#[from] serde_json::Error),
391    #[error(transparent)]
392    ToolCall(#[from] ToolCallError),
393}
394
395impl From<ToolCallValidationError> for ErrorData {
396    fn from(err: ToolCallValidationError) -> Self {
397        match err {
398            ToolCallValidationError::ToolNotFound {
399                ref tool_name,
400                ref suggestions,
401            } => {
402                let data = if suggestions.is_empty() {
403                    None
404                } else {
405                    Some(json!({
406                        "suggestions": suggestions
407                    }))
408                };
409                ErrorData::new(
410                    ErrorCode(-32601),
411                    format!("Tool '{tool_name}' not found"),
412                    data,
413                )
414            }
415            ToolCallValidationError::InvalidParameters { ref violations } => {
416                // Include the full validation error details
417                let data = Some(json!({
418                    "type": "validation-errors",
419                    "violations": violations
420                }));
421                ErrorData::new(ErrorCode(-32602), err.to_string(), data)
422            }
423            ToolCallValidationError::RequestConstructionError { ref reason } => {
424                // Include construction error details
425                let data = Some(json!({
426                    "type": "request-construction-error",
427                    "reason": reason
428                }));
429                ErrorData::new(ErrorCode(-32602), err.to_string(), data)
430            }
431        }
432    }
433}
434
435impl From<ToolCallError> for ErrorData {
436    fn from(err: ToolCallError) -> Self {
437        match err {
438            ToolCallError::Validation(validation_err) => validation_err.into(),
439            ToolCallError::Execution(execution_err) => {
440                // Execution errors should not be converted to ErrorData
441                // They should be returned as CallToolResult with is_error: true
442                // But for backward compatibility, we'll convert them
443                match execution_err {
444                    ToolCallExecutionError::HttpError {
445                        status,
446                        ref message,
447                        ..
448                    } => {
449                        let data = Some(json!({
450                            "type": "http-error",
451                            "status": status,
452                            "message": message
453                        }));
454                        ErrorData::new(ErrorCode(-32000), execution_err.to_string(), data)
455                    }
456                    ToolCallExecutionError::NetworkError {
457                        ref message,
458                        ref category,
459                    } => {
460                        let data = Some(json!({
461                            "type": "network-error",
462                            "message": message,
463                            "category": category
464                        }));
465                        ErrorData::new(ErrorCode(-32000), execution_err.to_string(), data)
466                    }
467                    ToolCallExecutionError::ResponseParsingError { ref reason, .. } => {
468                        let data = Some(json!({
469                            "type": "response-parsing-error",
470                            "reason": reason
471                        }));
472                        ErrorData::new(ErrorCode(-32700), execution_err.to_string(), data)
473                    }
474                }
475            }
476        }
477    }
478}
479
480impl From<OpenApiError> for ErrorData {
481    fn from(err: OpenApiError) -> Self {
482        match err {
483            OpenApiError::Spec(msg) => ErrorData::new(
484                ErrorCode(-32700),
485                format!("OpenAPI spec error: {msg}"),
486                None,
487            ),
488            OpenApiError::Validation(msg) => {
489                ErrorData::new(ErrorCode(-32602), format!("Validation error: {msg}"), None)
490            }
491            OpenApiError::HttpRequest(e) => {
492                ErrorData::new(ErrorCode(-32000), format!("HTTP request failed: {e}"), None)
493            }
494            OpenApiError::Http(msg) => {
495                ErrorData::new(ErrorCode(-32000), format!("HTTP error: {msg}"), None)
496            }
497            OpenApiError::Json(e) => {
498                ErrorData::new(ErrorCode(-32700), format!("JSON parsing error: {e}"), None)
499            }
500            OpenApiError::ToolCall(e) => e.into(),
501            _ => ErrorData::new(ErrorCode(-32000), err.to_string(), None),
502        }
503    }
504}
505
506/// Error that can occur during tool execution
507#[derive(Debug, Error, Serialize)]
508#[serde(untagged)]
509pub enum ToolCallError {
510    /// Validation errors that occur before tool execution
511    #[error(transparent)]
512    Validation(#[from] ToolCallValidationError),
513
514    /// Execution errors that occur during tool execution
515    #[error(transparent)]
516    Execution(#[from] ToolCallExecutionError),
517}
518
519/// Error response structure for tool execution failures
520#[derive(Debug, Serialize, JsonSchema)]
521pub struct ErrorResponse {
522    /// Error information
523    pub error: ToolCallExecutionError,
524}
525
526/// Validation errors that occur before tool execution
527/// These return as Err(ErrorData) with MCP protocol error codes
528#[derive(Debug, Error, Serialize)]
529#[serde(tag = "type", rename_all = "kebab-case")]
530pub enum ToolCallValidationError {
531    /// Tool not found
532    #[error("Tool '{tool_name}' not found")]
533    #[serde(rename = "tool-not-found")]
534    ToolNotFound {
535        /// Name of the tool that was not found
536        tool_name: String,
537        /// Suggested tool names based on similarity
538        suggestions: Vec<String>,
539    },
540
541    /// Invalid parameters (unknown names, missing required, constraints)
542    #[error("{}", format_validation_errors(violations))]
543    #[serde(rename = "validation-errors")]
544    InvalidParameters {
545        /// List of validation errors
546        violations: Vec<ValidationError>,
547    },
548
549    /// Request construction failed (JSON serialization for body)
550    #[error("Failed to construct request: {reason}")]
551    #[serde(rename = "request-construction-error")]
552    RequestConstructionError {
553        /// Description of the construction failure
554        reason: String,
555    },
556}
557
558/// Execution errors that occur during tool execution
559/// These return as Ok(CallToolResult { is_error: true })
560#[derive(Debug, Error, Serialize, JsonSchema)]
561#[serde(tag = "type", rename_all = "kebab-case")]
562#[schemars(tag = "type", rename_all = "kebab-case")]
563pub enum ToolCallExecutionError {
564    /// HTTP error response from the API
565    #[error("HTTP {status} error: {message}")]
566    #[serde(rename = "http-error")]
567    HttpError {
568        /// HTTP status code
569        status: u16,
570        /// Error message or response body
571        message: String,
572        /// Optional structured error details from API
573        #[serde(skip_serializing_if = "Option::is_none")]
574        details: Option<Value>,
575    },
576
577    /// Network/connection failures
578    #[error("Network error: {message}")]
579    #[serde(rename = "network-error")]
580    NetworkError {
581        /// Description of the network failure
582        message: String,
583        /// Error category for better handling
584        category: NetworkErrorCategory,
585    },
586
587    /// Response parsing failed
588    #[error("Failed to parse response: {reason}")]
589    #[serde(rename = "response-parsing-error")]
590    ResponseParsingError {
591        /// Description of the parsing failure
592        reason: String,
593        /// Raw response body for debugging
594        #[serde(skip_serializing_if = "Option::is_none")]
595        raw_response: Option<String>,
596    },
597}
598
599/// Network error categories for better error handling
600#[derive(Debug, Serialize, JsonSchema)]
601#[serde(rename_all = "kebab-case")]
602pub enum NetworkErrorCategory {
603    /// Request timeout
604    Timeout,
605    /// Connection error (DNS, refused, unreachable)
606    Connect,
607    /// Request construction/sending error
608    Request,
609    /// Response body error
610    Body,
611    /// Response decoding error
612    Decode,
613    /// Other network errors
614    Other,
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620    use insta::assert_json_snapshot;
621    use serde_json::json;
622
623    #[test]
624    fn test_tool_call_error_serialization_with_details() {
625        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
626            violations: vec![ValidationError::InvalidParameter {
627                parameter: "pet_id".to_string(),
628                suggestions: vec!["petId".to_string()],
629                valid_parameters: vec!["petId".to_string(), "timeout_seconds".to_string()],
630            }],
631        });
632
633        let serialized = serde_json::to_value(&error).unwrap();
634        assert_json_snapshot!(serialized);
635    }
636
637    #[test]
638    fn test_tool_call_error_serialization_without_details() {
639        let error = ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
640            tool_name: "unknownTool".to_string(),
641            suggestions: vec![],
642        });
643
644        let serialized = serde_json::to_value(&error).unwrap();
645        assert_json_snapshot!(serialized);
646    }
647
648    #[test]
649    fn test_tool_call_error_serialization_with_suggestions() {
650        let error = ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
651            tool_name: "getPetByID".to_string(),
652            suggestions: vec!["getPetById".to_string(), "getPetsByStatus".to_string()],
653        });
654
655        let serialized = serde_json::to_value(&error).unwrap();
656        assert_json_snapshot!(serialized);
657    }
658
659    #[test]
660    fn test_tool_call_error_multiple_suggestions() {
661        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
662            violations: vec![ValidationError::InvalidParameter {
663                parameter: "pet_i".to_string(),
664                suggestions: vec!["petId".to_string(), "petInfo".to_string()],
665                valid_parameters: vec![
666                    "petId".to_string(),
667                    "petInfo".to_string(),
668                    "timeout".to_string(),
669                ],
670            }],
671        });
672
673        let serialized = serde_json::to_value(&error).unwrap();
674        assert_json_snapshot!(serialized);
675    }
676
677    #[test]
678    fn test_tool_call_error_no_suggestions() {
679        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
680            violations: vec![ValidationError::InvalidParameter {
681                parameter: "completely_wrong".to_string(),
682                suggestions: vec![],
683                valid_parameters: vec!["petId".to_string(), "timeout".to_string()],
684            }],
685        });
686
687        let serialized = serde_json::to_value(&error).unwrap();
688        assert_json_snapshot!(serialized);
689    }
690
691    #[test]
692    fn test_tool_call_error_validation() {
693        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
694            violations: vec![ValidationError::MissingRequiredParameter {
695                parameter: "field".to_string(),
696                description: Some("Missing required field".to_string()),
697                expected_type: "string".to_string(),
698            }],
699        });
700        let serialized = serde_json::to_value(&error).unwrap();
701        assert_json_snapshot!(serialized);
702    }
703
704    #[test]
705    fn test_tool_call_error_validation_detailed() {
706        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
707            violations: vec![ValidationError::ConstraintViolation {
708                parameter: "age".to_string(),
709                message: "Parameter 'age' must be between 0 and 150".to_string(),
710                field_path: Some("age".to_string()),
711                actual_value: Some(Box::new(json!(200))),
712                expected_type: Some("integer".to_string()),
713                constraints: vec![
714                    ValidationConstraint::Minimum {
715                        value: 0.0,
716                        exclusive: false,
717                    },
718                    ValidationConstraint::Maximum {
719                        value: 150.0,
720                        exclusive: false,
721                    },
722                ],
723            }],
724        });
725
726        let serialized = serde_json::to_value(&error).unwrap();
727        assert_json_snapshot!(serialized);
728    }
729
730    #[test]
731    fn test_tool_call_error_validation_enum() {
732        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
733            violations: vec![ValidationError::ConstraintViolation {
734                parameter: "status".to_string(),
735                message: "Parameter 'status' must be one of: available, pending, sold".to_string(),
736                field_path: Some("status".to_string()),
737                actual_value: Some(Box::new(json!("unknown"))),
738                expected_type: Some("string".to_string()),
739                constraints: vec![ValidationConstraint::EnumValues {
740                    values: vec![json!("available"), json!("pending"), json!("sold")],
741                }],
742            }],
743        });
744
745        let serialized = serde_json::to_value(&error).unwrap();
746        assert_json_snapshot!(serialized);
747    }
748
749    #[test]
750    fn test_tool_call_error_validation_format() {
751        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
752            violations: vec![ValidationError::ConstraintViolation {
753                parameter: "email".to_string(),
754                message: "Invalid email format".to_string(),
755                field_path: Some("contact.email".to_string()),
756                actual_value: Some(Box::new(json!("not-an-email"))),
757                expected_type: Some("string".to_string()),
758                constraints: vec![ValidationConstraint::Format {
759                    format: "email".to_string(),
760                }],
761            }],
762        });
763
764        let serialized = serde_json::to_value(&error).unwrap();
765        assert_json_snapshot!(serialized);
766    }
767
768    #[test]
769    fn test_tool_call_error_http_error() {
770        let error = ToolCallError::Execution(ToolCallExecutionError::HttpError {
771            status: 404,
772            message: "Not found".to_string(),
773            details: None,
774        });
775        let serialized = serde_json::to_value(&error).unwrap();
776        assert_json_snapshot!(serialized);
777    }
778
779    #[test]
780    fn test_tool_call_error_http_request() {
781        let error = ToolCallError::Execution(ToolCallExecutionError::NetworkError {
782            message: "Connection timeout".to_string(),
783            category: NetworkErrorCategory::Timeout,
784        });
785        let serialized = serde_json::to_value(&error).unwrap();
786        assert_json_snapshot!(serialized);
787    }
788
789    #[test]
790    fn test_tool_call_error_json() {
791        let error = ToolCallError::Execution(ToolCallExecutionError::ResponseParsingError {
792            reason: "Invalid JSON".to_string(),
793            raw_response: None,
794        });
795        let serialized = serde_json::to_value(&error).unwrap();
796        assert_json_snapshot!(serialized);
797    }
798
799    #[test]
800    fn test_tool_call_error_request_construction() {
801        let error = ToolCallError::Validation(ToolCallValidationError::RequestConstructionError {
802            reason: "Invalid parameter location: body".to_string(),
803        });
804        let serialized = serde_json::to_value(&error).unwrap();
805        assert_json_snapshot!(serialized);
806    }
807
808    #[test]
809    fn test_error_response_serialization() {
810        let error = ToolCallExecutionError::HttpError {
811            status: 400,
812            message: "Bad Request".to_string(),
813            details: Some(json!({
814                "error": "Invalid parameter",
815                "parameter": "test_param"
816            })),
817        };
818
819        let response = ErrorResponse { error };
820        let serialized = serde_json::to_value(&response).unwrap();
821        assert_json_snapshot!(serialized);
822    }
823
824    #[test]
825    fn test_tool_call_error_validation_multiple_of() {
826        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
827            violations: vec![ValidationError::ConstraintViolation {
828                parameter: "price".to_string(),
829                message: "10.5 is not a multiple of 3".to_string(),
830                field_path: Some("price".to_string()),
831                actual_value: Some(Box::new(json!(10.5))),
832                expected_type: Some("number".to_string()),
833                constraints: vec![ValidationConstraint::MultipleOf { value: 3.0 }],
834            }],
835        });
836
837        let serialized = serde_json::to_value(&error).unwrap();
838        assert_json_snapshot!(serialized);
839    }
840
841    #[test]
842    fn test_tool_call_error_validation_min_items() {
843        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
844            violations: vec![ValidationError::ConstraintViolation {
845                parameter: "tags".to_string(),
846                message: "Array has 1 items but minimum is 2".to_string(),
847                field_path: Some("tags".to_string()),
848                actual_value: Some(Box::new(json!(["tag1"]))),
849                expected_type: Some("array".to_string()),
850                constraints: vec![ValidationConstraint::MinItems { value: 2 }],
851            }],
852        });
853
854        let serialized = serde_json::to_value(&error).unwrap();
855        assert_json_snapshot!(serialized);
856    }
857
858    #[test]
859    fn test_tool_call_error_validation_max_items() {
860        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
861            violations: vec![ValidationError::ConstraintViolation {
862                parameter: "categories".to_string(),
863                message: "Array has 4 items but maximum is 3".to_string(),
864                field_path: Some("categories".to_string()),
865                actual_value: Some(Box::new(json!(["a", "b", "c", "d"]))),
866                expected_type: Some("array".to_string()),
867                constraints: vec![ValidationConstraint::MaxItems { value: 3 }],
868            }],
869        });
870
871        let serialized = serde_json::to_value(&error).unwrap();
872        assert_json_snapshot!(serialized);
873    }
874
875    #[test]
876    fn test_tool_call_error_validation_unique_items() {
877        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
878            violations: vec![ValidationError::ConstraintViolation {
879                parameter: "numbers".to_string(),
880                message: "Array items [1, 2, 2, 3] are not unique".to_string(),
881                field_path: Some("numbers".to_string()),
882                actual_value: Some(Box::new(json!([1, 2, 2, 3]))),
883                expected_type: Some("array".to_string()),
884                constraints: vec![ValidationConstraint::UniqueItems],
885            }],
886        });
887
888        let serialized = serde_json::to_value(&error).unwrap();
889        assert_json_snapshot!(serialized);
890    }
891
892    #[test]
893    fn test_tool_call_error_validation_min_properties() {
894        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
895            violations: vec![ValidationError::ConstraintViolation {
896                parameter: "metadata".to_string(),
897                message: "Object has 2 properties but minimum is 3".to_string(),
898                field_path: Some("metadata".to_string()),
899                actual_value: Some(Box::new(json!({"name": "test", "version": "1.0"}))),
900                expected_type: Some("object".to_string()),
901                constraints: vec![ValidationConstraint::MinProperties { value: 3 }],
902            }],
903        });
904
905        let serialized = serde_json::to_value(&error).unwrap();
906        assert_json_snapshot!(serialized);
907    }
908
909    #[test]
910    fn test_tool_call_error_validation_max_properties() {
911        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
912            violations: vec![ValidationError::ConstraintViolation {
913                parameter: "config".to_string(),
914                message: "Object has 3 properties but maximum is 2".to_string(),
915                field_path: Some("config".to_string()),
916                actual_value: Some(Box::new(json!({"a": 1, "b": 2, "c": 3}))),
917                expected_type: Some("object".to_string()),
918                constraints: vec![ValidationConstraint::MaxProperties { value: 2 }],
919            }],
920        });
921
922        let serialized = serde_json::to_value(&error).unwrap();
923        assert_json_snapshot!(serialized);
924    }
925
926    #[test]
927    fn test_tool_call_error_validation_const() {
928        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
929            violations: vec![ValidationError::ConstraintViolation {
930                parameter: "environment".to_string(),
931                message: r#""staging" is not equal to const "production""#.to_string(),
932                field_path: Some("environment".to_string()),
933                actual_value: Some(Box::new(json!("staging"))),
934                expected_type: Some("string".to_string()),
935                constraints: vec![ValidationConstraint::ConstValue {
936                    value: json!("production"),
937                }],
938            }],
939        });
940
941        let serialized = serde_json::to_value(&error).unwrap();
942        assert_json_snapshot!(serialized);
943    }
944
945    #[test]
946    fn test_error_data_conversion_preserves_details() {
947        // Test InvalidParameter error conversion
948        let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
949            violations: vec![ValidationError::InvalidParameter {
950                parameter: "page".to_string(),
951                suggestions: vec!["page_number".to_string()],
952                valid_parameters: vec!["page_number".to_string(), "page_size".to_string()],
953            }],
954        });
955
956        let error_data: ErrorData = error.into();
957        let error_json = serde_json::to_value(&error_data).unwrap();
958
959        // Check that error details are preserved
960        assert!(error_json["data"].is_object(), "Should have data field");
961        assert_eq!(
962            error_json["data"]["type"].as_str(),
963            Some("validation-errors"),
964            "Should have validation-errors type"
965        );
966
967        // Test Network error conversion
968        let network_error = ToolCallError::Execution(ToolCallExecutionError::NetworkError {
969            message: "SSL/TLS connection failed - certificate verification error".to_string(),
970            category: NetworkErrorCategory::Connect,
971        });
972
973        let error_data: ErrorData = network_error.into();
974        let error_json = serde_json::to_value(&error_data).unwrap();
975
976        assert!(error_json["data"].is_object(), "Should have data field");
977        assert_eq!(
978            error_json["data"]["type"].as_str(),
979            Some("network-error"),
980            "Should have network-error type"
981        );
982        assert!(
983            error_json["data"]["message"]
984                .as_str()
985                .unwrap()
986                .contains("SSL/TLS"),
987            "Should preserve error message"
988        );
989    }
990}