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: {0}")]
408 Json(#[from] serde_json::Error),
409 #[error(transparent)]
410 ToolCall(#[from] ToolCallError),
411 #[error("Tool not found: {0}")]
412 ToolNotFound(String),
413}
414
415impl From<ToolCallValidationError> for ErrorData {
416 fn from(err: ToolCallValidationError) -> Self {
417 match err {
418 ToolCallValidationError::ToolNotFound {
419 ref tool_name,
420 ref suggestions,
421 } => {
422 let data = if suggestions.is_empty() {
423 None
424 } else {
425 Some(json!({
426 "suggestions": suggestions
427 }))
428 };
429 ErrorData::new(
430 ErrorCode(-32601),
431 format!("Tool '{tool_name}' not found"),
432 data,
433 )
434 }
435 ToolCallValidationError::InvalidParameters { ref violations } => {
436 let data = Some(json!({
438 "type": "validation-errors",
439 "violations": violations
440 }));
441 ErrorData::new(ErrorCode(-32602), err.to_string(), data)
442 }
443 ToolCallValidationError::RequestConstructionError { ref reason } => {
444 let data = Some(json!({
446 "type": "request-construction-error",
447 "reason": reason
448 }));
449 ErrorData::new(ErrorCode(-32602), err.to_string(), data)
450 }
451 }
452 }
453}
454
455impl From<ToolCallError> for ErrorData {
456 fn from(err: ToolCallError) -> Self {
457 match err {
458 ToolCallError::Validation(validation_err) => validation_err.into(),
459 ToolCallError::Execution(execution_err) => {
460 match execution_err {
464 ToolCallExecutionError::HttpError {
465 status,
466 ref message,
467 ..
468 } => {
469 let data = Some(json!({
470 "type": "http-error",
471 "status": status,
472 "message": message
473 }));
474 ErrorData::new(ErrorCode(-32000), execution_err.to_string(), data)
475 }
476 ToolCallExecutionError::NetworkError {
477 ref message,
478 ref category,
479 } => {
480 let data = Some(json!({
481 "type": "network-error",
482 "message": message,
483 "category": category
484 }));
485 ErrorData::new(ErrorCode(-32000), execution_err.to_string(), data)
486 }
487 ToolCallExecutionError::ResponseParsingError { ref reason, .. } => {
488 let data = Some(json!({
489 "type": "response-parsing-error",
490 "reason": reason
491 }));
492 ErrorData::new(ErrorCode(-32700), execution_err.to_string(), data)
493 }
494 }
495 }
496 }
497 }
498}
499
500impl From<Error> for ErrorData {
501 fn from(err: Error) -> Self {
502 match err {
503 Error::Spec(msg) => ErrorData::new(
504 ErrorCode(-32700),
505 format!("OpenAPI spec error: {msg}"),
506 None,
507 ),
508 Error::Validation(msg) => {
509 ErrorData::new(ErrorCode(-32602), format!("Validation error: {msg}"), None)
510 }
511 Error::HttpRequest(e) => {
512 ErrorData::new(ErrorCode(-32000), format!("HTTP request failed: {e}"), None)
513 }
514 Error::Http(msg) => {
515 ErrorData::new(ErrorCode(-32000), format!("HTTP error: {msg}"), None)
516 }
517 Error::Json(e) => {
518 ErrorData::new(ErrorCode(-32700), format!("JSON parsing error: {e}"), None)
519 }
520 Error::ToolCall(e) => e.into(),
521 _ => ErrorData::new(ErrorCode(-32000), err.to_string(), None),
522 }
523 }
524}
525
526#[derive(Debug, Error, Serialize)]
528#[serde(untagged)]
529pub enum ToolCallError {
530 #[error(transparent)]
532 Validation(#[from] ToolCallValidationError),
533
534 #[error(transparent)]
536 Execution(#[from] ToolCallExecutionError),
537}
538
539#[derive(Debug, Serialize, JsonSchema)]
541pub struct ErrorResponse {
542 pub error: ToolCallExecutionError,
544}
545
546#[derive(Debug, Error, Serialize)]
549#[serde(tag = "type", rename_all = "kebab-case")]
550pub enum ToolCallValidationError {
551 #[error("Tool '{tool_name}' not found")]
553 #[serde(rename = "tool-not-found")]
554 ToolNotFound {
555 tool_name: String,
557 suggestions: Vec<String>,
559 },
560
561 #[error("{}", format_validation_errors(violations))]
563 #[serde(rename = "validation-errors")]
564 InvalidParameters {
565 violations: Vec<ValidationError>,
567 },
568
569 #[error("Failed to construct request: {reason}")]
571 #[serde(rename = "request-construction-error")]
572 RequestConstructionError {
573 reason: String,
575 },
576}
577
578#[derive(Debug, Error, Serialize, JsonSchema)]
581#[serde(tag = "type", rename_all = "kebab-case")]
582#[schemars(tag = "type", rename_all = "kebab-case")]
583pub enum ToolCallExecutionError {
584 #[error("HTTP {status} error: {message}")]
586 #[serde(rename = "http-error")]
587 HttpError {
588 status: u16,
590 message: String,
592 #[serde(skip_serializing_if = "Option::is_none")]
594 details: Option<Value>,
595 },
596
597 #[error("Network error: {message}")]
599 #[serde(rename = "network-error")]
600 NetworkError {
601 message: String,
603 category: NetworkErrorCategory,
605 },
606
607 #[error("Failed to parse response: {reason}")]
609 #[serde(rename = "response-parsing-error")]
610 ResponseParsingError {
611 reason: String,
613 #[serde(skip_serializing_if = "Option::is_none")]
615 raw_response: Option<String>,
616 },
617}
618
619impl ToolCallValidationError {
620 pub fn tool_not_found(tool_name: String, available_tools: &[&str]) -> Self {
622 let suggestions = find_similar_strings(&tool_name, available_tools);
623 Self::ToolNotFound {
624 tool_name,
625 suggestions,
626 }
627 }
628}
629
630impl ValidationError {
631 pub fn invalid_parameter(parameter: String, valid_parameters: &[String]) -> Self {
633 let valid_params_refs: Vec<&str> = valid_parameters.iter().map(|s| s.as_str()).collect();
634 let suggestions = find_similar_strings(¶meter, &valid_params_refs);
635 Self::InvalidParameter {
636 parameter,
637 suggestions,
638 valid_parameters: valid_parameters.to_vec(),
639 }
640 }
641}
642
643#[derive(Debug, Serialize, JsonSchema)]
645#[serde(rename_all = "kebab-case")]
646pub enum NetworkErrorCategory {
647 Timeout,
649 Connect,
651 Request,
653 Body,
655 Decode,
657 Other,
659}
660
661#[cfg(test)]
662mod tests {
663 use super::*;
664 use insta::assert_json_snapshot;
665 use serde_json::json;
666
667 #[test]
668 fn test_tool_call_error_serialization_with_details() {
669 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
670 violations: vec![ValidationError::InvalidParameter {
671 parameter: "pet_id".to_string(),
672 suggestions: vec!["petId".to_string()],
673 valid_parameters: vec!["petId".to_string(), "timeout_seconds".to_string()],
674 }],
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_serialization_without_details() {
683 let error = ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
684 tool_name: "unknownTool".to_string(),
685 suggestions: vec![],
686 });
687
688 let serialized = serde_json::to_value(&error).unwrap();
689 assert_json_snapshot!(serialized);
690 }
691
692 #[test]
693 fn test_tool_call_error_serialization_with_suggestions() {
694 let error = ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
695 tool_name: "getPetByID".to_string(),
696 suggestions: vec!["getPetById".to_string(), "getPetsByStatus".to_string()],
697 });
698
699 let serialized = serde_json::to_value(&error).unwrap();
700 assert_json_snapshot!(serialized);
701 }
702
703 #[test]
704 fn test_tool_call_error_multiple_suggestions() {
705 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
706 violations: vec![ValidationError::InvalidParameter {
707 parameter: "pet_i".to_string(),
708 suggestions: vec!["petId".to_string(), "petInfo".to_string()],
709 valid_parameters: vec![
710 "petId".to_string(),
711 "petInfo".to_string(),
712 "timeout".to_string(),
713 ],
714 }],
715 });
716
717 let serialized = serde_json::to_value(&error).unwrap();
718 assert_json_snapshot!(serialized);
719 }
720
721 #[test]
722 fn test_tool_call_error_no_suggestions() {
723 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
724 violations: vec![ValidationError::InvalidParameter {
725 parameter: "completely_wrong".to_string(),
726 suggestions: vec![],
727 valid_parameters: vec!["petId".to_string(), "timeout".to_string()],
728 }],
729 });
730
731 let serialized = serde_json::to_value(&error).unwrap();
732 assert_json_snapshot!(serialized);
733 }
734
735 #[test]
736 fn test_tool_call_error_validation() {
737 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
738 violations: vec![ValidationError::MissingRequiredParameter {
739 parameter: "field".to_string(),
740 description: Some("Missing required field".to_string()),
741 expected_type: "string".to_string(),
742 }],
743 });
744 let serialized = serde_json::to_value(&error).unwrap();
745 assert_json_snapshot!(serialized);
746 }
747
748 #[test]
749 fn test_tool_call_error_validation_detailed() {
750 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
751 violations: vec![ValidationError::ConstraintViolation {
752 parameter: "age".to_string(),
753 message: "Parameter 'age' must be between 0 and 150".to_string(),
754 field_path: Some("age".to_string()),
755 actual_value: Some(Box::new(json!(200))),
756 expected_type: Some("integer".to_string()),
757 constraints: vec![
758 ValidationConstraint::Minimum {
759 value: 0.0,
760 exclusive: false,
761 },
762 ValidationConstraint::Maximum {
763 value: 150.0,
764 exclusive: false,
765 },
766 ],
767 }],
768 });
769
770 let serialized = serde_json::to_value(&error).unwrap();
771 assert_json_snapshot!(serialized);
772 }
773
774 #[test]
775 fn test_tool_call_error_validation_enum() {
776 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
777 violations: vec![ValidationError::ConstraintViolation {
778 parameter: "status".to_string(),
779 message: "Parameter 'status' must be one of: available, pending, sold".to_string(),
780 field_path: Some("status".to_string()),
781 actual_value: Some(Box::new(json!("unknown"))),
782 expected_type: Some("string".to_string()),
783 constraints: vec![ValidationConstraint::EnumValues {
784 values: vec![json!("available"), json!("pending"), json!("sold")],
785 }],
786 }],
787 });
788
789 let serialized = serde_json::to_value(&error).unwrap();
790 assert_json_snapshot!(serialized);
791 }
792
793 #[test]
794 fn test_tool_call_error_validation_format() {
795 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
796 violations: vec![ValidationError::ConstraintViolation {
797 parameter: "email".to_string(),
798 message: "Invalid email format".to_string(),
799 field_path: Some("contact.email".to_string()),
800 actual_value: Some(Box::new(json!("not-an-email"))),
801 expected_type: Some("string".to_string()),
802 constraints: vec![ValidationConstraint::Format {
803 format: "email".to_string(),
804 }],
805 }],
806 });
807
808 let serialized = serde_json::to_value(&error).unwrap();
809 assert_json_snapshot!(serialized);
810 }
811
812 #[test]
813 fn test_tool_call_error_http_error() {
814 let error = ToolCallError::Execution(ToolCallExecutionError::HttpError {
815 status: 404,
816 message: "Not found".to_string(),
817 details: None,
818 });
819 let serialized = serde_json::to_value(&error).unwrap();
820 assert_json_snapshot!(serialized);
821 }
822
823 #[test]
824 fn test_tool_call_error_http_request() {
825 let error = ToolCallError::Execution(ToolCallExecutionError::NetworkError {
826 message: "Connection timeout".to_string(),
827 category: NetworkErrorCategory::Timeout,
828 });
829 let serialized = serde_json::to_value(&error).unwrap();
830 assert_json_snapshot!(serialized);
831 }
832
833 #[test]
834 fn test_tool_call_error_json() {
835 let error = ToolCallError::Execution(ToolCallExecutionError::ResponseParsingError {
836 reason: "Invalid JSON".to_string(),
837 raw_response: None,
838 });
839 let serialized = serde_json::to_value(&error).unwrap();
840 assert_json_snapshot!(serialized);
841 }
842
843 #[test]
844 fn test_tool_call_error_request_construction() {
845 let error = ToolCallError::Validation(ToolCallValidationError::RequestConstructionError {
846 reason: "Invalid parameter location: body".to_string(),
847 });
848 let serialized = serde_json::to_value(&error).unwrap();
849 assert_json_snapshot!(serialized);
850 }
851
852 #[test]
853 fn test_error_response_serialization() {
854 let error = ToolCallExecutionError::HttpError {
855 status: 400,
856 message: "Bad Request".to_string(),
857 details: Some(json!({
858 "error": "Invalid parameter",
859 "parameter": "test_param"
860 })),
861 };
862
863 let response = ErrorResponse { error };
864 let serialized = serde_json::to_value(&response).unwrap();
865 assert_json_snapshot!(serialized);
866 }
867
868 #[test]
869 fn test_tool_call_error_validation_multiple_of() {
870 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
871 violations: vec![ValidationError::ConstraintViolation {
872 parameter: "price".to_string(),
873 message: "10.5 is not a multiple of 3".to_string(),
874 field_path: Some("price".to_string()),
875 actual_value: Some(Box::new(json!(10.5))),
876 expected_type: Some("number".to_string()),
877 constraints: vec![ValidationConstraint::MultipleOf { value: 3.0 }],
878 }],
879 });
880
881 let serialized = serde_json::to_value(&error).unwrap();
882 assert_json_snapshot!(serialized);
883 }
884
885 #[test]
886 fn test_tool_call_error_validation_min_items() {
887 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
888 violations: vec![ValidationError::ConstraintViolation {
889 parameter: "tags".to_string(),
890 message: "Array has 1 items but minimum is 2".to_string(),
891 field_path: Some("tags".to_string()),
892 actual_value: Some(Box::new(json!(["tag1"]))),
893 expected_type: Some("array".to_string()),
894 constraints: vec![ValidationConstraint::MinItems { value: 2 }],
895 }],
896 });
897
898 let serialized = serde_json::to_value(&error).unwrap();
899 assert_json_snapshot!(serialized);
900 }
901
902 #[test]
903 fn test_tool_call_error_validation_max_items() {
904 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
905 violations: vec![ValidationError::ConstraintViolation {
906 parameter: "categories".to_string(),
907 message: "Array has 4 items but maximum is 3".to_string(),
908 field_path: Some("categories".to_string()),
909 actual_value: Some(Box::new(json!(["a", "b", "c", "d"]))),
910 expected_type: Some("array".to_string()),
911 constraints: vec![ValidationConstraint::MaxItems { value: 3 }],
912 }],
913 });
914
915 let serialized = serde_json::to_value(&error).unwrap();
916 assert_json_snapshot!(serialized);
917 }
918
919 #[test]
920 fn test_tool_call_error_validation_unique_items() {
921 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
922 violations: vec![ValidationError::ConstraintViolation {
923 parameter: "numbers".to_string(),
924 message: "Array items [1, 2, 2, 3] are not unique".to_string(),
925 field_path: Some("numbers".to_string()),
926 actual_value: Some(Box::new(json!([1, 2, 2, 3]))),
927 expected_type: Some("array".to_string()),
928 constraints: vec![ValidationConstraint::UniqueItems],
929 }],
930 });
931
932 let serialized = serde_json::to_value(&error).unwrap();
933 assert_json_snapshot!(serialized);
934 }
935
936 #[test]
937 fn test_tool_call_error_validation_min_properties() {
938 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
939 violations: vec![ValidationError::ConstraintViolation {
940 parameter: "metadata".to_string(),
941 message: "Object has 2 properties but minimum is 3".to_string(),
942 field_path: Some("metadata".to_string()),
943 actual_value: Some(Box::new(json!({"name": "test", "version": "1.0"}))),
944 expected_type: Some("object".to_string()),
945 constraints: vec![ValidationConstraint::MinProperties { value: 3 }],
946 }],
947 });
948
949 let serialized = serde_json::to_value(&error).unwrap();
950 assert_json_snapshot!(serialized);
951 }
952
953 #[test]
954 fn test_tool_call_error_validation_max_properties() {
955 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
956 violations: vec![ValidationError::ConstraintViolation {
957 parameter: "config".to_string(),
958 message: "Object has 3 properties but maximum is 2".to_string(),
959 field_path: Some("config".to_string()),
960 actual_value: Some(Box::new(json!({"a": 1, "b": 2, "c": 3}))),
961 expected_type: Some("object".to_string()),
962 constraints: vec![ValidationConstraint::MaxProperties { value: 2 }],
963 }],
964 });
965
966 let serialized = serde_json::to_value(&error).unwrap();
967 assert_json_snapshot!(serialized);
968 }
969
970 #[test]
971 fn test_tool_call_error_validation_const() {
972 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
973 violations: vec![ValidationError::ConstraintViolation {
974 parameter: "environment".to_string(),
975 message: r#""staging" is not equal to const "production""#.to_string(),
976 field_path: Some("environment".to_string()),
977 actual_value: Some(Box::new(json!("staging"))),
978 expected_type: Some("string".to_string()),
979 constraints: vec![ValidationConstraint::ConstValue {
980 value: json!("production"),
981 }],
982 }],
983 });
984
985 let serialized = serde_json::to_value(&error).unwrap();
986 assert_json_snapshot!(serialized);
987 }
988
989 #[test]
990 fn test_error_data_conversion_preserves_details() {
991 let error = ToolCallError::Validation(ToolCallValidationError::InvalidParameters {
993 violations: vec![ValidationError::InvalidParameter {
994 parameter: "page".to_string(),
995 suggestions: vec!["page_number".to_string()],
996 valid_parameters: vec!["page_number".to_string(), "page_size".to_string()],
997 }],
998 });
999
1000 let error_data: ErrorData = error.into();
1001 let error_json = serde_json::to_value(&error_data).unwrap();
1002
1003 assert!(error_json["data"].is_object(), "Should have data field");
1005 assert_eq!(
1006 error_json["data"]["type"].as_str(),
1007 Some("validation-errors"),
1008 "Should have validation-errors type"
1009 );
1010
1011 let network_error = ToolCallError::Execution(ToolCallExecutionError::NetworkError {
1013 message: "SSL/TLS connection failed - certificate verification error".to_string(),
1014 category: NetworkErrorCategory::Connect,
1015 });
1016
1017 let error_data: ErrorData = network_error.into();
1018 let error_json = serde_json::to_value(&error_data).unwrap();
1019
1020 assert!(error_json["data"].is_object(), "Should have data field");
1021 assert_eq!(
1022 error_json["data"]["type"].as_str(),
1023 Some("network-error"),
1024 "Should have network-error type"
1025 );
1026 assert!(
1027 error_json["data"]["message"]
1028 .as_str()
1029 .unwrap()
1030 .contains("SSL/TLS"),
1031 "Should preserve error message"
1032 );
1033 }
1034
1035 #[test]
1036 fn test_find_similar_strings() {
1037 let known = vec!["page_size", "user_id", "status"];
1039 let suggestions = find_similar_strings("page_sixe", &known);
1040 assert_eq!(suggestions, vec!["page_size"]);
1041
1042 let suggestions = find_similar_strings("xyz123", &known);
1044 assert!(suggestions.is_empty());
1045
1046 let known = vec!["limit", "offset"];
1048 let suggestions = find_similar_strings("lmiit", &known);
1049 assert_eq!(suggestions, vec!["limit"]);
1050
1051 let known = vec!["project_id", "merge_request_id"];
1053 let suggestions = find_similar_strings("projct_id", &known);
1054 assert_eq!(suggestions, vec!["project_id"]);
1055
1056 let known = vec!["name", "email"];
1058 let suggestions = find_similar_strings("namee", &known);
1059 assert_eq!(suggestions, vec!["name"]);
1060 }
1061}