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