Skip to main content

deps_cargo/
types.rs

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