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}