1use crate::error::Result;
2use std::any::Any;
3use std::pin::Pin;
4
5type BoxFuture<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
6
7pub trait Registry: Send + Sync {
77 fn get_versions<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result<Vec<Box<dyn Version>>>>;
88
89 fn get_latest_matching<'a>(
105 &'a self,
106 name: &'a str,
107 req: &'a str,
108 ) -> BoxFuture<'a, Result<Option<Box<dyn Version>>>>;
109
110 fn search<'a>(
118 &'a self,
119 query: &'a str,
120 limit: usize,
121 ) -> BoxFuture<'a, Result<Vec<Box<dyn Metadata>>>>;
122
123 fn package_url(&self, name: &str) -> String;
127
128 fn as_any(&self) -> &dyn Any;
130}
131
132pub trait Version: Send + Sync {
136 fn version_string(&self) -> &str;
138
139 fn is_yanked(&self) -> bool;
141
142 fn is_prerelease(&self) -> bool {
146 let v = self.version_string().to_lowercase();
147 v.contains("-alpha")
148 || v.contains("-beta")
149 || v.contains("-rc")
150 || v.contains("-dev")
151 || v.contains("-pre")
152 || v.contains("-snapshot")
153 || v.contains("-canary")
154 || v.contains("-nightly")
155 }
156
157 fn features(&self) -> Vec<String> {
159 vec![]
160 }
161
162 fn as_any(&self) -> &dyn Any;
164
165 fn is_stable(&self) -> bool {
167 !self.is_yanked() && !self.is_prerelease()
168 }
169}
170
171pub fn find_latest_stable(versions: &[Box<dyn Version>]) -> Option<&dyn Version> {
203 versions.iter().find(|v| v.is_stable()).map(|v| v.as_ref())
204}
205
206pub trait Metadata: Send + Sync {
210 fn name(&self) -> &str;
212
213 fn description(&self) -> Option<&str>;
215
216 fn repository(&self) -> Option<&str>;
218
219 fn documentation(&self) -> Option<&str>;
221
222 fn latest_version(&self) -> &str;
224
225 fn as_any(&self) -> &dyn Any;
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 struct MockVersion {
234 version: String,
235 yanked: bool,
236 }
237
238 impl Version for MockVersion {
239 fn version_string(&self) -> &str {
240 &self.version
241 }
242
243 fn is_yanked(&self) -> bool {
244 self.yanked
245 }
246
247 fn as_any(&self) -> &dyn Any {
248 self
249 }
250 }
251
252 #[test]
253 fn test_version_default_features() {
254 let version = MockVersion {
255 version: "1.0.0".into(),
256 yanked: false,
257 };
258
259 assert_eq!(version.features(), Vec::<String>::new());
260 }
261
262 #[test]
263 fn test_version_trait_object() {
264 let version = MockVersion {
265 version: "1.2.3".into(),
266 yanked: false,
267 };
268
269 let boxed: Box<dyn Version> = Box::new(version);
270 assert_eq!(boxed.version_string(), "1.2.3");
271 assert!(!boxed.is_yanked());
272 }
273
274 #[test]
275 fn test_version_downcast() {
276 let version = MockVersion {
277 version: "1.0.0".into(),
278 yanked: true,
279 };
280
281 let boxed: Box<dyn Version> = Box::new(version);
282 let any = boxed.as_any();
283
284 assert!(any.is::<MockVersion>());
285 }
286
287 struct MockMetadata {
288 name: String,
289 latest: String,
290 }
291
292 impl Metadata for MockMetadata {
293 fn name(&self) -> &str {
294 &self.name
295 }
296
297 fn description(&self) -> Option<&str> {
298 None
299 }
300
301 fn repository(&self) -> Option<&str> {
302 None
303 }
304
305 fn documentation(&self) -> Option<&str> {
306 None
307 }
308
309 fn latest_version(&self) -> &str {
310 &self.latest
311 }
312
313 fn as_any(&self) -> &dyn Any {
314 self
315 }
316 }
317
318 #[test]
319 fn test_metadata_trait_object() {
320 let metadata = MockMetadata {
321 name: "test-package".into(),
322 latest: "2.0.0".into(),
323 };
324
325 let boxed: Box<dyn Metadata> = Box::new(metadata);
326 assert_eq!(boxed.name(), "test-package");
327 assert_eq!(boxed.latest_version(), "2.0.0");
328 assert!(boxed.description().is_none());
329 assert!(boxed.repository().is_none());
330 assert!(boxed.documentation().is_none());
331 }
332
333 #[test]
334 fn test_metadata_with_full_info() {
335 struct FullMetadata {
336 name: String,
337 desc: String,
338 repo: String,
339 docs: String,
340 latest: String,
341 }
342
343 impl Metadata for FullMetadata {
344 fn name(&self) -> &str {
345 &self.name
346 }
347 fn description(&self) -> Option<&str> {
348 Some(&self.desc)
349 }
350 fn repository(&self) -> Option<&str> {
351 Some(&self.repo)
352 }
353 fn documentation(&self) -> Option<&str> {
354 Some(&self.docs)
355 }
356 fn latest_version(&self) -> &str {
357 &self.latest
358 }
359 fn as_any(&self) -> &dyn Any {
360 self
361 }
362 }
363
364 let meta = FullMetadata {
365 name: "serde".into(),
366 desc: "Serialization framework".into(),
367 repo: "https://github.com/serde-rs/serde".into(),
368 docs: "https://docs.rs/serde".into(),
369 latest: "1.0.214".into(),
370 };
371
372 assert_eq!(meta.description(), Some("Serialization framework"));
373 assert_eq!(meta.repository(), Some("https://github.com/serde-rs/serde"));
374 assert_eq!(meta.documentation(), Some("https://docs.rs/serde"));
375 }
376
377 #[test]
378 fn test_is_prerelease_alpha() {
379 let version = MockVersion {
380 version: "4.0.0-alpha.13".into(),
381 yanked: false,
382 };
383 assert!(version.is_prerelease());
384 }
385
386 #[test]
387 fn test_is_prerelease_beta() {
388 let version = MockVersion {
389 version: "2.0.0-beta.1".into(),
390 yanked: false,
391 };
392 assert!(version.is_prerelease());
393 }
394
395 #[test]
396 fn test_is_prerelease_rc() {
397 let version = MockVersion {
398 version: "1.5.0-rc.2".into(),
399 yanked: false,
400 };
401 assert!(version.is_prerelease());
402 }
403
404 #[test]
405 fn test_is_prerelease_dev() {
406 let version = MockVersion {
407 version: "3.0.0-dev".into(),
408 yanked: false,
409 };
410 assert!(version.is_prerelease());
411 }
412
413 #[test]
414 fn test_is_prerelease_canary() {
415 let version = MockVersion {
416 version: "5.0.0-canary".into(),
417 yanked: false,
418 };
419 assert!(version.is_prerelease());
420 }
421
422 #[test]
423 fn test_is_prerelease_nightly() {
424 let version = MockVersion {
425 version: "6.0.0-nightly".into(),
426 yanked: false,
427 };
428 assert!(version.is_prerelease());
429 }
430
431 #[test]
432 fn test_is_not_prerelease_stable() {
433 let version = MockVersion {
434 version: "1.2.3".into(),
435 yanked: false,
436 };
437 assert!(!version.is_prerelease());
438 }
439
440 #[test]
441 fn test_is_not_prerelease_patch() {
442 let version = MockVersion {
443 version: "1.0.214".into(),
444 yanked: false,
445 };
446 assert!(!version.is_prerelease());
447 }
448
449 #[test]
450 fn test_is_stable_true() {
451 let version = MockVersion {
452 version: "1.0.0".into(),
453 yanked: false,
454 };
455 assert!(version.is_stable());
456 }
457
458 #[test]
459 fn test_is_stable_false_yanked() {
460 let version = MockVersion {
461 version: "1.0.0".into(),
462 yanked: true,
463 };
464 assert!(!version.is_stable());
465 }
466
467 #[test]
468 fn test_is_stable_false_prerelease() {
469 let version = MockVersion {
470 version: "1.0.0-alpha.1".into(),
471 yanked: false,
472 };
473 assert!(!version.is_stable());
474 }
475
476 #[test]
477 fn test_find_latest_stable_skips_prerelease() {
478 let versions: Vec<Box<dyn Version>> = vec![
479 Box::new(MockVersion {
480 version: "2.0.0-alpha.1".into(),
481 yanked: false,
482 }),
483 Box::new(MockVersion {
484 version: "1.5.0".into(),
485 yanked: false,
486 }),
487 ];
488 let latest = super::find_latest_stable(&versions);
489 assert_eq!(latest.map(|v| v.version_string()), Some("1.5.0"));
490 }
491
492 #[test]
493 fn test_find_latest_stable_skips_yanked() {
494 let versions: Vec<Box<dyn Version>> = vec![
495 Box::new(MockVersion {
496 version: "2.0.0".into(),
497 yanked: true,
498 }),
499 Box::new(MockVersion {
500 version: "1.5.0".into(),
501 yanked: false,
502 }),
503 ];
504 let latest = super::find_latest_stable(&versions);
505 assert_eq!(latest.map(|v| v.version_string()), Some("1.5.0"));
506 }
507
508 #[test]
509 fn test_find_latest_stable_returns_first_stable() {
510 let versions: Vec<Box<dyn Version>> = vec![
511 Box::new(MockVersion {
512 version: "3.0.0-beta.1".into(),
513 yanked: false,
514 }),
515 Box::new(MockVersion {
516 version: "2.0.0".into(),
517 yanked: true,
518 }),
519 Box::new(MockVersion {
520 version: "1.5.0".into(),
521 yanked: false,
522 }),
523 Box::new(MockVersion {
524 version: "1.4.0".into(),
525 yanked: false,
526 }),
527 ];
528 let latest = super::find_latest_stable(&versions);
529 assert_eq!(latest.map(|v| v.version_string()), Some("1.5.0"));
530 }
531
532 #[test]
533 fn test_find_latest_stable_empty_list() {
534 let versions: Vec<Box<dyn Version>> = vec![];
535 let latest = super::find_latest_stable(&versions);
536 assert!(latest.is_none());
537 }
538
539 #[test]
540 fn test_find_latest_stable_no_stable_versions() {
541 let versions: Vec<Box<dyn Version>> = vec![
542 Box::new(MockVersion {
543 version: "2.0.0-alpha.1".into(),
544 yanked: false,
545 }),
546 Box::new(MockVersion {
547 version: "1.0.0".into(),
548 yanked: true,
549 }),
550 ];
551 let latest = super::find_latest_stable(&versions);
552 assert!(latest.is_none());
553 }
554}