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}