deps_cargo/
handler.rs

1//! Cargo ecosystem handler implementation.
2//!
3//! Implements the EcosystemHandler trait for Cargo/crates.io,
4//! enabling generic LSP operations (inlay hints, hover, etc.).
5
6use crate::{CratesIoRegistry, ParsedDependency, crate_url};
7use async_trait::async_trait;
8use deps_core::{EcosystemHandler, HttpCache, SemverMatcher, VersionRequirementMatcher};
9use std::sync::Arc;
10
11/// Cargo ecosystem handler.
12///
13/// Provides Cargo-specific implementations of the generic handler trait,
14/// using crates.io registry and semver version matching.
15pub struct CargoHandler {
16    registry: CratesIoRegistry,
17}
18
19#[async_trait]
20impl EcosystemHandler for CargoHandler {
21    type Registry = CratesIoRegistry;
22    type Dependency = ParsedDependency;
23    type UnifiedDep = ParsedDependency; // Self-contained: no UnifiedDependency needed
24
25    fn new(cache: Arc<HttpCache>) -> Self {
26        Self {
27            registry: CratesIoRegistry::new(cache),
28        }
29    }
30
31    fn registry(&self) -> &Self::Registry {
32        &self.registry
33    }
34
35    fn extract_dependency(dep: &Self::UnifiedDep) -> Option<&Self::Dependency> {
36        // In standalone use, UnifiedDep is just ParsedDependency
37        Some(dep)
38    }
39
40    fn package_url(name: &str) -> String {
41        crate_url(name)
42    }
43
44    fn ecosystem_display_name() -> &'static str {
45        "crates.io"
46    }
47
48    #[inline]
49    fn is_version_latest(version_req: &str, latest: &str) -> bool {
50        SemverMatcher.is_latest_satisfying(version_req, latest)
51    }
52
53    fn format_version_for_edit(_dep: &Self::Dependency, version: &str) -> String {
54        format!("\"{version}\"")
55    }
56
57    fn is_deprecated(version: &crate::CargoVersion) -> bool {
58        version.yanked
59    }
60
61    fn is_valid_version_syntax(version_req: &str) -> bool {
62        version_req.parse::<semver::VersionReq>().is_ok()
63    }
64
65    fn parse_version_req(version_req: &str) -> Option<semver::VersionReq> {
66        version_req.parse().ok()
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_package_url() {
76        let url = CargoHandler::package_url("serde");
77        assert_eq!(url, "https://crates.io/crates/serde");
78    }
79
80    #[test]
81    fn test_ecosystem_display_name() {
82        assert_eq!(CargoHandler::ecosystem_display_name(), "crates.io");
83    }
84
85    #[test]
86    fn test_is_version_latest_compatible() {
87        assert!(CargoHandler::is_version_latest("1.0.0", "1.0.5"));
88        assert!(CargoHandler::is_version_latest("^1.0.0", "1.5.0"));
89        assert!(CargoHandler::is_version_latest("0.1", "0.1.83"));
90    }
91
92    #[test]
93    fn test_is_version_latest_incompatible() {
94        assert!(!CargoHandler::is_version_latest("1.0.0", "2.0.0"));
95        assert!(!CargoHandler::is_version_latest("0.1", "0.2.0"));
96    }
97
98    #[test]
99    fn test_new_creates_handler() {
100        let cache = Arc::new(HttpCache::new());
101        let handler = CargoHandler::new(cache);
102        let registry = handler.registry();
103        assert!(std::ptr::addr_of!(*registry) == std::ptr::addr_of!(handler.registry));
104    }
105
106    #[test]
107    fn test_extract_dependency_returns_some() {
108        use crate::ParsedDependency;
109        use tower_lsp_server::ls_types::{Position, Range};
110
111        let dep = ParsedDependency {
112            name: "test".into(),
113            name_range: Range::new(Position::new(0, 0), Position::new(0, 4)),
114            version_req: Some("1.0.0".into()),
115            version_range: Some(Range::new(Position::new(0, 8), Position::new(0, 13))),
116            features: vec![],
117            features_range: None,
118            source: crate::DependencySource::Registry,
119            workspace_inherited: false,
120            section: crate::DependencySection::Dependencies,
121        };
122        let result = CargoHandler::extract_dependency(&dep);
123        assert!(result.is_some());
124        assert_eq!(result.unwrap().name, "test");
125    }
126
127    #[test]
128    fn test_is_version_latest_with_tilde() {
129        assert!(CargoHandler::is_version_latest("~1.0.0", "1.0.5"));
130        assert!(!CargoHandler::is_version_latest("~1.0.0", "1.1.0"));
131    }
132
133    #[test]
134    fn test_is_version_latest_with_exact() {
135        assert!(CargoHandler::is_version_latest("=1.0.0", "1.0.0"));
136        assert!(!CargoHandler::is_version_latest("=1.0.0", "1.0.1"));
137    }
138
139    #[test]
140    fn test_is_version_latest_edge_cases() {
141        assert!(CargoHandler::is_version_latest("0.0.1", "0.0.1"));
142        assert!(!CargoHandler::is_version_latest("0.0.1", "0.0.2"));
143    }
144}