1use rmcp::model::{ErrorCode, ErrorData};
113use schemars::JsonSchema;
114use serde::Serialize;
115use serde_json::{Value, json};
116use std::fmt;
117use thiserror::Error;
118
119fn find_similar_strings(unknown: &str, known_strings: &[&str]) -> Vec<String> {
122 use strsim::jaro;
123
124 let mut candidates = Vec::new();
125 for string in known_strings {
126 let confidence = jaro(unknown, string);
127 if confidence > 0.7 {
128 candidates.push((confidence, string.to_string()));
129 }
130 }
131
132 candidates.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
134 candidates.into_iter().map(|(_, name)| name).collect()
135}
136
137#[derive(Debug, Serialize, JsonSchema)]
139#[serde(tag = "type", rename_all = "kebab-case")]
140pub enum ValidationConstraint {
141 Minimum {
143 value: f64,
145 exclusive: bool,
147 },
148 Maximum {
150 value: f64,
152 exclusive: bool,
154 },
155 MinLength {
157 value: usize,
159 },
160 MaxLength {
162 value: usize,
164 },
165 Pattern {
167 pattern: String,
169 },
170 EnumValues {
172 values: Vec<Value>,
174 },
175 Format {
177 format: String,
179 },
180 MultipleOf {
182 value: f64,
184 },
185 MinItems {
187 value: usize,
189 },
190 MaxItems {
192 value: usize,
194 },
195 UniqueItems,
197 MinProperties {
199 value: usize,
201 },
202 MaxProperties {
204 value: usize,
206 },
207 ConstValue {
209 value: Value,
211 },
212 Required {
214 properties: Vec<String>,
216 },
217}
218
219#[derive(Debug, Serialize, JsonSchema)]
221#[serde(tag = "type", rename_all = "kebab-case")]
222pub enum ValidationError {
223 InvalidParameter {
225 parameter: String,
227 suggestions: Vec<String>,
229 valid_parameters: Vec<String>,
231 },
232 MissingRequiredParameter {
234 parameter: String,
236 #[serde(skip_serializing_if = "Option::is_none")]
238 description: Option<String>,
239 expected_type: String,
241 },
242 ConstraintViolation {
244 parameter: String,
246 message: String,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 field_path: Option<String>,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 actual_value: Option<Box<Value>>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 expected_type: Option<String>,
257 #[serde(skip_serializing_if = "Vec::is_empty")]
259 constraints: Vec<ValidationConstraint>,
260 },
261}
262
263impl fmt::Display for ValidationError {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 match self {
266 ValidationError::InvalidParameter {
267 parameter,
268 suggestions,
269 ..
270 } => {
271 if suggestions.is_empty() {
272 write!(f, "'{parameter}'")
273 } else {
274 write!(f, "'{parameter}' (suggestions: {})", suggestions.join(", "))
275 }
276 }
277 ValidationError::MissingRequiredParameter {
278 parameter,
279 expected_type,
280 ..
281 } => {
282 write!(f, "'{parameter}' is required (expected: {expected_type})")
283 }
284 ValidationError::ConstraintViolation {
285 parameter, message, ..
286 } => {
287 write!(f, "'{parameter}': {message}")
288 }
289 }
290 }
291}
292
293fn format_validation_errors(violations: &[ValidationError]) -> String {
295 match violations.len() {
296 0 => "Validation failed".to_string(),
297 1 => {
298 let error = &violations[0];
300 match error {
301 ValidationError::InvalidParameter { .. } => {
302 format!("Validation failed - invalid parameter {error}")
303 }
304 ValidationError::MissingRequiredParameter { .. } => {
305 format!("Validation failed - missing required parameter: {error}")
306 }
307 ValidationError::ConstraintViolation { .. } => {
308 format!("Validation failed - parameter {error}")
309 }
310 }
311 }
312 _ => {
313 let mut invalid_params = Vec::new();
315 let mut missing_params = Vec::new();
316 let mut constraint_violations = Vec::new();
317
318 for error in violations {
320 match error {
321 ValidationError::InvalidParameter { .. } => {
322 invalid_params.push(error.to_string());
323 }
324 ValidationError::MissingRequiredParameter { .. } => {
325 missing_params.push(error.to_string());
326 }
327 ValidationError::ConstraintViolation { .. } => {
328 constraint_violations.push(error.to_string());
329 }
330 }
331 }
332
333 let mut parts = Vec::new();
334
335 if !invalid_params.is_empty() {
337 let params_str = invalid_params.join(", ");
338 parts.push(format!("invalid parameters: {params_str}"));
339 }
340
341 if !missing_params.is_empty() {
343 let params_str = missing_params.join(", ");
344 parts.push(format!("missing parameters: {params_str}"));
345 }
346
347 if !constraint_violations.is_empty() {
349 let violations_str = constraint_violations.join("; ");
350 parts.push(format!("constraint violations: {violations_str}"));
351 }
352
353 format!("Validation failed - {}", parts.join("; "))
354 }
355 }
356}
357
358#[derive(Debug, Error)]
360pub enum CliError {
361 #[error("Invalid header format in '{header}': expected 'name: value' format")]
362 InvalidHeaderFormat { header: String },
363
364 #[error("Invalid header name in '{header}': {source}")]
365 InvalidHeaderName {
366 header: String,
367 #[source]
368 source: http::header::InvalidHeaderName,
369 },
370
371 #[error("Invalid header value in '{header}': {source}")]
372 InvalidHeaderValue {
373 header: String,
374 #[source]
375 source: http::header::InvalidHeaderValue,
376 },
377}
378
379#[derive(Debug, Error)]
380pub enum Error {
381 #[error("CLI error: {0}")]
382 Cli(#[from] CliError),
383 #[error("Environment variable error: {0}")]
384 EnvVar(#[from] std::env::VarError),
385 #[error("IO error: {0}")]
386 Io(#[from] std::io::Error),
387 #[error("OpenAPI spec error: {0}")]
388 Spec(String),
389 #[error("Tool generation error: {0}")]
390 ToolGeneration(String),
391 #[error("Invalid parameter location: {0}")]
392 InvalidParameterLocation(String),
393 #[error("Invalid URL: {0}")]
394 InvalidUrl(String),
395 #[error("File not found: {0}")]
396 FileNotFound(String),
397 #[error("MCP error: {0}")]
398 McpError(String),
399 #[error("Invalid path: {0}")]
400 InvalidPath(String),
401 #[error("Validation error: {0}")]
402 Validation(String),
403 #[error("HTTP error: {0}")]
404 Http(String),
405 #[error("HTTP request error: {0}")]
406 HttpRequest(#[from] reqwest::Error),
407 #[error("JSON error at {path}: {source}")]
408 JsonAtPath {
409 path: String,
410 source: serde_json::Error,
411 },
412 #[error("JSON error: {0}")]
413 Json(#[from] serde_json::Error),
414 #[error(transparent)]
415 ToolCall(#[from] ToolCallError),
416 #[error("Tool not found: {0}")]
417 ToolNotFound(String),
418}
419
420impl From<ToolCallValidationError> for ErrorData {
421 fn from(err: ToolCallValidationError) -> Self {
422 match err {
423 ToolCallValidationError::ToolNotFound {
424 ref tool_name,
425 ref suggestions,
426 } => {
427 let data = if suggestions.is_empty() {
428 None
429 } else {
430 Some(json!({
431 "suggestions": suggestions
432 }))
433 };
434 ErrorData::new(
435 ErrorCode(-32601),
436 format!("Tool '{tool_name}' not found"),
437 data,
438 )
439 }
440 ToolCallValidationError::InvalidParameters { ref violations } => {
441 let data = Some(json!({
443 "type": "validation-errors",
444 "violations": violations
445 }));
446 ErrorData::new(ErrorCode(-32602), err.to_string(), data)
447 }
448 ToolCallValidationError::RequestConstructionError { ref reason } => {
449 let data = Some(json!({
451 "type": "request-construction-error",
452 "reason": reason
453 }));
454 ErrorData::new(ErrorCode(-32602), err.to_string(), data)
455 }
456 }
457 }
458}
459
460impl From<ToolCallError> for ErrorData {
461 fn from(err: ToolCallError) -> Self {
462 match err {
463 ToolCallError::Validation(validation_err) => validation_err.into(),
464 ToolCallError::Execution(execution_err) => {
465 match execution_err {
469 ToolCallExecutionError::HttpError {
470 status,
471 ref message,
472 ..
473 } => {
474 let data = Some(json!({
475 "type": "http-error",
476 "status": status,
477 "message": message
478 }));
479 ErrorData::new(ErrorCode(-32000), execution_err.to_string(), data)
480 }
481 ToolCallExecutionError::NetworkError {
482 ref message,
483 ref category,
484 } => {
485 let data = Some(json!({
486 "type": "network-error",
487 "message": message,
488 "category": category
489 }));
490 ErrorData::new(ErrorCode(-32000), execution_err.to_string(), data)
491 }
492 ToolCallExecutionError::ResponseParsingError { ref reason, .. } => {
493 let data = Some(json!({
494 "type": "response-parsing-error",
495 "reason": reason
496 }));
497 ErrorData::new(ErrorCode(-32700), execution_err.to_string(), data)
498 }
499 }
500 }
501 }
502 }
503}
504
505impl From<Error> for ErrorData {
506 fn from(err: Error) -> Self {
507 match err {
508 Error::Spec(msg) => ErrorData::new(
509 ErrorCode(-32700),
510 format!("OpenAPI spec error: {msg}"),
511 None,
512 ),
513 Error::Validation(msg) => {
514 ErrorData::new(ErrorCode(-32602), format!("Validation error: {msg}"), None)
515 }
516 Error::HttpRequest(e) => {
517 ErrorData::new(ErrorCode(-32000), format!("HTTP request failed: {e}"), None)
518 }
519 Error::Http(msg) => {
520 ErrorData::new(ErrorCode(-32000), format!("HTTP error: {msg}"), None)
521 }
522 Error::Json(e) => {
523 ErrorData::new(ErrorCode(-32700), format!("JSON parsing error: {e}"), None)
524 }
525 Error::ToolCall(e) => e.into(),
526 _ => ErrorData::new(ErrorCode(-32000), err.to_string(), None),
527 }
528 }
529}
530
531#[derive(Debug, Error, Serialize)]
533#[serde(untagged)]
534pub enum ToolCallError {
535 #[error(transparent)]
537 Validation(#[from] ToolCallValidationError),
538
539 #[error(transparent)]
541 Execution(#[from] ToolCallExecutionError),
542}
543
544#[derive(Debug, Serialize, JsonSchema)]
546pub struct ErrorResponse {
547 pub error: ToolCallExecutionError,
549}
550
551#[derive(Debug, Error, Serialize)]
554#[serde(tag = "type", rename_all = "kebab-case")]
555pub enum ToolCallValidationError {
556 #[error("Tool '{tool_name}' not found")]
558 #[serde(rename = "tool-not-found")]
559 ToolNotFound {
560 tool_name: String,
562 suggestions: Vec<String>,
564 },
565
566 #[error("{}", format_validation_errors(violations))]
568 #[serde(rename = "validation-errors")]
569 InvalidParameters {
570 violations: Vec<ValidationError>,
572 },
573
574 #[error("Failed to construct request: {reason}")]
576 #[serde(rename = "request-construction-error")]
577 RequestConstructionError {
578 reason: String,
580 },
581}
582
583#[derive(Debug, Error, Serialize, JsonSchema)]
586#[serde(tag = "type", rename_all = "kebab-case")]
587#[schemars(tag = "type", rename_all = "kebab-case")]
588pub enum ToolCallExecutionError {
589 #[error("HTTP {status} error: {message}")]
591 #[serde(rename = "http-error")]
592 HttpError {
593 status: u16,
595 message: String,
597 #[serde(skip_serializing_if = "Option::is_none")]
599 details: Option<Value>,
600 },
601
602 #[error("Network error: {message}")]
604 #[serde(rename = "network-error")]
605 NetworkError {
606 message: String,
608 category: NetworkErrorCategory,
610 },
611
612 #[error("Failed to parse response: {reason}")]
614 #[serde(rename = "response-parsing-error")]
615 ResponseParsingError {
616 reason: String,
618 #[serde(skip_serializing_if = "Option::is_none")]
620 raw_response: Option<String>,
621 },
622}
623
624impl ToolCallValidationError {
625 pub fn tool_not_found(tool_name: String, available_tools: &[&str]) -> Self {
627 let suggestions = find_similar_strings(&tool_name, available_tools);
628 Self::ToolNotFound {
629 tool_name,
630 suggestions,
631 }
632 }
633}
634
635impl ValidationError {
636 pub fn invalid_parameter(parameter: String, valid_parameters: &[String]) -> Self {
638 let valid_params_refs: Vec<&str> = valid_parameters.iter().map(|s| s.as_str()).collect();
639 let suggestions = find_similar_strings(¶meter, &valid_params_refs);
640 Self::InvalidParameter {
641 parameter,
642 suggestions,
643 valid_parameters: valid_parameters.to_vec(),
644 }
645 }
646}
647
648#[derive(Debug, Serialize, JsonSchema)]
650#[serde(rename_all = "kebab-case")]
651pub enum NetworkErrorCategory {
652 Timeout,
654 Connect,
656 Request,
658 Body,
660 Decode,
662 Other,
664}
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669 use insta::assert_json_snapshot;
670 use serde_json::json;
671
672 #[test]
673 fn test_tool_call_error_serialization_with_details() {
674 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
675 violations: vec![ValidationError::InvalidParameter {
676 parameter: "pet_id".to_string(),
677 suggestions: vec!["petId".to_string()],
678 valid_parameters: vec!["petId".to_string(), "timeout_seconds".to_string()],
679 }],
680 });
681
682 let serialized = serde_json::to_value(&error).unwrap();
683 assert_json_snapshot!(serialized);
684 }
685
686 #[test]
687 fn test_tool_call_error_serialization_without_details() {
688 let error = ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
689 tool_name: "unknownTool".to_string(),
690 suggestions: vec![],
691 });
692
693 let serialized = serde_json::to_value(&error).unwrap();
694 assert_json_snapshot!(serialized);
695 }
696
697 #[test]
698 fn test_tool_call_error_serialization_with_suggestions() {
699 let error = ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
700 tool_name: "getPetByID".to_string(),
701 suggestions: vec!["getPetById".to_string(), "getPetsByStatus".to_string()],
702 });
703
704 let serialized = serde_json::to_value(&error).unwrap();
705 assert_json_snapshot!(serialized);
706 }
707
708 #[test]
709 fn test_tool_call_error_multiple_suggestions() {
710 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
711 violations: vec![ValidationError::InvalidParameter {
712 parameter: "pet_i".to_string(),
713 suggestions: vec!["petId".to_string(), "petInfo".to_string()],
714 valid_parameters: vec![
715 "petId".to_string(),
716 "petInfo".to_string(),
717 "timeout".to_string(),
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_no_suggestions() {
728 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
729 violations: vec![ValidationError::InvalidParameter {
730 parameter: "completely_wrong".to_string(),
731 suggestions: vec![],
732 valid_parameters: vec!["petId".to_string(), "timeout".to_string()],
733 }],
734 });
735
736 let serialized = serde_json::to_value(&error).unwrap();
737 assert_json_snapshot!(serialized);
738 }
739
740 #[test]
741 fn test_tool_call_error_validation() {
742 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
743 violations: vec![ValidationError::MissingRequiredParameter {
744 parameter: "field".to_string(),
745 description: Some("Missing required field".to_string()),
746 expected_type: "string".to_string(),
747 }],
748 });
749 let serialized = serde_json::to_value(&error).unwrap();
750 assert_json_snapshot!(serialized);
751 }
752
753 #[test]
754 fn test_tool_call_error_validation_detailed() {
755 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
756 violations: vec![ValidationError::ConstraintViolation {
757 parameter: "age".to_string(),
758 message: "Parameter 'age' must be between 0 and 150".to_string(),
759 field_path: Some("age".to_string()),
760 actual_value: Some(Box::new(json!(200))),
761 expected_type: Some("integer".to_string()),
762 constraints: vec![
763 ValidationConstraint::Minimum {
764 value: 0.0,
765 exclusive: false,
766 },
767 ValidationConstraint::Maximum {
768 value: 150.0,
769 exclusive: false,
770 },
771 ],
772 }],
773 });
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_validation_enum() {
781 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
782 violations: vec![ValidationError::ConstraintViolation {
783 parameter: "status".to_string(),
784 message: "Parameter 'status' must be one of: available, pending, sold".to_string(),
785 field_path: Some("status".to_string()),
786 actual_value: Some(Box::new(json!("unknown"))),
787 expected_type: Some("string".to_string()),
788 constraints: vec![ValidationConstraint::EnumValues {
789 values: vec![json!("available"), json!("pending"), json!("sold")],
790 }],
791 }],
792 });
793
794 let serialized = serde_json::to_value(&error).unwrap();
795 assert_json_snapshot!(serialized);
796 }
797
798 #[test]
799 fn test_tool_call_error_validation_format() {
800 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
801 violations: vec![ValidationError::ConstraintViolation {
802 parameter: "email".to_string(),
803 message: "Invalid email format".to_string(),
804 field_path: Some("contact.email".to_string()),
805 actual_value: Some(Box::new(json!("not-an-email"))),
806 expected_type: Some("string".to_string()),
807 constraints: vec![ValidationConstraint::Format {
808 format: "email".to_string(),
809 }],
810 }],
811 });
812
813 let serialized = serde_json::to_value(&error).unwrap();
814 assert_json_snapshot!(serialized);
815 }
816
817 #[test]
818 fn test_tool_call_error_http_error() {
819 let error = ToolCallError::Execution(ToolCallExecutionError::HttpError {
820 status: 404,
821 message: "Not found".to_string(),
822 details: None,
823 });
824 let serialized = serde_json::to_value(&error).unwrap();
825 assert_json_snapshot!(serialized);
826 }
827
828 #[test]
829 fn test_tool_call_error_http_request() {
830 let error = ToolCallError::Execution(ToolCallExecutionError::NetworkError {
831 message: "Connection timeout".to_string(),
832 category: NetworkErrorCategory::Timeout,
833 });
834 let serialized = serde_json::to_value(&error).unwrap();
835 assert_json_snapshot!(serialized);
836 }
837
838 #[test]
839 fn test_tool_call_error_json() {
840 let error = ToolCallError::Execution(ToolCallExecutionError::ResponseParsingError {
841 reason: "Invalid JSON".to_string(),
842 raw_response: None,
843 });
844 let serialized = serde_json::to_value(&error).unwrap();
845 assert_json_snapshot!(serialized);
846 }
847
848 #[test]
849 fn test_tool_call_error_request_construction() {
850 let error = ToolCallError::Validation(ToolCallValidationError::RequestConstructionError {
851 reason: "Invalid parameter location: body".to_string(),
852 });
853 let serialized = serde_json::to_value(&error).unwrap();
854 assert_json_snapshot!(serialized);
855 }
856
857 #[test]
858 fn test_error_response_serialization() {
859 let error = ToolCallExecutionError::HttpError {
860 status: 400,
861 message: "Bad Request".to_string(),
862 details: Some(json!({
863 "error": "Invalid parameter",
864 "parameter": "test_param"
865 })),
866 };
867
868 let response = ErrorResponse { error };
869 let serialized = serde_json::to_value(&response).unwrap();
870 assert_json_snapshot!(serialized);
871 }
872
873 #[test]
874 fn test_tool_call_error_validation_multiple_of() {
875 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
876 violations: vec![ValidationError::ConstraintViolation {
877 parameter: "price".to_string(),
878 message: "10.5 is not a multiple of 3".to_string(),
879 field_path: Some("price".to_string()),
880 actual_value: Some(Box::new(json!(10.5))),
881 expected_type: Some("number".to_string()),
882 constraints: vec![ValidationConstraint::MultipleOf { value: 3.0 }],
883 }],
884 });
885
886 let serialized = serde_json::to_value(&error).unwrap();
887 assert_json_snapshot!(serialized);
888 }
889
890 #[test]
891 fn test_tool_call_error_validation_min_items() {
892 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
893 violations: vec![ValidationError::ConstraintViolation {
894 parameter: "tags".to_string(),
895 message: "Array has 1 items but minimum is 2".to_string(),
896 field_path: Some("tags".to_string()),
897 actual_value: Some(Box::new(json!(["tag1"]))),
898 expected_type: Some("array".to_string()),
899 constraints: vec![ValidationConstraint::MinItems { value: 2 }],
900 }],
901 });
902
903 let serialized = serde_json::to_value(&error).unwrap();
904 assert_json_snapshot!(serialized);
905 }
906
907 #[test]
908 fn test_tool_call_error_validation_max_items() {
909 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
910 violations: vec![ValidationError::ConstraintViolation {
911 parameter: "categories".to_string(),
912 message: "Array has 4 items but maximum is 3".to_string(),
913 field_path: Some("categories".to_string()),
914 actual_value: Some(Box::new(json!(["a", "b", "c", "d"]))),
915 expected_type: Some("array".to_string()),
916 constraints: vec![ValidationConstraint::MaxItems { value: 3 }],
917 }],
918 });
919
920 let serialized = serde_json::to_value(&error).unwrap();
921 assert_json_snapshot!(serialized);
922 }
923
924 #[test]
925 fn test_tool_call_error_validation_unique_items() {
926 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
927 violations: vec![ValidationError::ConstraintViolation {
928 parameter: "numbers".to_string(),
929 message: "Array items [1, 2, 2, 3] are not unique".to_string(),
930 field_path: Some("numbers".to_string()),
931 actual_value: Some(Box::new(json!([1, 2, 2, 3]))),
932 expected_type: Some("array".to_string()),
933 constraints: vec![ValidationConstraint::UniqueItems],
934 }],
935 });
936
937 let serialized = serde_json::to_value(&error).unwrap();
938 assert_json_snapshot!(serialized);
939 }
940
941 #[test]
942 fn test_tool_call_error_validation_min_properties() {
943 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
944 violations: vec![ValidationError::ConstraintViolation {
945 parameter: "metadata".to_string(),
946 message: "Object has 2 properties but minimum is 3".to_string(),
947 field_path: Some("metadata".to_string()),
948 actual_value: Some(Box::new(json!({"name": "test", "version": "1.0"}))),
949 expected_type: Some("object".to_string()),
950 constraints: vec![ValidationConstraint::MinProperties { value: 3 }],
951 }],
952 });
953
954 let serialized = serde_json::to_value(&error).unwrap();
955 assert_json_snapshot!(serialized);
956 }
957
958 #[test]
959 fn test_tool_call_error_validation_max_properties() {
960 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
961 violations: vec![ValidationError::ConstraintViolation {
962 parameter: "config".to_string(),
963 message: "Object has 3 properties but maximum is 2".to_string(),
964 field_path: Some("config".to_string()),
965 actual_value: Some(Box::new(json!({"a": 1, "b": 2, "c": 3}))),
966 expected_type: Some("object".to_string()),
967 constraints: vec![ValidationConstraint::MaxProperties { value: 2 }],
968 }],
969 });
970
971 let serialized = serde_json::to_value(&error).unwrap();
972 assert_json_snapshot!(serialized);
973 }
974
975 #[test]
976 fn test_tool_call_error_validation_const() {
977 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
978 violations: vec![ValidationError::ConstraintViolation {
979 parameter: "environment".to_string(),
980 message: r#""staging" is not equal to const "production""#.to_string(),
981 field_path: Some("environment".to_string()),
982 actual_value: Some(Box::new(json!("staging"))),
983 expected_type: Some("string".to_string()),
984 constraints: vec![ValidationConstraint::ConstValue {
985 value: json!("production"),
986 }],
987 }],
988 });
989
990 let serialized = serde_json::to_value(&error).unwrap();
991 assert_json_snapshot!(serialized);
992 }
993
994 #[test]
995 fn test_error_data_conversion_preserves_details() {
996 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
998 violations: vec![ValidationError::InvalidParameter {
999 parameter: "page".to_string(),
1000 suggestions: vec!["page_number".to_string()],
1001 valid_parameters: vec!["page_number".to_string(), "page_size".to_string()],
1002 }],
1003 });
1004
1005 let error_data: ErrorData = error.into();
1006 let error_json = serde_json::to_value(&error_data).unwrap();
1007
1008 assert!(error_json["data"].is_object(), "Should have data field");
1010 assert_eq!(
1011 error_json["data"]["type"].as_str(),
1012 Some("validation-errors"),
1013 "Should have validation-errors type"
1014 );
1015
1016 let network_error = ToolCallError::Execution(ToolCallExecutionError::NetworkError {
1018 message: "SSL/TLS connection failed - certificate verification error".to_string(),
1019 category: NetworkErrorCategory::Connect,
1020 });
1021
1022 let error_data: ErrorData = network_error.into();
1023 let error_json = serde_json::to_value(&error_data).unwrap();
1024
1025 assert!(error_json["data"].is_object(), "Should have data field");
1026 assert_eq!(
1027 error_json["data"]["type"].as_str(),
1028 Some("network-error"),
1029 "Should have network-error type"
1030 );
1031 assert!(
1032 error_json["data"]["message"]
1033 .as_str()
1034 .unwrap()
1035 .contains("SSL/TLS"),
1036 "Should preserve error message"
1037 );
1038 }
1039
1040 #[test]
1041 fn test_find_similar_strings() {
1042 let known = vec!["page_size", "user_id", "status"];
1044 let suggestions = find_similar_strings("page_sixe", &known);
1045 assert_eq!(suggestions, vec!["page_size"]);
1046
1047 let suggestions = find_similar_strings("xyz123", &known);
1049 assert!(suggestions.is_empty());
1050
1051 let known = vec!["limit", "offset"];
1053 let suggestions = find_similar_strings("lmiit", &known);
1054 assert_eq!(suggestions, vec!["limit"]);
1055
1056 let known = vec!["project_id", "merge_request_id"];
1058 let suggestions = find_similar_strings("projct_id", &known);
1059 assert_eq!(suggestions, vec!["project_id"]);
1060
1061 let known = vec!["name", "email"];
1063 let suggestions = find_similar_strings("namee", &known);
1064 assert_eq!(suggestions, vec!["name"]);
1065 }
1066}