1use rmcp::model::{ErrorCode, ErrorData};
113use schemars::JsonSchema;
114use serde::Serialize;
115use serde_json::{Value, json};
116use std::fmt;
117use thiserror::Error;
118
119#[derive(Debug, Serialize, JsonSchema)]
121#[serde(tag = "type", rename_all = "kebab-case")]
122pub enum ValidationConstraint {
123 Minimum {
125 value: f64,
127 exclusive: bool,
129 },
130 Maximum {
132 value: f64,
134 exclusive: bool,
136 },
137 MinLength {
139 value: usize,
141 },
142 MaxLength {
144 value: usize,
146 },
147 Pattern {
149 pattern: String,
151 },
152 EnumValues {
154 values: Vec<Value>,
156 },
157 Format {
159 format: String,
161 },
162 MultipleOf {
164 value: f64,
166 },
167 MinItems {
169 value: usize,
171 },
172 MaxItems {
174 value: usize,
176 },
177 UniqueItems,
179 MinProperties {
181 value: usize,
183 },
184 MaxProperties {
186 value: usize,
188 },
189 ConstValue {
191 value: Value,
193 },
194 Required {
196 properties: Vec<String>,
198 },
199}
200
201#[derive(Debug, Serialize, JsonSchema)]
203#[serde(tag = "type", rename_all = "kebab-case")]
204pub enum ValidationError {
205 InvalidParameter {
207 parameter: String,
209 suggestions: Vec<String>,
211 valid_parameters: Vec<String>,
213 },
214 MissingRequiredParameter {
216 parameter: String,
218 #[serde(skip_serializing_if = "Option::is_none")]
220 description: Option<String>,
221 expected_type: String,
223 },
224 ConstraintViolation {
226 parameter: String,
228 message: String,
230 #[serde(skip_serializing_if = "Option::is_none")]
232 field_path: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 actual_value: Option<Box<Value>>,
236 #[serde(skip_serializing_if = "Option::is_none")]
238 expected_type: Option<String>,
239 #[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
275fn format_validation_errors(violations: &[ValidationError]) -> String {
277 match violations.len() {
278 0 => "Validation failed".to_string(),
279 1 => {
280 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 let mut invalid_params = Vec::new();
297 let mut missing_params = Vec::new();
298 let mut constraint_violations = Vec::new();
299
300 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 if !invalid_params.is_empty() {
319 let params_str = invalid_params.join(", ");
320 parts.push(format!("invalid parameters: {params_str}"));
321 }
322
323 if !missing_params.is_empty() {
325 let params_str = missing_params.join(", ");
326 parts.push(format!("missing parameters: {params_str}"));
327 }
328
329 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)]
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 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 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 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#[derive(Debug, Error, Serialize)]
508#[serde(untagged)]
509pub enum ToolCallError {
510 #[error(transparent)]
512 Validation(#[from] ToolCallValidationError),
513
514 #[error(transparent)]
516 Execution(#[from] ToolCallExecutionError),
517}
518
519#[derive(Debug, Serialize, JsonSchema)]
521pub struct ErrorResponse {
522 pub error: ToolCallExecutionError,
524}
525
526#[derive(Debug, Error, Serialize)]
529#[serde(tag = "type", rename_all = "kebab-case")]
530pub enum ToolCallValidationError {
531 #[error("Tool '{tool_name}' not found")]
533 #[serde(rename = "tool-not-found")]
534 ToolNotFound {
535 tool_name: String,
537 suggestions: Vec<String>,
539 },
540
541 #[error("{}", format_validation_errors(violations))]
543 #[serde(rename = "validation-errors")]
544 InvalidParameters {
545 violations: Vec<ValidationError>,
547 },
548
549 #[error("Failed to construct request: {reason}")]
551 #[serde(rename = "request-construction-error")]
552 RequestConstructionError {
553 reason: String,
555 },
556}
557
558#[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 #[error("HTTP {status} error: {message}")]
566 #[serde(rename = "http-error")]
567 HttpError {
568 status: u16,
570 message: String,
572 #[serde(skip_serializing_if = "Option::is_none")]
574 details: Option<Value>,
575 },
576
577 #[error("Network error: {message}")]
579 #[serde(rename = "network-error")]
580 NetworkError {
581 message: String,
583 category: NetworkErrorCategory,
585 },
586
587 #[error("Failed to parse response: {reason}")]
589 #[serde(rename = "response-parsing-error")]
590 ResponseParsingError {
591 reason: String,
593 #[serde(skip_serializing_if = "Option::is_none")]
595 raw_response: Option<String>,
596 },
597}
598
599#[derive(Debug, Serialize, JsonSchema)]
601#[serde(rename_all = "kebab-case")]
602pub enum NetworkErrorCategory {
603 Timeout,
605 Connect,
607 Request,
609 Body,
611 Decode,
613 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 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 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 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}