#[macro_export]
macro_rules! impl_dependency {
($type:ty {
name: $name:ident,
name_range: $name_range:ident,
version: $version:ident,
version_range: $version_range:ident $(,)?
}) => {
impl $crate::ecosystem::Dependency for $type {
fn name(&self) -> &$crate::PackageName {
&self.$name
}
fn name_range(&self) -> $crate::position::Range {
self.$name_range
}
fn version_requirement(&self) -> Option<&$crate::VersionReq> {
self.$version.as_ref()
}
fn version_range(&self) -> Option<$crate::position::Range> {
self.$version_range
}
fn source(&self) -> $crate::parser::DependencySource {
$crate::parser::DependencySource::Registry
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty {
name: $name:ident,
name_range: $name_range:ident,
version: $version:ident,
version_range: $version_range:ident,
source: $source:ident $(,)?
}) => {
impl $crate::ecosystem::Dependency for $type {
fn name(&self) -> &$crate::PackageName {
&self.$name
}
fn name_range(&self) -> $crate::position::Range {
self.$name_range
}
fn version_requirement(&self) -> Option<&$crate::VersionReq> {
self.$version.as_ref()
}
fn version_range(&self) -> Option<$crate::position::Range> {
self.$version_range
}
fn source(&self) -> $crate::parser::DependencySource {
self.$source.clone()
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty {
name: $name:ident,
name_range: $name_range:ident,
version: $version:ident,
version_range: $version_range:ident,
source: $source:ident,
version_literal: $version_literal:ident $(,)?
}) => {
impl $crate::ecosystem::Dependency for $type {
fn name(&self) -> &$crate::PackageName {
&self.$name
}
fn name_range(&self) -> $crate::position::Range {
self.$name_range
}
fn version_requirement(&self) -> Option<&$crate::VersionReq> {
self.$version.as_ref()
}
fn version_range(&self) -> Option<$crate::position::Range> {
self.$version_range
}
fn source(&self) -> $crate::parser::DependencySource {
self.$source.clone()
}
fn version_literal(&self) -> Option<&str> {
self.$version_literal.as_deref()
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
}
#[macro_export]
macro_rules! impl_version {
($type:ty {
version: $version:ident,
status: $status:expr $(,)?
}) => {
impl $crate::registry::Version for $type {
fn version_string(&self) -> &$crate::ConcreteVersion {
&self.$version
}
fn removal_status(&self) -> $crate::registry::RemovalStatus {
($status)(self)
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty {
version: $version:ident,
status: $status:expr,
published_at: $published_at:ident $(,)?
}) => {
impl $crate::registry::Version for $type {
fn version_string(&self) -> &$crate::ConcreteVersion {
&self.$version
}
fn removal_status(&self) -> $crate::registry::RemovalStatus {
($status)(self)
}
fn published_at(&self) -> Option<$crate::freshness::PublishTime> {
self.$published_at
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty {
version: $version:ident,
status: $status:expr,
prerelease: $prerelease:expr $(,)?
}) => {
impl $crate::registry::Version for $type {
fn version_string(&self) -> &$crate::ConcreteVersion {
&self.$version
}
fn removal_status(&self) -> $crate::registry::RemovalStatus {
($status)(self)
}
fn is_prerelease(&self) -> bool {
($prerelease)(self)
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty {
version: $version:ident,
status: $status:expr,
published_at: $published_at:ident,
prerelease: $prerelease:expr $(,)?
}) => {
impl $crate::registry::Version for $type {
fn version_string(&self) -> &$crate::ConcreteVersion {
&self.$version
}
fn removal_status(&self) -> $crate::registry::RemovalStatus {
($status)(self)
}
fn published_at(&self) -> Option<$crate::freshness::PublishTime> {
self.$published_at
}
fn is_prerelease(&self) -> bool {
($prerelease)(self)
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty {
version: $version:ident,
status: $status:expr,
published_at: $published_at:ident,
prerelease: $prerelease:expr,
deprecation: $deprecation:expr $(,)?
}) => {
impl $crate::registry::Version for $type {
fn version_string(&self) -> &$crate::ConcreteVersion {
&self.$version
}
fn removal_status(&self) -> $crate::registry::RemovalStatus {
($status)(self)
}
fn published_at(&self) -> Option<$crate::freshness::PublishTime> {
self.$published_at
}
fn is_prerelease(&self) -> bool {
($prerelease)(self)
}
fn deprecation(&self) -> Option<&$crate::registry::Deprecation> {
let f: fn(&$type) -> Option<&$crate::registry::Deprecation> = $deprecation;
f(self)
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty {
version: $version:ident,
status: $status:expr,
published_at: $published_at:ident,
prerelease: $prerelease:expr,
deprecation: $deprecation:expr,
license: $license:ident $(,)?
}) => {
impl $crate::registry::Version for $type {
fn version_string(&self) -> &$crate::ConcreteVersion {
&self.$version
}
fn removal_status(&self) -> $crate::registry::RemovalStatus {
($status)(self)
}
fn published_at(&self) -> Option<$crate::freshness::PublishTime> {
self.$published_at
}
fn is_prerelease(&self) -> bool {
($prerelease)(self)
}
fn deprecation(&self) -> Option<&$crate::registry::Deprecation> {
let f: fn(&$type) -> Option<&$crate::registry::Deprecation> = $deprecation;
f(self)
}
fn license(&self) -> &[String] {
&self.$license
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
}
#[macro_export]
macro_rules! impl_metadata {
($type:ty {
name: $name:ident,
description: $description:ident,
repository: $repository:ident,
documentation: $documentation:ident,
latest_version: $latest_version:ident $(,)?
}) => {
impl $crate::registry::Metadata for $type {
fn name(&self) -> &$crate::PackageName {
&self.$name
}
fn description(&self) -> Option<&str> {
self.$description.as_deref()
}
fn repository(&self) -> Option<&str> {
self.$repository.as_deref()
}
fn documentation(&self) -> Option<&str> {
self.$documentation.as_deref()
}
fn latest_version(&self) -> &$crate::ConcreteVersion {
&self.$latest_version
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
}
#[macro_export]
macro_rules! impl_parse_result {
($type:ty, $dep_type:ty {
dependencies: $dependencies:ident,
uri: $uri:ident $(,)?
}) => {
impl $crate::ecosystem::ParseResult for $type {
fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
self.$dependencies
.iter()
.map(|d| d as &dyn $crate::ecosystem::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&::std::path::Path> {
None
}
fn uri(&self) -> &::url::Url {
&self.$uri
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty, $dep_type:ty {
dependencies: $dependencies:ident,
uri: $uri:ident,
workspace_root: $workspace_root:ident $(,)?
}) => {
impl $crate::ecosystem::ParseResult for $type {
fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
self.$dependencies
.iter()
.map(|d| d as &dyn $crate::ecosystem::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&::std::path::Path> {
self.$workspace_root.as_deref()
}
fn uri(&self) -> &::url::Url {
&self.$uri
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty, $dep_type:ty {
dependencies: $dependencies:ident,
uri: $uri:ident,
dependency_truncation: $dependency_truncation:ident $(,)?
}) => {
impl $crate::ecosystem::ParseResult for $type {
fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
self.$dependencies
.iter()
.map(|d| d as &dyn $crate::ecosystem::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&::std::path::Path> {
None
}
fn uri(&self) -> &::url::Url {
&self.$uri
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn dependency_truncation(&self) -> Option<(usize, usize)> {
self.$dependency_truncation
}
}
};
($type:ty, $dep_type:ty {
dependencies: $dependencies:ident,
uri: $uri:ident,
workspace_root: $workspace_root:ident,
dependency_truncation: $dependency_truncation:ident $(,)?
}) => {
impl $crate::ecosystem::ParseResult for $type {
fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
self.$dependencies
.iter()
.map(|d| d as &dyn $crate::ecosystem::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&::std::path::Path> {
self.$workspace_root.as_deref()
}
fn uri(&self) -> &::url::Url {
&self.$uri
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn dependency_truncation(&self) -> Option<(usize, usize)> {
self.$dependency_truncation
}
}
};
($type:ty, $dep_type:ty {
dependencies: $dependencies:ident,
uri: $uri:ident,
blocked_registries: $blocked_registries:ident $(,)?
}) => {
impl $crate::ecosystem::ParseResult for $type {
fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
self.$dependencies
.iter()
.map(|d| d as &dyn $crate::ecosystem::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&::std::path::Path> {
None
}
fn uri(&self) -> &::url::Url {
&self.$uri
}
fn blocked_registries(&self) -> Vec<$crate::ecosystem::BlockedRegistryOccurrence> {
self.$blocked_registries.clone()
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty, $dep_type:ty {
dependencies: $dependencies:ident,
uri: $uri:ident,
workspace_root: $workspace_root:ident,
blocked_registries: $blocked_registries:ident $(,)?
}) => {
impl $crate::ecosystem::ParseResult for $type {
fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
self.$dependencies
.iter()
.map(|d| d as &dyn $crate::ecosystem::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&::std::path::Path> {
self.$workspace_root.as_deref()
}
fn uri(&self) -> &::url::Url {
&self.$uri
}
fn blocked_registries(&self) -> Vec<$crate::ecosystem::BlockedRegistryOccurrence> {
self.$blocked_registries.clone()
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
};
($type:ty, $dep_type:ty {
dependencies: $dependencies:ident,
uri: $uri:ident,
dependency_truncation: $dependency_truncation:ident,
blocked_registries: $blocked_registries:ident $(,)?
}) => {
impl $crate::ecosystem::ParseResult for $type {
fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
self.$dependencies
.iter()
.map(|d| d as &dyn $crate::ecosystem::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&::std::path::Path> {
None
}
fn uri(&self) -> &::url::Url {
&self.$uri
}
fn blocked_registries(&self) -> Vec<$crate::ecosystem::BlockedRegistryOccurrence> {
self.$blocked_registries.clone()
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn dependency_truncation(&self) -> Option<(usize, usize)> {
self.$dependency_truncation
}
}
};
($type:ty, $dep_type:ty {
dependencies: $dependencies:ident,
uri: $uri:ident,
workspace_root: $workspace_root:ident,
dependency_truncation: $dependency_truncation:ident,
blocked_registries: $blocked_registries:ident $(,)?
}) => {
impl $crate::ecosystem::ParseResult for $type {
fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
self.$dependencies
.iter()
.map(|d| d as &dyn $crate::ecosystem::Dependency)
.collect()
}
fn workspace_root(&self) -> Option<&::std::path::Path> {
self.$workspace_root.as_deref()
}
fn uri(&self) -> &::url::Url {
&self.$uri
}
fn blocked_registries(&self) -> Vec<$crate::ecosystem::BlockedRegistryOccurrence> {
self.$blocked_registries.clone()
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn dependency_truncation(&self) -> Option<(usize, usize)> {
self.$dependency_truncation
}
}
};
}
#[macro_export]
macro_rules! impl_registry_versions_method {
(get_versions) => {
fn get_versions<'a>(
&'a self,
name: &'a $crate::PackageName,
) -> $crate::ecosystem::BoxFuture<
'a,
$crate::error::Result<::std::vec::Vec<::std::boxed::Box<dyn $crate::Version>>>,
> {
::std::boxed::Box::pin(async move {
let versions = self.get_versions(name.as_str()).await?;
Ok(versions
.into_iter()
.map(|v| ::std::boxed::Box::new(v) as ::std::boxed::Box<dyn $crate::Version>)
.collect())
})
}
};
(get_versions_with) => {
fn get_versions_with<'a>(
&'a self,
name: &'a $crate::PackageName,
freshness: $crate::FreshnessSettings,
) -> $crate::ecosystem::BoxFuture<
'a,
$crate::error::Result<::std::vec::Vec<::std::boxed::Box<dyn $crate::Version>>>,
> {
::std::boxed::Box::pin(async move {
let versions = self.get_versions_with(name.as_str(), freshness).await?;
Ok(versions
.into_iter()
.map(|v| ::std::boxed::Box::new(v) as ::std::boxed::Box<dyn $crate::Version>)
.collect())
})
}
};
}
#[macro_export]
macro_rules! impl_get_versions_with_passthrough {
($type:ty, $version:ty $(,)?) => {
impl $type {
pub async fn get_versions_with(
&self,
name: &str,
_freshness: $crate::FreshnessSettings,
) -> $crate::error::Result<::std::vec::Vec<$version>> {
self.get_versions(name).await
}
}
};
}
#[cfg(test)]
mod tests {
use crate::ConcreteVersion;
use crate::position::{Position, Range};
#[derive(Debug, Clone)]
struct TestDependency {
name: crate::PackageName,
name_range: Range,
version_req: Option<crate::VersionReq>,
version_range: Option<Range>,
}
#[derive(Debug, Clone)]
struct TestDependencyWithSource {
name: crate::PackageName,
name_range: Range,
version_req: Option<crate::VersionReq>,
version_range: Option<Range>,
source: crate::parser::DependencySource,
version_literal: Option<String>,
}
#[derive(Debug, Clone)]
struct TestVersion {
version: ConcreteVersion,
yanked: bool,
}
#[derive(Debug, Clone)]
struct TestVersionWithPublishedAt {
version: ConcreteVersion,
yanked: bool,
published_at: Option<crate::freshness::PublishTime>,
}
#[derive(Debug, Clone)]
struct TestVersionWithPrerelease {
version: ConcreteVersion,
yanked: bool,
}
#[derive(Debug, Clone)]
struct TestVersionWithPublishedAtAndPrerelease {
version: ConcreteVersion,
yanked: bool,
published_at: Option<crate::freshness::PublishTime>,
}
#[derive(Debug, Clone)]
struct TestPackage {
name: crate::PackageName,
description: Option<String>,
repository: Option<String>,
homepage: Option<String>,
latest_version: ConcreteVersion,
}
#[derive(Debug)]
struct TestParseResult {
dependencies: Vec<TestDependency>,
uri: url::Url,
}
impl_dependency!(TestDependency {
name: name,
name_range: name_range,
version: version_req,
version_range: version_range,
});
impl_dependency!(TestDependencyWithSource {
name: name,
name_range: name_range,
version: version_req,
version_range: version_range,
source: source,
version_literal: version_literal,
});
impl_version!(TestVersion {
version: version,
status: |v: &TestVersion| crate::registry::RemovalStatus::from_yanked(v.yanked),
});
impl_version!(TestVersionWithPublishedAt {
version: version,
status: |v: &TestVersionWithPublishedAt| crate::registry::RemovalStatus::from_yanked(
v.yanked
),
published_at: published_at,
});
impl_version!(TestVersionWithPrerelease {
version: version,
status: |v: &TestVersionWithPrerelease| crate::registry::RemovalStatus::from_yanked(
v.yanked
),
prerelease: |v: &TestVersionWithPrerelease| v.version.as_str().contains(".pre"),
});
impl_version!(TestVersionWithPublishedAtAndPrerelease {
version: version,
status: |v: &TestVersionWithPublishedAtAndPrerelease| {
crate::registry::RemovalStatus::from_yanked(v.yanked)
},
published_at: published_at,
prerelease: |v: &TestVersionWithPublishedAtAndPrerelease| v
.version
.as_str()
.contains(".pre"),
});
impl_metadata!(TestPackage {
name: name,
description: description,
repository: repository,
documentation: homepage,
latest_version: latest_version,
});
impl_parse_result!(
TestParseResult,
TestDependency {
dependencies: dependencies,
uri: uri,
}
);
#[test]
fn test_impl_dependency_macro() {
use crate::ecosystem::Dependency;
let dep = TestDependency {
name: "test-pkg".into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 8)),
version_req: Some("1.0.0".into()),
version_range: Some(Range::new(Position::new(0, 10), Position::new(0, 15))),
};
assert_eq!(dep.name(), "test-pkg");
assert_eq!(
dep.version_requirement().map(crate::VersionReq::as_str),
Some("1.0.0")
);
assert!(dep.as_any().is::<TestDependency>());
assert!(matches!(
dep.source(),
crate::parser::DependencySource::Registry
));
}
#[test]
fn test_impl_dependency_macro_with_source_and_version_literal() {
use crate::ecosystem::Dependency;
let dep = TestDependencyWithSource {
name: "test-pkg".into(),
name_range: Range::new(Position::new(0, 0), Position::new(0, 8)),
version_req: Some("1.0.0".into()),
version_range: Some(Range::new(Position::new(0, 10), Position::new(0, 15))),
source: crate::parser::DependencySource::Git {
url: "https://example.com/repo.git".into(),
rev: None,
},
version_literal: Some("v1.0.0".into()),
};
assert!(matches!(
dep.source(),
crate::parser::DependencySource::Git { .. }
));
assert_eq!(dep.version_literal(), Some("v1.0.0"));
}
#[test]
fn test_impl_version_macro() {
use crate::registry::Version;
let version = TestVersion {
version: "2.0.0".into(),
yanked: true,
};
assert_eq!(version.version_string().as_str(), "2.0.0");
assert!(version.removal_status().blocks_resolution());
assert!(version.as_any().is::<TestVersion>());
assert!(version.published_at().is_none());
}
#[test]
fn test_impl_version_macro_with_published_at() {
use crate::freshness::PublishTime;
use crate::registry::Version;
let version = TestVersionWithPublishedAt {
version: "3.0.0".into(),
yanked: false,
published_at: Some(PublishTime::from_unix_secs(1_000)),
};
assert_eq!(version.version_string().as_str(), "3.0.0");
assert!(!version.removal_status().blocks_resolution());
assert_eq!(
version.published_at(),
Some(PublishTime::from_unix_secs(1_000))
);
assert!(version.as_any().is::<TestVersionWithPublishedAt>());
}
#[test]
fn test_impl_version_macro_with_prerelease() {
use crate::registry::Version;
let stable = TestVersionWithPrerelease {
version: "1.0.0".into(),
yanked: false,
};
let prerelease = TestVersionWithPrerelease {
version: "1.0.0.pre1".into(),
yanked: false,
};
assert!(!stable.is_prerelease());
assert!(prerelease.is_prerelease());
assert!(stable.is_stable());
assert!(!prerelease.is_stable());
}
#[test]
fn test_impl_version_macro_with_published_at_and_prerelease() {
use crate::freshness::PublishTime;
use crate::registry::Version;
let version = TestVersionWithPublishedAtAndPrerelease {
version: "2.0.0.pre1".into(),
yanked: false,
published_at: Some(PublishTime::from_unix_secs(2_000)),
};
assert!(version.is_prerelease());
assert_eq!(
version.published_at(),
Some(PublishTime::from_unix_secs(2_000))
);
}
#[test]
fn test_impl_metadata_macro() {
use crate::registry::Metadata;
let pkg = TestPackage {
name: crate::PackageName::new("my-pkg"),
description: Some("A test package".into()),
repository: Some("user/repo".into()),
homepage: Some("https://example.com".into()),
latest_version: "3.0.0".into(),
};
assert_eq!(pkg.name(), "my-pkg");
assert_eq!(pkg.description(), Some("A test package"));
assert_eq!(pkg.documentation(), Some("https://example.com"));
assert!(pkg.as_any().is::<TestPackage>());
}
#[test]
fn test_impl_parse_result_macro() {
use crate::ecosystem::ParseResult;
let result = TestParseResult {
dependencies: vec![TestDependency {
name: "dep1".into(),
name_range: Range::default(),
version_req: None,
version_range: None,
}],
uri: crate::test_util::test_uri("/test"),
};
assert_eq!(result.dependencies().len(), 1);
assert!(result.workspace_root().is_none());
assert!(result.as_any().is::<TestParseResult>());
}
}