Skip to main content

deps_maven/
types.rs

1//! Domain types for Maven/pom.xml dependencies.
2
3use std::any::Any;
4use tower_lsp_server::ls_types::Range;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct MavenDependency {
8    pub group_id: String,
9    pub artifact_id: String,
10    /// Canonical identifier: "{groupId}:{artifactId}"
11    pub name: deps_core::PackageName,
12    pub name_range: Range,
13    pub version_req: Option<deps_core::VersionReq>,
14    pub version_range: Option<Range>,
15    pub scope: MavenScope,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Default)]
19pub enum MavenScope {
20    #[default]
21    Compile,
22    Test,
23    Runtime,
24    Provided,
25    System,
26    Import,
27}
28
29impl std::str::FromStr for MavenScope {
30    type Err = std::convert::Infallible;
31
32    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
33        Ok(match s.to_lowercase().as_str() {
34            "test" => Self::Test,
35            "runtime" => Self::Runtime,
36            "provided" => Self::Provided,
37            "system" => Self::System,
38            "import" => Self::Import,
39            _ => Self::Compile,
40        })
41    }
42}
43
44#[derive(Debug, Clone)]
45pub struct MavenVersion {
46    pub version: deps_core::ConcreteVersion,
47    /// When this version was published, from the `repo1.maven.org` directory listing.
48    ///
49    /// `None` whenever the listing could not be fetched or parsed (Google Maven and the
50    /// Gradle Plugin Portal never carry a date, and the fetch is disabled by
51    /// `freshness.enabled: false`) — this is graceful degradation, not an error.
52    pub published_at: Option<deps_core::PublishTime>,
53}
54
55#[derive(Debug, Clone)]
56pub struct ArtifactInfo {
57    pub group_id: String,
58    pub artifact_id: String,
59    /// "{groupId}:{artifactId}"
60    pub name: deps_core::PackageName,
61    pub description: Option<String>,
62    pub latest_version: deps_core::ConcreteVersion,
63    pub repository: Option<String>,
64}
65
66// deps-core trait implementations
67
68impl deps_core::Dependency for MavenDependency {
69    fn name(&self) -> &deps_core::PackageName {
70        &self.name
71    }
72
73    fn name_range(&self) -> Range {
74        self.name_range
75    }
76
77    fn version_requirement(&self) -> Option<&deps_core::VersionReq> {
78        self.version_req.as_ref()
79    }
80
81    fn version_range(&self) -> Option<Range> {
82        self.version_range
83    }
84
85    fn source(&self) -> deps_core::parser::DependencySource {
86        deps_core::parser::DependencySource::Registry
87    }
88
89    fn features(&self) -> &[String] {
90        &[]
91    }
92
93    fn as_any(&self) -> &dyn Any {
94        self
95    }
96}
97
98impl deps_core::Version for MavenVersion {
99    fn version_string(&self) -> &deps_core::ConcreteVersion {
100        &self.version
101    }
102
103    // No `removal_status` override: Maven Central does not support version
104    // retraction, so the default `RemovalStatus::Available` is correct.
105    fn is_prerelease(&self) -> bool {
106        crate::version::is_prerelease(self.version.as_str())
107    }
108
109    fn features(&self) -> Vec<String> {
110        vec![]
111    }
112
113    fn as_any(&self) -> &dyn Any {
114        self
115    }
116
117    fn published_at(&self) -> Option<deps_core::PublishTime> {
118        self.published_at
119    }
120}
121
122impl deps_core::Metadata for ArtifactInfo {
123    fn name(&self) -> &deps_core::PackageName {
124        &self.name
125    }
126
127    fn description(&self) -> Option<&str> {
128        self.description.as_deref()
129    }
130
131    fn repository(&self) -> Option<&str> {
132        self.repository.as_deref()
133    }
134
135    fn documentation(&self) -> Option<&str> {
136        None
137    }
138
139    fn latest_version(&self) -> &deps_core::ConcreteVersion {
140        &self.latest_version
141    }
142
143    fn as_any(&self) -> &dyn Any {
144        self
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use std::assert_matches;
152    use tower_lsp_server::ls_types::Position;
153
154    fn test_dep() -> MavenDependency {
155        MavenDependency {
156            group_id: "org.apache.commons".into(),
157            artifact_id: "commons-lang3".into(),
158            name: "org.apache.commons:commons-lang3".into(),
159            name_range: Range::new(Position::new(5, 4), Position::new(5, 17)),
160            version_req: Some("3.14.0".into()),
161            version_range: Some(Range::new(Position::new(7, 13), Position::new(7, 19))),
162            scope: MavenScope::Compile,
163        }
164    }
165
166    #[test]
167    fn test_scope_variants() {
168        use std::str::FromStr;
169        assert_matches!("test".parse::<MavenScope>().unwrap(), MavenScope::Test);
170        assert_matches!(
171            "runtime".parse::<MavenScope>().unwrap(),
172            MavenScope::Runtime
173        );
174        assert_matches!(
175            "provided".parse::<MavenScope>().unwrap(),
176            MavenScope::Provided
177        );
178        assert_matches!("system".parse::<MavenScope>().unwrap(), MavenScope::System);
179        assert_matches!("import".parse::<MavenScope>().unwrap(), MavenScope::Import);
180        assert_matches!(
181            "compile".parse::<MavenScope>().unwrap(),
182            MavenScope::Compile
183        );
184        assert_matches!(
185            "unknown".parse::<MavenScope>().unwrap(),
186            MavenScope::Compile
187        );
188        let _ = MavenScope::from_str; // ensure trait is accessible
189    }
190
191    #[test]
192    fn test_scope_default() {
193        assert_matches!(MavenScope::default(), MavenScope::Compile);
194    }
195
196    #[test]
197    fn test_dependency_trait() {
198        use deps_core::Dependency;
199
200        let dep = test_dep();
201        assert_eq!(dep.name(), "org.apache.commons:commons-lang3");
202        assert_eq!(
203            dep.version_requirement().map(deps_core::VersionReq::as_str),
204            Some("3.14.0")
205        );
206        assert!(dep.features().is_empty());
207        assert!(dep.as_any().is::<MavenDependency>());
208        assert_matches!(dep.source(), deps_core::parser::DependencySource::Registry);
209    }
210
211    #[test]
212    fn test_dependency_info_trait() {
213        use deps_core::Dependency;
214
215        let dep = test_dep();
216        assert_eq!(dep.name(), "org.apache.commons:commons-lang3");
217        assert!(dep.version_range().is_some());
218        assert_matches!(dep.source(), deps_core::parser::DependencySource::Registry);
219    }
220
221    #[test]
222    fn test_dependency_without_version() {
223        use deps_core::Dependency;
224
225        let dep = MavenDependency {
226            group_id: "com.example".into(),
227            artifact_id: "test".into(),
228            name: "com.example:test".into(),
229            name_range: Range::default(),
230            version_req: None,
231            version_range: None,
232            scope: MavenScope::Compile,
233        };
234        assert!(dep.version_requirement().is_none());
235        assert!(dep.version_range().is_none());
236    }
237
238    #[test]
239    fn test_version_trait() {
240        use deps_core::Version;
241
242        let ver = MavenVersion {
243            version: "3.14.0".into(),
244            published_at: Some(deps_core::PublishTime::from_unix_secs(1_699_000_000)),
245        };
246        assert_eq!(ver.version_string(), "3.14.0");
247        assert!(!ver.removal_status().blocks_resolution());
248        assert!(!ver.is_prerelease());
249        assert!(ver.features().is_empty());
250        assert!(ver.as_any().is::<MavenVersion>());
251        assert_eq!(
252            ver.published_at(),
253            Some(deps_core::PublishTime::from_unix_secs(1_699_000_000))
254        );
255    }
256
257    #[test]
258    fn test_version_prerelease() {
259        use deps_core::Version;
260
261        let ver = MavenVersion {
262            version: "1.0.0-SNAPSHOT".into(),
263            published_at: None,
264        };
265        assert!(ver.is_prerelease());
266
267        let ver = MavenVersion {
268            version: "2.0.0-M1".into(),
269            published_at: None,
270        };
271        assert!(ver.is_prerelease());
272    }
273
274    #[test]
275    fn test_metadata_trait() {
276        use deps_core::Metadata;
277
278        let info = ArtifactInfo {
279            group_id: "org.apache.commons".into(),
280            artifact_id: "commons-lang3".into(),
281            name: deps_core::PackageName::new("org.apache.commons:commons-lang3"),
282            description: Some("Apache Commons Lang".into()),
283            latest_version: "3.14.0".into(),
284            repository: None,
285        };
286        assert_eq!(info.name(), "org.apache.commons:commons-lang3");
287        assert_eq!(info.description(), Some("Apache Commons Lang"));
288        assert_eq!(info.latest_version(), "3.14.0");
289        assert!(info.repository().is_none());
290        assert!(info.documentation().is_none());
291        assert!(info.as_any().is::<ArtifactInfo>());
292    }
293}