use crate::error::Result;
use std::any::Any;
use std::pin::Pin;
type BoxFuture<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
pub trait Registry: Send + Sync {
fn get_versions<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result<Vec<Box<dyn Version>>>>;
fn get_latest_matching<'a>(
&'a self,
name: &'a str,
req: &'a str,
) -> BoxFuture<'a, Result<Option<Box<dyn Version>>>>;
fn search<'a>(
&'a self,
query: &'a str,
limit: usize,
) -> BoxFuture<'a, Result<Vec<Box<dyn Metadata>>>>;
fn package_url(&self, name: &str) -> String;
fn as_any(&self) -> &dyn Any;
}
pub trait Version: Send + Sync {
fn version_string(&self) -> &str;
fn is_yanked(&self) -> bool;
fn is_prerelease(&self) -> bool {
let v = self.version_string().to_lowercase();
v.contains("-alpha")
|| v.contains("-beta")
|| v.contains("-rc")
|| v.contains("-dev")
|| v.contains("-pre")
|| v.contains("-snapshot")
|| v.contains("-canary")
|| v.contains("-nightly")
}
fn features(&self) -> Vec<String> {
vec![]
}
fn as_any(&self) -> &dyn Any;
fn is_stable(&self) -> bool {
!self.is_yanked() && !self.is_prerelease()
}
}
pub fn find_latest_stable(versions: &[Box<dyn Version>]) -> Option<&dyn Version> {
versions.iter().find(|v| v.is_stable()).map(|v| v.as_ref())
}
pub trait Metadata: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> Option<&str>;
fn repository(&self) -> Option<&str>;
fn documentation(&self) -> Option<&str>;
fn latest_version(&self) -> &str;
fn as_any(&self) -> &dyn Any;
}
#[cfg(test)]
mod tests {
use super::*;
struct MockVersion {
version: String,
yanked: bool,
}
impl Version for MockVersion {
fn version_string(&self) -> &str {
&self.version
}
fn is_yanked(&self) -> bool {
self.yanked
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[test]
fn test_version_default_features() {
let version = MockVersion {
version: "1.0.0".into(),
yanked: false,
};
assert_eq!(version.features(), Vec::<String>::new());
}
#[test]
fn test_version_trait_object() {
let version = MockVersion {
version: "1.2.3".into(),
yanked: false,
};
let boxed: Box<dyn Version> = Box::new(version);
assert_eq!(boxed.version_string(), "1.2.3");
assert!(!boxed.is_yanked());
}
#[test]
fn test_version_downcast() {
let version = MockVersion {
version: "1.0.0".into(),
yanked: true,
};
let boxed: Box<dyn Version> = Box::new(version);
let any = boxed.as_any();
assert!(any.is::<MockVersion>());
}
struct MockMetadata {
name: String,
latest: String,
}
impl Metadata for MockMetadata {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> Option<&str> {
None
}
fn repository(&self) -> Option<&str> {
None
}
fn documentation(&self) -> Option<&str> {
None
}
fn latest_version(&self) -> &str {
&self.latest
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[test]
fn test_metadata_trait_object() {
let metadata = MockMetadata {
name: "test-package".into(),
latest: "2.0.0".into(),
};
let boxed: Box<dyn Metadata> = Box::new(metadata);
assert_eq!(boxed.name(), "test-package");
assert_eq!(boxed.latest_version(), "2.0.0");
assert!(boxed.description().is_none());
assert!(boxed.repository().is_none());
assert!(boxed.documentation().is_none());
}
#[test]
fn test_metadata_with_full_info() {
struct FullMetadata {
name: String,
desc: String,
repo: String,
docs: String,
latest: String,
}
impl Metadata for FullMetadata {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> Option<&str> {
Some(&self.desc)
}
fn repository(&self) -> Option<&str> {
Some(&self.repo)
}
fn documentation(&self) -> Option<&str> {
Some(&self.docs)
}
fn latest_version(&self) -> &str {
&self.latest
}
fn as_any(&self) -> &dyn Any {
self
}
}
let meta = FullMetadata {
name: "serde".into(),
desc: "Serialization framework".into(),
repo: "https://github.com/serde-rs/serde".into(),
docs: "https://docs.rs/serde".into(),
latest: "1.0.214".into(),
};
assert_eq!(meta.description(), Some("Serialization framework"));
assert_eq!(meta.repository(), Some("https://github.com/serde-rs/serde"));
assert_eq!(meta.documentation(), Some("https://docs.rs/serde"));
}
#[test]
fn test_is_prerelease_alpha() {
let version = MockVersion {
version: "4.0.0-alpha.13".into(),
yanked: false,
};
assert!(version.is_prerelease());
}
#[test]
fn test_is_prerelease_beta() {
let version = MockVersion {
version: "2.0.0-beta.1".into(),
yanked: false,
};
assert!(version.is_prerelease());
}
#[test]
fn test_is_prerelease_rc() {
let version = MockVersion {
version: "1.5.0-rc.2".into(),
yanked: false,
};
assert!(version.is_prerelease());
}
#[test]
fn test_is_prerelease_dev() {
let version = MockVersion {
version: "3.0.0-dev".into(),
yanked: false,
};
assert!(version.is_prerelease());
}
#[test]
fn test_is_prerelease_canary() {
let version = MockVersion {
version: "5.0.0-canary".into(),
yanked: false,
};
assert!(version.is_prerelease());
}
#[test]
fn test_is_prerelease_nightly() {
let version = MockVersion {
version: "6.0.0-nightly".into(),
yanked: false,
};
assert!(version.is_prerelease());
}
#[test]
fn test_is_not_prerelease_stable() {
let version = MockVersion {
version: "1.2.3".into(),
yanked: false,
};
assert!(!version.is_prerelease());
}
#[test]
fn test_is_not_prerelease_patch() {
let version = MockVersion {
version: "1.0.214".into(),
yanked: false,
};
assert!(!version.is_prerelease());
}
#[test]
fn test_is_stable_true() {
let version = MockVersion {
version: "1.0.0".into(),
yanked: false,
};
assert!(version.is_stable());
}
#[test]
fn test_is_stable_false_yanked() {
let version = MockVersion {
version: "1.0.0".into(),
yanked: true,
};
assert!(!version.is_stable());
}
#[test]
fn test_is_stable_false_prerelease() {
let version = MockVersion {
version: "1.0.0-alpha.1".into(),
yanked: false,
};
assert!(!version.is_stable());
}
#[test]
fn test_find_latest_stable_skips_prerelease() {
let versions: Vec<Box<dyn Version>> = vec![
Box::new(MockVersion {
version: "2.0.0-alpha.1".into(),
yanked: false,
}),
Box::new(MockVersion {
version: "1.5.0".into(),
yanked: false,
}),
];
let latest = super::find_latest_stable(&versions);
assert_eq!(latest.map(|v| v.version_string()), Some("1.5.0"));
}
#[test]
fn test_find_latest_stable_skips_yanked() {
let versions: Vec<Box<dyn Version>> = vec![
Box::new(MockVersion {
version: "2.0.0".into(),
yanked: true,
}),
Box::new(MockVersion {
version: "1.5.0".into(),
yanked: false,
}),
];
let latest = super::find_latest_stable(&versions);
assert_eq!(latest.map(|v| v.version_string()), Some("1.5.0"));
}
#[test]
fn test_find_latest_stable_returns_first_stable() {
let versions: Vec<Box<dyn Version>> = vec![
Box::new(MockVersion {
version: "3.0.0-beta.1".into(),
yanked: false,
}),
Box::new(MockVersion {
version: "2.0.0".into(),
yanked: true,
}),
Box::new(MockVersion {
version: "1.5.0".into(),
yanked: false,
}),
Box::new(MockVersion {
version: "1.4.0".into(),
yanked: false,
}),
];
let latest = super::find_latest_stable(&versions);
assert_eq!(latest.map(|v| v.version_string()), Some("1.5.0"));
}
#[test]
fn test_find_latest_stable_empty_list() {
let versions: Vec<Box<dyn Version>> = vec![];
let latest = super::find_latest_stable(&versions);
assert!(latest.is_none());
}
#[test]
fn test_find_latest_stable_no_stable_versions() {
let versions: Vec<Box<dyn Version>> = vec![
Box::new(MockVersion {
version: "2.0.0-alpha.1".into(),
yanked: false,
}),
Box::new(MockVersion {
version: "1.0.0".into(),
yanked: true,
}),
];
let latest = super::find_latest_stable(&versions);
assert!(latest.is_none());
}
}