Skip to main content

deps_cargo/
types.rs

1use std::any::Any;
2use std::collections::HashMap;
3use tower_lsp_server::ls_types::Range;
4
5/// Parsed dependency from Cargo.toml with position tracking.
6///
7/// Stores all information about a dependency declaration, including its name,
8/// version requirement, features, and source positions for LSP operations.
9/// Positions are critical for features like hover, completion, and inlay hints.
10///
11/// # Examples
12///
13/// ```
14/// use deps_cargo::types::{ParsedDependency, DependencySource, DependencySection};
15/// use tower_lsp_server::ls_types::{Position, Range};
16///
17/// let dep = ParsedDependency {
18///     name: "serde".into(),
19///     name_range: Range::new(Position::new(5, 0), Position::new(5, 5)),
20///     version_req: Some("1.0".into()),
21///     version_range: Some(Range::new(Position::new(5, 9), Position::new(5, 14))),
22///     features: vec!["derive".into()],
23///     features_range: None,
24///     source: DependencySource::Registry,
25///     workspace_inherited: false,
26///     section: DependencySection::Dependencies,
27/// };
28///
29/// assert_eq!(dep.name, "serde");
30/// assert!(matches!(dep.source, DependencySource::Registry));
31/// ```
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct ParsedDependency {
34    pub name: String,
35    pub name_range: Range,
36    pub version_req: Option<String>,
37    pub version_range: Option<Range>,
38    pub features: Vec<String>,
39    pub features_range: Option<Range>,
40    pub source: DependencySource,
41    pub workspace_inherited: bool,
42    pub section: DependencySection,
43}
44
45/// Source location of a dependency.
46///
47/// Dependencies can come from the crates.io registry, a Git repository,
48/// or a local filesystem path. This affects how the LSP server resolves
49/// version information and provides completions.
50///
51/// # Examples
52///
53/// ```
54/// use deps_cargo::types::DependencySource;
55///
56/// let registry = DependencySource::Registry;
57/// let git = DependencySource::Git {
58///     url: "https://github.com/serde-rs/serde".into(),
59///     rev: Some("v1.0.0".into()),
60/// };
61/// let path = DependencySource::Path {
62///     path: "../local-crate".into(),
63/// };
64/// ```
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum DependencySource {
67    /// Dependency from crates.io registry
68    Registry,
69    /// Dependency from Git repository
70    Git { url: String, rev: Option<String> },
71    /// Dependency from local filesystem path
72    Path { path: String },
73}
74
75/// Section in Cargo.toml where a dependency is declared.
76///
77/// Cargo.toml has four dependency sections with different purposes:
78/// - `[dependencies]`: Runtime dependencies
79/// - `[dev-dependencies]`: Test and example dependencies
80/// - `[build-dependencies]`: Build script dependencies
81/// - `[workspace.dependencies]`: Workspace-wide dependency definitions
82///
83/// # Examples
84///
85/// ```
86/// use deps_cargo::types::DependencySection;
87///
88/// let section = DependencySection::Dependencies;
89/// assert!(matches!(section, DependencySection::Dependencies));
90/// ```
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum DependencySection {
93    /// Runtime dependencies (`[dependencies]`)
94    Dependencies,
95    /// Development dependencies (`[dev-dependencies]`)
96    DevDependencies,
97    /// Build script dependencies (`[build-dependencies]`)
98    BuildDependencies,
99    /// Workspace-wide dependency definitions (`[workspace.dependencies]`)
100    WorkspaceDependencies,
101}
102
103/// Version information for a crate from crates.io.
104///
105/// Retrieved from the sparse index at `https://index.crates.io/{cr}/{at}/{crate}`.
106/// Contains version number, yanked status, and available feature flags.
107///
108/// # Examples
109///
110/// ```
111/// use deps_cargo::types::CargoVersion;
112/// use std::collections::HashMap;
113///
114/// let version = CargoVersion {
115///     num: "1.0.214".into(),
116///     yanked: false,
117///     features: {
118///         let mut f = HashMap::new();
119///         f.insert("derive".into(), vec!["serde_derive".into()]);
120///         f
121///     },
122/// };
123///
124/// assert!(!version.yanked);
125/// assert!(version.features.contains_key("derive"));
126/// ```
127#[derive(Debug, Clone)]
128pub struct CargoVersion {
129    pub num: String,
130    pub yanked: bool,
131    pub features: HashMap<String, Vec<String>>,
132}
133
134/// Crate metadata from crates.io search API.
135///
136/// Contains basic information about a crate for display in completion suggestions.
137/// Retrieved from `https://crates.io/api/v1/crates?q={query}`.
138///
139/// # Examples
140///
141/// ```
142/// use deps_cargo::types::CrateInfo;
143///
144/// let info = CrateInfo {
145///     name: "serde".into(),
146///     description: Some("A serialization framework".into()),
147///     repository: Some("https://github.com/serde-rs/serde".into()),
148///     documentation: Some("https://docs.rs/serde".into()),
149///     max_version: "1.0.214".into(),
150/// };
151///
152/// assert_eq!(info.name, "serde");
153/// ```
154#[derive(Debug, Clone)]
155pub struct CrateInfo {
156    pub name: String,
157    pub description: Option<String>,
158    pub repository: Option<String>,
159    pub documentation: Option<String>,
160    pub max_version: String,
161}
162
163// Trait implementations for deps-core integration
164
165impl deps_core::Dependency for ParsedDependency {
166    fn name(&self) -> &str {
167        &self.name
168    }
169
170    fn name_range(&self) -> Range {
171        self.name_range
172    }
173
174    fn version_requirement(&self) -> Option<&str> {
175        self.version_req.as_deref()
176    }
177
178    fn version_range(&self) -> Option<Range> {
179        self.version_range
180    }
181
182    fn source(&self) -> deps_core::parser::DependencySource {
183        match &self.source {
184            DependencySource::Registry => deps_core::parser::DependencySource::Registry,
185            DependencySource::Git { url, rev } => deps_core::parser::DependencySource::Git {
186                url: url.clone(),
187                rev: rev.clone(),
188            },
189            DependencySource::Path { path } => {
190                deps_core::parser::DependencySource::Path { path: path.clone() }
191            }
192        }
193    }
194
195    fn features(&self) -> &[String] {
196        &self.features
197    }
198
199    fn as_any(&self) -> &dyn Any {
200        self
201    }
202}
203
204impl deps_core::Version for CargoVersion {
205    fn version_string(&self) -> &str {
206        &self.num
207    }
208
209    fn is_yanked(&self) -> bool {
210        self.yanked
211    }
212
213    fn features(&self) -> Vec<String> {
214        self.features.keys().cloned().collect()
215    }
216
217    fn as_any(&self) -> &dyn Any {
218        self
219    }
220}
221
222impl deps_core::Metadata for CrateInfo {
223    fn name(&self) -> &str {
224        &self.name
225    }
226
227    fn description(&self) -> Option<&str> {
228        self.description.as_deref()
229    }
230
231    fn repository(&self) -> Option<&str> {
232        self.repository.as_deref()
233    }
234
235    fn documentation(&self) -> Option<&str> {
236        self.documentation.as_deref()
237    }
238
239    fn latest_version(&self) -> &str {
240        &self.max_version
241    }
242
243    fn as_any(&self) -> &dyn Any {
244        self
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_dependency_source_variants() {
254        let registry = DependencySource::Registry;
255        let git = DependencySource::Git {
256            url: "https://github.com/user/repo".into(),
257            rev: Some("main".into()),
258        };
259        let path = DependencySource::Path {
260            path: "../local".into(),
261        };
262
263        assert!(matches!(registry, DependencySource::Registry));
264        assert!(matches!(git, DependencySource::Git { .. }));
265        assert!(matches!(path, DependencySource::Path { .. }));
266    }
267
268    #[test]
269    fn test_dependency_section_variants() {
270        let deps = DependencySection::Dependencies;
271        let dev_deps = DependencySection::DevDependencies;
272        let build_deps = DependencySection::BuildDependencies;
273        let workspace_deps = DependencySection::WorkspaceDependencies;
274
275        assert!(matches!(deps, DependencySection::Dependencies));
276        assert!(matches!(dev_deps, DependencySection::DevDependencies));
277        assert!(matches!(build_deps, DependencySection::BuildDependencies));
278        assert!(matches!(
279            workspace_deps,
280            DependencySection::WorkspaceDependencies
281        ));
282    }
283
284    #[test]
285    fn test_cargo_version_creation() {
286        let version = CargoVersion {
287            num: "1.0.0".into(),
288            yanked: false,
289            features: HashMap::new(),
290        };
291
292        assert_eq!(version.num, "1.0.0");
293        assert!(!version.yanked);
294        assert!(version.features.is_empty());
295    }
296}