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