1use std::any::Any;
4use tower_lsp_server::ls_types::Range;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct BundlerDependency {
9 pub name: deps_core::PackageName,
10 pub name_range: Range,
11 pub version_req: Option<deps_core::VersionReq>,
12 pub version_range: Option<Range>,
13 pub group: DependencyGroup,
14 pub source: DependencySource,
15 pub platforms: Vec<String>,
16 pub require: Option<String>,
17}
18
19pub use deps_core::parser::DependencySource;
20
21#[derive(Debug, Clone, PartialEq, Eq, Default)]
23pub enum DependencyGroup {
24 #[default]
26 Default,
27 Development,
29 Test,
31 Production,
33 Custom(String),
35}
36
37#[derive(Debug, Clone)]
39pub struct BundlerVersion {
40 pub number: deps_core::ConcreteVersion,
41 pub prerelease: bool,
42 pub yanked: bool,
43 pub published_at: Option<deps_core::PublishTime>,
49 pub platform: String,
50}
51
52impl BundlerVersion {
53 pub fn is_stable(&self) -> bool {
55 !self.prerelease && !self.yanked
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct GemInfo {
62 pub name: deps_core::PackageName,
63 pub info: Option<String>,
64 pub homepage_uri: Option<String>,
65 pub source_code_uri: Option<String>,
66 pub documentation_uri: Option<String>,
67 pub version: deps_core::ConcreteVersion,
68 pub licenses: Vec<String>,
69 pub authors: Option<String>,
70 pub downloads: u64,
71}
72
73impl deps_core::Dependency for BundlerDependency {
76 fn name(&self) -> &deps_core::PackageName {
77 &self.name
78 }
79
80 fn name_range(&self) -> Range {
81 self.name_range
82 }
83
84 fn version_requirement(&self) -> Option<&deps_core::VersionReq> {
85 self.version_req.as_ref()
86 }
87
88 fn version_range(&self) -> Option<Range> {
89 self.version_range
90 }
91
92 fn source(&self) -> deps_core::parser::DependencySource {
93 self.source.clone()
94 }
95
96 fn features(&self) -> &[String] {
97 &[]
98 }
99
100 fn as_any(&self) -> &dyn Any {
101 self
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use std::assert_matches;
109 use tower_lsp_server::ls_types::Position;
110
111 fn create_test_dependency(source: DependencySource) -> BundlerDependency {
112 BundlerDependency {
113 name: "test_gem".into(),
114 name_range: Range::new(Position::new(1, 5), Position::new(1, 13)),
115 version_req: Some("~> 1.0".into()),
116 version_range: Some(Range::new(Position::new(1, 17), Position::new(1, 23))),
117 group: DependencyGroup::Default,
118 source,
119 platforms: vec![],
120 require: None,
121 }
122 }
123
124 #[test]
125 fn test_dependency_source_variants() {
126 let registry = DependencySource::Registry;
127 let git = DependencySource::Git {
128 url: "https://github.com/rails/rails".into(),
129 rev: Some("main".into()),
130 };
131 let path = DependencySource::Path {
132 path: "../local".into(),
133 };
134 let custom = DependencySource::CustomRegistry {
135 url: "https://custom.gem.source".into(),
136 };
137
138 assert!(registry.is_registry());
139 assert!(!git.is_registry());
140 assert!(!path.is_registry());
141 assert!(custom.is_registry());
142 }
143
144 #[test]
145 fn test_dependency_group_variants() {
146 let default = DependencyGroup::Default;
147 let dev = DependencyGroup::Development;
148 let test = DependencyGroup::Test;
149 let prod = DependencyGroup::Production;
150 let custom = DependencyGroup::Custom("staging".into());
151
152 assert_matches!(default, DependencyGroup::Default);
153 assert_matches!(dev, DependencyGroup::Development);
154 assert_matches!(test, DependencyGroup::Test);
155 assert_matches!(prod, DependencyGroup::Production);
156 assert_matches!(custom, DependencyGroup::Custom(_));
157 }
158
159 #[test]
160 fn test_dependency_group_default() {
161 let group = DependencyGroup::default();
162 assert_matches!(group, DependencyGroup::Default);
163 }
164
165 #[test]
166 fn test_bundler_version_creation() {
167 let version = BundlerVersion {
168 number: "7.0.8".into(),
169 prerelease: false,
170 yanked: false,
171 published_at: Some(deps_core::PublishTime::from_unix_secs(1_694_208_000)),
172 platform: "ruby".into(),
173 };
174
175 assert_eq!(version.number, "7.0.8");
176 assert!(!version.yanked);
177 assert!(version.is_stable());
178 }
179
180 #[test]
181 fn test_bundler_version_prerelease() {
182 let version = BundlerVersion {
183 number: "7.1.0.beta1".into(),
184 prerelease: true,
185 yanked: false,
186 published_at: None,
187 platform: "ruby".into(),
188 };
189
190 assert!(version.prerelease);
191 assert!(!version.is_stable());
192 }
193
194 #[test]
195 fn test_bundler_version_yanked() {
196 let version = BundlerVersion {
197 number: "1.0.0".into(),
198 prerelease: false,
199 yanked: true,
200 published_at: None,
201 platform: "ruby".into(),
202 };
203
204 assert!(version.yanked);
205 assert!(!version.is_stable());
206 }
207
208 #[test]
209 fn test_bundler_dependency_trait() {
210 use deps_core::Dependency;
211
212 let dep = BundlerDependency {
213 name: "rails".into(),
214 name_range: Range::new(Position::new(1, 5), Position::new(1, 10)),
215 version_req: Some("~> 7.0".into()),
216 version_range: Some(Range::new(Position::new(1, 14), Position::new(1, 20))),
217 group: DependencyGroup::Default,
218 source: DependencySource::Registry,
219 platforms: vec![],
220 require: None,
221 };
222
223 assert_eq!(dep.name(), "rails");
224 assert_eq!(
225 dep.version_requirement().map(deps_core::VersionReq::as_str),
226 Some("~> 7.0")
227 );
228 }
229
230 #[test]
231 fn test_dependency_info_trait_registry() {
232 use deps_core::Dependency;
233
234 let dep = create_test_dependency(DependencySource::Registry);
235 assert_eq!(dep.name(), "test_gem");
236 assert_eq!(dep.name_range().start.line, 1);
237 assert_eq!(
238 dep.version_requirement().map(deps_core::VersionReq::as_str),
239 Some("~> 1.0")
240 );
241 assert!(dep.version_range().is_some());
242 assert!(dep.features().is_empty());
243 assert!(dep.source().is_registry());
244 }
245
246 #[test]
247 fn test_dependency_info_trait_git() {
248 use deps_core::Dependency;
249
250 let dep = create_test_dependency(DependencySource::Git {
251 url: "https://github.com/rails/rails".into(),
252 rev: Some("abc123".into()),
253 });
254
255 match dep.source() {
256 deps_core::parser::DependencySource::Git { url, rev } => {
257 assert_eq!(url, "https://github.com/rails/rails");
258 assert_eq!(rev, Some("abc123".to_string()));
259 }
260 _ => panic!("Expected Git source"),
261 }
262 }
263
264 #[test]
265 fn test_dependency_info_trait_path() {
266 use deps_core::Dependency;
267
268 let dep = create_test_dependency(DependencySource::Path {
269 path: "../local_gem".into(),
270 });
271
272 match dep.source() {
273 deps_core::parser::DependencySource::Path { path } => {
274 assert_eq!(path, "../local_gem");
275 }
276 _ => panic!("Expected Path source"),
277 }
278 }
279
280 #[test]
281 fn test_dependency_info_trait_custom_source() {
282 use deps_core::Dependency;
283
284 let dep = create_test_dependency(DependencySource::CustomRegistry {
285 url: "https://gems.example.com".into(),
286 });
287
288 assert!(dep.source().is_registry());
289 }
290
291 #[test]
292 fn test_dependency_trait_as_any() {
293 use deps_core::Dependency;
294
295 let dep = create_test_dependency(DependencySource::Registry);
296 let any = dep.as_any();
297 assert!(any.is::<BundlerDependency>());
298 assert!(any.downcast_ref::<BundlerDependency>().is_some());
299 }
300
301 #[test]
302 fn test_dependency_trait_source_conversions() {
303 use deps_core::Dependency;
304
305 let sources = vec![
306 DependencySource::Registry,
307 DependencySource::Git {
308 url: "https://github.com/test/repo".into(),
309 rev: Some("v1.0".into()),
310 },
311 DependencySource::Path {
312 path: "./local".into(),
313 },
314 DependencySource::CustomRegistry {
315 url: "https://custom.example.com".into(),
316 },
317 ];
318
319 for source in sources {
320 let dep = create_test_dependency(source);
321 let _ = dep.source();
322 }
323 }
324
325 #[test]
326 fn test_dependency_without_version() {
327 use deps_core::Dependency;
328
329 let dep = BundlerDependency {
330 name: "test".into(),
331 name_range: Range::default(),
332 version_req: None,
333 version_range: None,
334 group: DependencyGroup::Default,
335 source: DependencySource::Registry,
336 platforms: vec![],
337 require: None,
338 };
339
340 assert_eq!(dep.name(), "test");
341 assert!(dep.version_requirement().is_none());
342 assert!(dep.version_range().is_none());
343 }
344
345 #[test]
346 fn test_version_trait_implementation() {
347 use deps_core::Version;
348
349 let version = BundlerVersion {
350 number: "1.2.3".into(),
351 prerelease: false,
352 yanked: false,
353 published_at: Some(deps_core::PublishTime::from_unix_secs(1_704_067_200)),
354 platform: "ruby".into(),
355 };
356
357 assert_eq!(version.version_string(), "1.2.3");
358 assert!(!version.removal_status().blocks_resolution());
359 assert!(version.features().is_empty());
360 assert!(version.as_any().is::<BundlerVersion>());
361 }
362
363 #[test]
364 fn test_version_trait_yanked() {
365 use deps_core::Version;
366
367 let version = BundlerVersion {
368 number: "1.0.0".into(),
369 prerelease: false,
370 yanked: true,
371 published_at: None,
372 platform: "ruby".into(),
373 };
374
375 assert!(version.removal_status().blocks_resolution());
376 }
377
378 #[test]
379 fn test_metadata_trait_full() {
380 use deps_core::Metadata;
381
382 let gem = GemInfo {
383 name: "rails".into(),
384 info: Some("Full-stack web application framework".into()),
385 homepage_uri: Some("https://rubyonrails.org".into()),
386 source_code_uri: Some("https://github.com/rails/rails".into()),
387 documentation_uri: Some("https://api.rubyonrails.org".into()),
388 version: "7.0.8".into(),
389 licenses: vec!["MIT".into()],
390 authors: Some("DHH".into()),
391 downloads: 500_000_000,
392 };
393
394 assert_eq!(gem.name(), "rails");
395 assert_eq!(
396 gem.description(),
397 Some("Full-stack web application framework")
398 );
399 assert_eq!(gem.repository(), Some("https://github.com/rails/rails"));
400 assert_eq!(gem.documentation(), Some("https://api.rubyonrails.org"));
401 assert_eq!(gem.latest_version(), "7.0.8");
402 assert!(gem.as_any().is::<GemInfo>());
403 }
404
405 #[test]
406 fn test_metadata_trait_minimal() {
407 use deps_core::Metadata;
408
409 let gem = GemInfo {
410 name: "minimal".into(),
411 info: None,
412 homepage_uri: None,
413 source_code_uri: None,
414 documentation_uri: None,
415 version: "0.1.0".into(),
416 licenses: vec![],
417 authors: None,
418 downloads: 0,
419 };
420
421 assert_eq!(gem.name(), "minimal");
422 assert!(gem.description().is_none());
423 assert!(gem.repository().is_none());
424 assert!(gem.documentation().is_none());
425 assert_eq!(gem.latest_version(), "0.1.0");
426 }
427
428 #[test]
429 fn test_gem_info_fields() {
430 let gem = GemInfo {
431 name: "test".into(),
432 info: Some("Test gem".into()),
433 homepage_uri: Some("https://example.com".into()),
434 source_code_uri: Some("https://github.com/test/test".into()),
435 documentation_uri: Some("https://docs.example.com".into()),
436 version: "1.0.0".into(),
437 licenses: vec!["MIT".into(), "Apache-2.0".into()],
438 authors: Some("Test Author".into()),
439 downloads: 1000,
440 };
441
442 assert_eq!(gem.licenses.len(), 2);
443 assert_eq!(gem.authors, Some("Test Author".into()));
444 assert_eq!(gem.downloads, 1000);
445 }
446
447 #[test]
448 fn test_bundler_dependency_clone() {
449 let dep = create_test_dependency(DependencySource::Registry);
450 let cloned = dep.clone();
451 assert_eq!(dep, cloned);
452 }
453
454 #[test]
455 fn test_bundler_dependency_debug() {
456 let dep = create_test_dependency(DependencySource::Registry);
457 let debug_str = format!("{:?}", dep);
458 assert!(debug_str.contains("test_gem"));
459 }
460}