use crate::error::Result;
use crate::parser::DependencySource;
use crate::{ConcreteVersion, PackageName, VersionReq};
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 PackageName,
) -> BoxFuture<'a, Result<Vec<Box<dyn Version>>>>;
fn get_versions_with<'a>(
&'a self,
name: &'a PackageName,
freshness: crate::freshness::FreshnessSettings,
) -> BoxFuture<'a, Result<Vec<Box<dyn Version>>>> {
let _ = freshness;
self.get_versions(name)
}
fn get_versions_from<'a>(
&'a self,
name: &'a PackageName,
source: &'a DependencySource,
freshness: crate::freshness::FreshnessSettings,
) -> BoxFuture<'a, Result<Vec<Box<dyn Version>>>> {
let _ = source;
self.get_versions_with(name, freshness)
}
fn get_latest_matching<'a>(
&'a self,
name: &'a PackageName,
req: &'a VersionReq,
) -> BoxFuture<'a, Result<Option<Box<dyn Version>>>>;
fn get_latest_matching_with_context<'a>(
&'a self,
name: &'a PackageName,
req: &'a VersionReq,
minimum_stability: Option<&'a str>,
) -> BoxFuture<'a, Result<Option<Box<dyn Version>>>> {
let _ = minimum_stability;
self.get_latest_matching(name, req)
}
fn get_latest_matching_from<'a>(
&'a self,
name: &'a PackageName,
source: &'a DependencySource,
req: &'a VersionReq,
minimum_stability: Option<&'a str>,
) -> BoxFuture<'a, Result<Option<Box<dyn Version>>>> {
let _ = source;
self.get_latest_matching_with_context(name, req, minimum_stability)
}
fn search<'a>(
&'a self,
query: &'a str,
limit: usize,
) -> BoxFuture<'a, Result<Vec<Box<dyn Metadata>>>>;
fn select_latest_matching(
&self,
_versions: &[Box<dyn Version>],
_req: &VersionReq,
) -> Option<usize> {
None
}
fn select_latest_matching_with_context(
&self,
versions: &[Box<dyn Version>],
req: &VersionReq,
minimum_stability: Option<&str>,
) -> Option<usize> {
let _ = minimum_stability;
self.select_latest_matching(versions, req)
}
fn reports_yanked(&self) -> bool {
true
}
fn as_any(&self) -> &dyn Any;
}
#[must_use]
pub fn has_default_prerelease_marker(version: &str) -> bool {
let v = version.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")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RemovalStatus {
Available,
AdvisoryDeprecated,
Yanked,
}
impl RemovalStatus {
#[must_use]
pub const fn blocks_resolution(self) -> bool {
matches!(self, Self::Yanked)
}
#[must_use]
pub const fn is_flagged(self) -> bool {
!matches!(self, Self::Available)
}
#[must_use]
pub const fn from_yanked(flag: bool) -> Self {
if flag { Self::Yanked } else { Self::Available }
}
#[must_use]
pub const fn from_advisory(flag: bool) -> Self {
if flag {
Self::AdvisoryDeprecated
} else {
Self::Available
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Deprecation {
pub reason: Option<String>,
pub replacement: Option<String>,
}
pub trait Version: Send + Sync {
fn version_string(&self) -> &ConcreteVersion;
fn removal_status(&self) -> RemovalStatus {
RemovalStatus::Available
}
fn deprecation(&self) -> Option<&Deprecation> {
None
}
fn is_prerelease(&self) -> bool {
has_default_prerelease_marker(self.version_string().as_str())
}
fn features(&self) -> Vec<String> {
vec![]
}
fn as_any(&self) -> &dyn Any;
fn is_stable(&self) -> bool {
!self.removal_status().blocks_resolution() && !self.is_prerelease()
}
fn published_at(&self) -> Option<crate::freshness::PublishTime> {
None
}
}
pub fn find_latest_stable(versions: &[Box<dyn Version>]) -> Option<&dyn Version> {
versions.iter().find(|v| v.is_stable()).map(|v| v.as_ref())
}
#[must_use]
pub fn is_existence_wildcard(req: &crate::VersionReq) -> bool {
is_existence_wildcard_str(req.as_str())
}
#[must_use]
pub fn is_existence_wildcard_str(req: &str) -> bool {
matches!(req.trim(), "" | "*")
}
#[must_use]
pub fn select_latest_for_existence<T>(
versions: &[T],
as_version: impl Fn(&T) -> &dyn Version,
) -> Option<usize> {
if versions.is_empty() {
return None;
}
Some(
versions
.iter()
.position(|v| {
let v = as_version(v);
!v.removal_status().is_flagged() && !v.is_prerelease()
})
.or_else(|| {
versions
.iter()
.position(|v| !as_version(v).removal_status().blocks_resolution())
})
.unwrap_or(0),
)
}
pub trait Metadata: Send + Sync {
fn name(&self) -> &crate::PackageName;
fn description(&self) -> Option<&str>;
fn repository(&self) -> Option<&str>;
fn documentation(&self) -> Option<&str>;
fn latest_version(&self) -> &ConcreteVersion;
fn as_any(&self) -> &dyn Any;
}
#[cfg(test)]
mod tests {
use super::*;
struct MockVersion {
version: ConcreteVersion,
yanked: bool,
}
impl Version for MockVersion {
fn version_string(&self) -> &ConcreteVersion {
&self.version
}
fn removal_status(&self) -> RemovalStatus {
RemovalStatus::from_yanked(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().as_str(), "1.2.3");
assert!(!boxed.removal_status().blocks_resolution());
}
#[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: crate::PackageName,
latest: ConcreteVersion,
}
impl Metadata for MockMetadata {
fn name(&self) -> &crate::PackageName {
&self.name
}
fn description(&self) -> Option<&str> {
None
}
fn repository(&self) -> Option<&str> {
None
}
fn documentation(&self) -> Option<&str> {
None
}
fn latest_version(&self) -> &ConcreteVersion {
&self.latest
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[test]
fn test_metadata_trait_object() {
let metadata = MockMetadata {
name: crate::PackageName::new("test-package"),
latest: "2.0.0".into(),
};
let boxed: Box<dyn Metadata> = Box::new(metadata);
assert_eq!(boxed.name(), "test-package");
assert_eq!(boxed.latest_version().as_str(), "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: crate::PackageName,
desc: String,
repo: String,
docs: String,
latest: ConcreteVersion,
}
impl Metadata for FullMetadata {
fn name(&self) -> &crate::PackageName {
&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) -> &ConcreteVersion {
&self.latest
}
fn as_any(&self) -> &dyn Any {
self
}
}
let meta = FullMetadata {
name: crate::PackageName::new("serde"),
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().as_str()), 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().as_str()), 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().as_str()), 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());
}
struct StatusVersion {
version: ConcreteVersion,
status: RemovalStatus,
}
impl Version for StatusVersion {
fn version_string(&self) -> &ConcreteVersion {
&self.version
}
fn removal_status(&self) -> RemovalStatus {
self.status
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[test]
fn test_is_stable_true_for_advisory_deprecated() {
let version = StatusVersion {
version: "1.0.0".into(),
status: RemovalStatus::AdvisoryDeprecated,
};
assert!(version.is_stable());
}
#[test]
fn test_is_stable_false_for_yanked() {
let version = StatusVersion {
version: "1.0.0".into(),
status: RemovalStatus::Yanked,
};
assert!(!version.is_stable());
}
#[test]
fn test_removal_status_blocks_resolution() {
assert!(!RemovalStatus::Available.blocks_resolution());
assert!(!RemovalStatus::AdvisoryDeprecated.blocks_resolution());
assert!(RemovalStatus::Yanked.blocks_resolution());
}
#[test]
fn test_removal_status_is_flagged() {
assert!(!RemovalStatus::Available.is_flagged());
assert!(RemovalStatus::AdvisoryDeprecated.is_flagged());
assert!(RemovalStatus::Yanked.is_flagged());
}
#[test]
fn test_removal_status_from_yanked() {
assert_eq!(RemovalStatus::from_yanked(true), RemovalStatus::Yanked);
assert_eq!(RemovalStatus::from_yanked(false), RemovalStatus::Available);
}
#[test]
fn test_removal_status_from_advisory() {
assert_eq!(
RemovalStatus::from_advisory(true),
RemovalStatus::AdvisoryDeprecated
);
assert_eq!(
RemovalStatus::from_advisory(false),
RemovalStatus::Available
);
}
}