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: String,
12    pub name_range: Range,
13    pub version_req: Option<String>,
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: String,
47    pub timestamp: Option<u64>,
48}
49
50#[derive(Debug, Clone)]
51pub struct ArtifactInfo {
52    pub group_id: String,
53    pub artifact_id: String,
54    /// "{groupId}:{artifactId}"
55    pub name: String,
56    pub description: Option<String>,
57    pub latest_version: String,
58    pub repository: Option<String>,
59}
60
61// deps-core trait implementations
62
63impl deps_core::DependencyInfo for MavenDependency {
64    fn name(&self) -> &str {
65        &self.name
66    }
67
68    fn name_range(&self) -> Range {
69        self.name_range
70    }
71
72    fn version_requirement(&self) -> Option<&str> {
73        self.version_req.as_deref()
74    }
75
76    fn version_range(&self) -> Option<Range> {
77        self.version_range
78    }
79
80    fn source(&self) -> deps_core::parser::DependencySource {
81        deps_core::parser::DependencySource::Registry
82    }
83
84    fn features(&self) -> &[String] {
85        &[]
86    }
87}
88
89impl deps_core::Dependency for MavenDependency {
90    fn name(&self) -> &str {
91        &self.name
92    }
93
94    fn name_range(&self) -> Range {
95        self.name_range
96    }
97
98    fn version_requirement(&self) -> Option<&str> {
99        self.version_req.as_deref()
100    }
101
102    fn version_range(&self) -> Option<Range> {
103        self.version_range
104    }
105
106    fn source(&self) -> deps_core::parser::DependencySource {
107        deps_core::parser::DependencySource::Registry
108    }
109
110    fn features(&self) -> &[String] {
111        &[]
112    }
113
114    fn as_any(&self) -> &dyn Any {
115        self
116    }
117}
118
119impl deps_core::Version for MavenVersion {
120    fn version_string(&self) -> &str {
121        &self.version
122    }
123
124    fn is_yanked(&self) -> bool {
125        // Maven Central does not support version retraction
126        false
127    }
128
129    fn is_prerelease(&self) -> bool {
130        crate::version::is_prerelease(&self.version)
131    }
132
133    fn features(&self) -> Vec<String> {
134        vec![]
135    }
136
137    fn as_any(&self) -> &dyn Any {
138        self
139    }
140}
141
142impl deps_core::Metadata for ArtifactInfo {
143    fn name(&self) -> &str {
144        &self.name
145    }
146
147    fn description(&self) -> Option<&str> {
148        self.description.as_deref()
149    }
150
151    fn repository(&self) -> Option<&str> {
152        self.repository.as_deref()
153    }
154
155    fn documentation(&self) -> Option<&str> {
156        None
157    }
158
159    fn latest_version(&self) -> &str {
160        &self.latest_version
161    }
162
163    fn as_any(&self) -> &dyn Any {
164        self
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use tower_lsp_server::ls_types::Position;
172
173    fn test_dep() -> MavenDependency {
174        MavenDependency {
175            group_id: "org.apache.commons".into(),
176            artifact_id: "commons-lang3".into(),
177            name: "org.apache.commons:commons-lang3".into(),
178            name_range: Range::new(Position::new(5, 4), Position::new(5, 17)),
179            version_req: Some("3.14.0".into()),
180            version_range: Some(Range::new(Position::new(7, 13), Position::new(7, 19))),
181            scope: MavenScope::Compile,
182        }
183    }
184
185    #[test]
186    fn test_scope_variants() {
187        use std::str::FromStr;
188        assert!(matches!(
189            "test".parse::<MavenScope>().unwrap(),
190            MavenScope::Test
191        ));
192        assert!(matches!(
193            "runtime".parse::<MavenScope>().unwrap(),
194            MavenScope::Runtime
195        ));
196        assert!(matches!(
197            "provided".parse::<MavenScope>().unwrap(),
198            MavenScope::Provided
199        ));
200        assert!(matches!(
201            "system".parse::<MavenScope>().unwrap(),
202            MavenScope::System
203        ));
204        assert!(matches!(
205            "import".parse::<MavenScope>().unwrap(),
206            MavenScope::Import
207        ));
208        assert!(matches!(
209            "compile".parse::<MavenScope>().unwrap(),
210            MavenScope::Compile
211        ));
212        assert!(matches!(
213            "unknown".parse::<MavenScope>().unwrap(),
214            MavenScope::Compile
215        ));
216        let _ = MavenScope::from_str; // ensure trait is accessible
217    }
218
219    #[test]
220    fn test_scope_default() {
221        assert!(matches!(MavenScope::default(), MavenScope::Compile));
222    }
223
224    #[test]
225    fn test_dependency_trait() {
226        use deps_core::Dependency;
227
228        let dep = test_dep();
229        assert_eq!(dep.name(), "org.apache.commons:commons-lang3");
230        assert_eq!(dep.version_requirement(), Some("3.14.0"));
231        assert!(dep.features().is_empty());
232        assert!(dep.as_any().is::<MavenDependency>());
233        assert!(matches!(
234            dep.source(),
235            deps_core::parser::DependencySource::Registry
236        ));
237    }
238
239    #[test]
240    fn test_dependency_info_trait() {
241        use deps_core::DependencyInfo;
242
243        let dep = test_dep();
244        assert_eq!(dep.name(), "org.apache.commons:commons-lang3");
245        assert!(dep.version_range().is_some());
246        assert!(matches!(
247            dep.source(),
248            deps_core::parser::DependencySource::Registry
249        ));
250    }
251
252    #[test]
253    fn test_dependency_without_version() {
254        use deps_core::Dependency;
255
256        let dep = MavenDependency {
257            group_id: "com.example".into(),
258            artifact_id: "test".into(),
259            name: "com.example:test".into(),
260            name_range: Range::default(),
261            version_req: None,
262            version_range: None,
263            scope: MavenScope::Compile,
264        };
265        assert!(dep.version_requirement().is_none());
266        assert!(dep.version_range().is_none());
267    }
268
269    #[test]
270    fn test_version_trait() {
271        use deps_core::Version;
272
273        let ver = MavenVersion {
274            version: "3.14.0".into(),
275            timestamp: Some(1_699_000_000),
276        };
277        assert_eq!(ver.version_string(), "3.14.0");
278        assert!(!ver.is_yanked());
279        assert!(!ver.is_prerelease());
280        assert!(ver.features().is_empty());
281        assert!(ver.as_any().is::<MavenVersion>());
282    }
283
284    #[test]
285    fn test_version_prerelease() {
286        use deps_core::Version;
287
288        let ver = MavenVersion {
289            version: "1.0.0-SNAPSHOT".into(),
290            timestamp: None,
291        };
292        assert!(ver.is_prerelease());
293
294        let ver = MavenVersion {
295            version: "2.0.0-M1".into(),
296            timestamp: None,
297        };
298        assert!(ver.is_prerelease());
299    }
300
301    #[test]
302    fn test_metadata_trait() {
303        use deps_core::Metadata;
304
305        let info = ArtifactInfo {
306            group_id: "org.apache.commons".into(),
307            artifact_id: "commons-lang3".into(),
308            name: "org.apache.commons:commons-lang3".into(),
309            description: Some("Apache Commons Lang".into()),
310            latest_version: "3.14.0".into(),
311            repository: None,
312        };
313        assert_eq!(info.name(), "org.apache.commons:commons-lang3");
314        assert_eq!(info.description(), Some("Apache Commons Lang"));
315        assert_eq!(info.latest_version(), "3.14.0");
316        assert!(info.repository().is_none());
317        assert!(info.documentation().is_none());
318        assert!(info.as_any().is::<ArtifactInfo>());
319    }
320}