use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use deps_core::policy_config::PolicyConfig;
use deps_core::{EcosystemRegistry, HttpCache};
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct EcosystemRuntime {
pub policy: Arc<deps_core::net_policy::RegistryAccessPolicy>,
pub nuget_user_profile_sources: Arc<AtomicBool>,
pub gitlab_instance_host: Arc<std::sync::RwLock<Option<String>>>,
pub lockfile_cache: Arc<deps_core::lockfile::LockFileCache>,
}
impl EcosystemRuntime {
#[must_use]
pub fn new(
policy: Arc<deps_core::net_policy::RegistryAccessPolicy>,
nuget_user_profile_sources: Arc<AtomicBool>,
gitlab_instance_host: Arc<std::sync::RwLock<Option<String>>>,
) -> Self {
Self {
policy,
nuget_user_profile_sources,
gitlab_instance_host,
lockfile_cache: Arc::new(deps_core::lockfile::LockFileCache::new()),
}
}
#[must_use]
pub fn with_lockfile_cache(
mut self,
lockfile_cache: Arc<deps_core::lockfile::LockFileCache>,
) -> Self {
self.lockfile_cache = lockfile_cache;
self
}
#[must_use]
pub fn from_policy(policy: &PolicyConfig) -> Self {
let resolved = policy.registries.resolve();
Self::new(
Arc::new(deps_core::net_policy::RegistryAccessPolicy::new(
resolved.workspace_registries,
)),
Arc::new(AtomicBool::new(resolved.nuget_user_profile_sources)),
Arc::new(std::sync::RwLock::new(resolved.gitlab_instance_host)),
)
}
}
#[cfg(feature = "gitlab-ci")]
pub fn validate_gitlab_instance_host(
raw: &str,
policy: &deps_core::net_policy::RegistryAccessPolicy,
) -> Result<(), deps_core::net_policy::IndexUrlError> {
deps_gitlab_ci::GitlabHost::parse(raw, policy).map(|_| ())
}
macro_rules! ecosystem {
($feature:literal, $crate_name:ident, $ecosystem:ident, [$($types:ident),* $(,)?]) => {
#[cfg(feature = $feature)]
pub use $crate_name::{$ecosystem, $($types),*};
};
}
macro_rules! register {
($feature:literal, $ecosystem:ident, $registry:expr, $cache:expr) => {
#[cfg(feature = $feature)]
$registry.register(Arc::new($ecosystem::new(Arc::clone($cache))));
};
}
ecosystem!(
"cargo",
deps_cargo,
CargoEcosystem,
[
CargoDependency,
CargoDependencySection,
CargoFormatter,
CargoLockParser,
CargoParseResult,
CargoParser,
CargoRegistry,
CargoVersion,
CrateInfo,
CratesIoRegistry,
parse_cargo_toml,
]
);
ecosystem!(
"npm",
deps_npm,
NpmEcosystem,
[
NpmDependency,
NpmDependencySection,
NpmFormatter,
NpmLockParser,
NpmPackage,
NpmParseResult,
NpmRegistry,
NpmVersion,
parse_package_json,
]
);
ecosystem!(
"pypi",
deps_pypi,
PypiEcosystem,
[
PypiDependency,
PypiDependencySection,
PypiFormatter,
PypiLockParser,
PypiParser,
PypiRegistry,
PypiVersion,
]
);
ecosystem!(
"go",
deps_go,
GoEcosystem,
[
GoDependency,
GoDirective,
GoFormatter,
GoParseResult,
GoRegistry,
GoSumParser,
GoVersion,
parse_go_mod,
]
);
ecosystem!(
"bundler",
deps_bundler,
BundlerEcosystem,
[
BundlerDependency,
BundlerFormatter,
BundlerParseResult,
BundlerVersion,
DependencyGroup,
GemInfo,
GemfileLockParser,
RubyGemsRegistry,
parse_gemfile,
]
);
ecosystem!(
"dart",
deps_dart,
DartEcosystem,
[
DartDependency,
DartParseResult,
DartVersion,
DartFormatter,
PackageInfo,
PubDevRegistry,
PubspecLockParser,
parse_pubspec_yaml,
]
);
ecosystem!(
"maven",
deps_maven,
MavenEcosystem,
[
MavenDependency,
MavenParseResult,
MavenVersion,
MavenFormatter,
ArtifactInfo,
MavenCentralRegistry,
parse_pom_xml,
]
);
ecosystem!(
"gradle",
deps_gradle,
GradleEcosystem,
[
GradleDependency,
GradleParseResult,
GradleVersion,
GradleFormatter,
parse_gradle,
]
);
ecosystem!(
"swift",
deps_swift,
SwiftEcosystem,
[
SwiftDependency,
SwiftParseResult,
SwiftVersion,
SwiftPackage,
SwiftFormatter,
SwiftRegistry,
SwiftLockParser,
parse_package_swift,
]
);
ecosystem!(
"composer",
deps_composer,
ComposerEcosystem,
[
ComposerDependency,
ComposerFormatter,
ComposerLockParser,
ComposerSection,
ComposerPackage,
ComposerParseResult,
PackagistRegistry,
ComposerVersion,
parse_composer_json,
]
);
ecosystem!(
"nuget",
deps_nuget,
NuGetEcosystem,
[
NuGetDependency,
NuGetParseResult,
NuGetVersion,
NuGetFormatter,
NuGetRegistry,
NuGetLockParser,
parse_project_file,
]
);
ecosystem!(
"deno",
deps_deno,
DenoEcosystem,
[
DenoDependency,
DenoDependencySection,
DenoFormatter,
DenoMetadata,
DenoParseResult,
DenoRegistry,
JsrPackage,
JsrRegistry,
JsrVersion,
parse_deno_json,
]
);
ecosystem!(
"github-actions",
deps_github_actions,
GithubActionsEcosystem,
[
GithubActionsDependency,
GithubActionsFormatter,
GithubActionsParseResult,
GithubActionsRegistry,
GithubActionsVersion,
parse_workflow_yaml,
]
);
ecosystem!(
"gitlab-ci",
deps_gitlab_ci,
GitlabCiEcosystem,
[
GitlabCiDependency,
GitlabCiFormatter,
GitlabCiParseResult,
GitlabCiRegistry,
GitlabCiVersion,
parse_gitlab_ci_yaml,
]
);
pub fn register_ecosystems(
registry: &EcosystemRegistry,
cache: Arc<HttpCache>,
runtime: &EcosystemRuntime,
) -> Vec<&'static str> {
let policy = Arc::clone(&runtime.policy);
let _ = &policy;
let _ = (®istry, &cache);
let mut workspace_registry_ecosystems = Vec::new();
let _ = &mut workspace_registry_ecosystems;
#[cfg(feature = "cargo")]
{
let context = deps_cargo::parser::CargoParseContext::new(
Arc::clone(&policy),
Arc::new(deps_cargo::config::ConfigFileCache::new()),
);
registry.register(Arc::new(CargoEcosystem::with_context(
Arc::clone(&cache),
context,
)));
workspace_registry_ecosystems.push("cargo");
}
#[cfg(all(feature = "npm", feature = "deno"))]
{
let npm_config_cache = Arc::new(deps_npm::config::NpmConfigCache::new());
let npm_context = deps_npm::config::NpmParseContext::new(
Arc::clone(&policy),
Arc::clone(&npm_config_cache),
Arc::new(deps_npm::catalog::PnpmWorkspaceCache::new()),
);
let deno_context =
deps_deno::parser::DenoParseContext::new(Arc::clone(&policy), npm_config_cache);
let npm_registry = Arc::new(NpmRegistry::new(Arc::clone(&cache)));
registry.register(Arc::new(NpmEcosystem::with_context(
Arc::clone(&npm_registry),
npm_context,
)));
workspace_registry_ecosystems.push("npm");
registry.register(Arc::new(DenoEcosystem::with_context(
Arc::clone(&cache),
npm_registry.as_ref().clone(),
deno_context,
)));
workspace_registry_ecosystems.push("deno");
}
#[cfg(all(feature = "npm", not(feature = "deno")))]
{
let npm_context = deps_npm::config::NpmParseContext::new(
Arc::clone(&policy),
Arc::new(deps_npm::config::NpmConfigCache::new()),
Arc::new(deps_npm::catalog::PnpmWorkspaceCache::new()),
);
registry.register(Arc::new(NpmEcosystem::with_context(
Arc::new(NpmRegistry::new(Arc::clone(&cache))),
npm_context,
)));
workspace_registry_ecosystems.push("npm");
}
#[cfg(all(feature = "deno", not(feature = "npm")))]
{
let deno_context = deps_deno::parser::DenoParseContext::with_policy(Arc::clone(&policy));
registry.register(Arc::new(DenoEcosystem::with_context_standalone(
Arc::clone(&cache),
deno_context,
)));
workspace_registry_ecosystems.push("deno");
}
#[cfg(feature = "pypi")]
{
registry.register(Arc::new(PypiEcosystem::with_policy(
Arc::new(PypiRegistry::new(Arc::clone(&cache))),
Arc::clone(&policy),
)));
workspace_registry_ecosystems.push("pypi");
}
#[cfg(feature = "go")]
{
let go_context = deps_go::config::GoParseContext::new(
Arc::clone(&policy),
Arc::new(deps_go::config::GoEnvCache::new()),
deps_go::config::goenv_path(),
);
registry.register(Arc::new(GoEcosystem::with_context(
Arc::new(GoRegistry::new(Arc::clone(&cache))),
go_context,
)));
workspace_registry_ecosystems.push("go");
}
register!("bundler", BundlerEcosystem, registry, &cache);
register!("dart", DartEcosystem, registry, &cache);
register!("maven", MavenEcosystem, registry, &cache);
register!("gradle", GradleEcosystem, registry, &cache);
register!("swift", SwiftEcosystem, registry, &cache);
#[cfg(feature = "composer")]
registry.register(Arc::new(ComposerEcosystem::with_context(
Arc::clone(&cache),
Arc::clone(&runtime.lockfile_cache),
)));
#[cfg(feature = "nuget")]
{
let nuget_context = deps_nuget::config::NuGetParseContext::new(
Arc::clone(&policy),
Arc::new(deps_nuget::config::NuGetConfigCache::new()),
Arc::clone(&runtime.nuget_user_profile_sources),
);
registry.register(Arc::new(NuGetEcosystem::with_context(
Arc::new(NuGetRegistry::new(Arc::clone(&cache))),
nuget_context,
)));
workspace_registry_ecosystems.push("nuget");
}
register!("github-actions", GithubActionsEcosystem, registry, &cache);
#[cfg(feature = "gitlab-ci")]
registry.register(Arc::new(GitlabCiEcosystem::with_context(
Arc::clone(&cache),
Arc::clone(&policy),
Arc::clone(&runtime.gitlab_instance_host),
)));
workspace_registry_ecosystems
}
#[cfg(test)]
mod tests {
use super::*;
fn test_runtime() -> EcosystemRuntime {
EcosystemRuntime::new(
Arc::new(deps_core::net_policy::RegistryAccessPolicy::default()),
Arc::new(AtomicBool::new(false)),
Arc::new(std::sync::RwLock::new(None)),
)
}
#[test]
fn test_from_policy_non_default_branches() {
use deps_core::net_policy::WorkspaceRegistryAccess;
use deps_core::policy_config::{
PolicyConfig, RegistriesConfig, WorkspaceRegistriesSetting,
};
let policy = PolicyConfig {
registries: RegistriesConfig::new()
.with_workspace_registries(WorkspaceRegistriesSetting::All)
.with_nuget_user_profile_sources(true)
.with_gitlab_instance_host("gitlab.corp"),
..PolicyConfig::default()
};
let runtime = EcosystemRuntime::from_policy(&policy);
assert_eq!(runtime.policy.get(), WorkspaceRegistryAccess::All);
assert!(
runtime
.nuget_user_profile_sources
.load(std::sync::atomic::Ordering::Relaxed)
);
assert_eq!(
runtime
.gitlab_instance_host
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_deref(),
Some("gitlab.corp")
);
}
#[cfg(feature = "gitlab-ci")]
#[test]
fn test_validate_gitlab_instance_host_accepts_valid_rejects_invalid() {
use deps_core::net_policy::{RegistryAccessPolicy, WorkspaceRegistryAccess};
let policy = RegistryAccessPolicy::new(WorkspaceRegistryAccess::PublicOnly);
assert!(validate_gitlab_instance_host("gitlab.com", &policy).is_ok());
let raw = "user:hunter2@gitlab.corp";
let error = validate_gitlab_instance_host(raw, &policy)
.expect_err("credential-shaped host must be rejected");
assert!(
!error.to_string().contains("hunter2"),
"rejected-host error must not leak the raw credential: {error}"
);
}
#[test]
fn test_register_ecosystems() {
let registry = Arc::new(EcosystemRegistry::new());
let cache = Arc::new(HttpCache::new());
register_ecosystems(®istry, Arc::clone(&cache), &test_runtime());
}
#[test]
#[allow(
clippy::vec_init_then_push,
reason = "each push is independently feature-gated, so a `vec![]` literal can't \
express the feature-conditional membership"
)]
fn test_register_ecosystems_workspace_registry_list_matches_registered_ecosystems() {
let registry = Arc::new(EcosystemRegistry::new());
let cache = Arc::new(HttpCache::new());
let workspace_registry_ecosystems =
register_ecosystems(®istry, Arc::clone(&cache), &test_runtime());
for id in &workspace_registry_ecosystems {
assert!(
registry.get(id).is_some(),
"{id:?} was returned as policy-consuming but is not a registered ecosystem"
);
}
let mut expected: Vec<&str> = Vec::new();
#[cfg(feature = "cargo")]
expected.push("cargo");
#[cfg(feature = "npm")]
expected.push("npm");
#[cfg(feature = "deno")]
expected.push("deno");
#[cfg(feature = "pypi")]
expected.push("pypi");
#[cfg(feature = "go")]
expected.push("go");
#[cfg(feature = "nuget")]
expected.push("nuget");
expected.sort_unstable();
let mut actual = workspace_registry_ecosystems.clone();
actual.sort_unstable();
assert_eq!(
actual, expected,
"workspace-registry-policy ecosystem set changed — update this test's `expected` \
list alongside whatever registration change caused it"
);
}
#[cfg(all(
feature = "cargo",
feature = "npm",
feature = "pypi",
feature = "go",
feature = "bundler",
feature = "dart",
feature = "maven",
feature = "gradle",
feature = "swift",
feature = "composer",
feature = "nuget",
feature = "deno",
feature = "github-actions",
feature = "gitlab-ci"
))]
#[test]
fn test_ecosystem_id_all_registered() {
let registry = Arc::new(EcosystemRegistry::new());
let cache = Arc::new(HttpCache::new());
register_ecosystems(®istry, Arc::clone(&cache), &test_runtime());
for id in deps_core::EcosystemId::ALL {
assert!(
registry.get(id.id()).is_some(),
"{id:?} is in EcosystemId::ALL but was not registered by register_ecosystems"
);
}
}
#[test]
fn test_registered_ecosystems_universal_invariants() {
let registry = Arc::new(EcosystemRegistry::new());
let cache = Arc::new(HttpCache::new());
register_ecosystems(®istry, Arc::clone(&cache), &test_runtime());
let mut display_names = std::collections::HashSet::new();
for id in registry.ecosystem_ids() {
let ecosystem = registry
.get(id)
.unwrap_or_else(|| panic!("{id:?} came from registry.ecosystem_ids() itself"));
let parsed_id: deps_core::EcosystemId = id.parse().unwrap_or_else(|_| {
panic!("{id:?} has no matching EcosystemId variant (see issue #118)")
});
assert_eq!(
parsed_id.id(),
id,
"{id:?}: EcosystemId round-trip mismatch"
);
assert_eq!(ecosystem.id(), id, "{id:?}: Ecosystem::id() mismatch");
let display_name = ecosystem.display_name();
assert!(!display_name.is_empty(), "{id:?} has an empty display_name");
assert!(
display_names.insert(display_name),
"{id:?}'s display_name {display_name:?} collides with another ecosystem's"
);
assert!(
!ecosystem.manifest_filenames().is_empty()
|| !ecosystem.manifest_patterns().is_empty()
|| !ecosystem.manifest_extensions().is_empty()
|| !ecosystem.manifest_directory_patterns().is_empty(),
"{id:?} has no routing surface at all (manifest_filenames/patterns/extensions/directory_patterns)"
);
assert_eq!(
ecosystem.lockfile_filenames().is_empty(),
ecosystem.lockfile_provider().is_none(),
"{id:?}: lockfile_filenames()/lockfile_provider() disagree on whether a lock file format exists"
);
deps_core::conformance::assert_package_url_hostile_input_safe(
ecosystem.formatter(),
&format!("{id:?}"),
);
let metadata = deps_core::test_util::MockMetadata::new("conformance-probe", "1.0.0");
let _ = ecosystem.completion_insert_text(&metadata);
}
}
#[cfg(all(feature = "github-actions", feature = "gitlab-ci"))]
#[test]
fn test_gitlab_ci_directory_pattern_wins_over_github_actions_basename_match() {
let registry = Arc::new(EcosystemRegistry::new());
let cache = Arc::new(HttpCache::new());
register_ecosystems(®istry, Arc::clone(&cache), &test_runtime());
let uri = deps_core::test_util::test_uri("/repo/.gitlab/ci/action.yml");
assert_eq!(
registry.for_uri(&uri).map(|e| e.id()),
Some(deps_core::EcosystemId::GitlabCi.id()),
"a real .gitlab/ci/action.yml file must route to gitlab-ci, not github-actions"
);
let root_action = deps_core::test_util::test_uri("/repo/action.yml");
assert_eq!(
registry.for_uri(&root_action).map(|e| e.id()),
Some(deps_core::EcosystemId::GithubActions.id())
);
let nested_action =
deps_core::test_util::test_uri("/repo/.github/actions/my-action/action.yml");
assert_eq!(
registry.for_uri(&nested_action).map(|e| e.id()),
Some(deps_core::EcosystemId::GithubActions.id())
);
}
#[cfg(any(
feature = "cargo",
feature = "npm",
feature = "go",
feature = "bundler",
feature = "dart",
feature = "maven",
feature = "composer",
feature = "gradle",
feature = "nuget",
feature = "deno",
feature = "github-actions",
feature = "gitlab-ci",
feature = "swift",
feature = "pypi"
))]
fn formatter_treats_bare_version_as_exact_pin(
formatter: &dyn deps_core::lsp_helpers::EcosystemFormatter,
bare: &str,
) -> bool {
use deps_core::{ConcreteVersion, VersionReq};
let requirement = VersionReq::new(bare);
let matches = |candidate: &str| -> bool {
let version = ConcreteVersion::from(candidate);
if let Some(matcher) = formatter.compile_requirement(&requirement) {
matcher.matches(&version) == Some(true)
} else {
formatter.version_satisfies_requirement(&version, bare)
}
};
matches(bare) && !matches("9.9.9") && !matches("1.2.9")
}
#[cfg(any(
feature = "cargo",
feature = "npm",
feature = "go",
feature = "bundler",
feature = "dart",
feature = "maven",
feature = "composer",
feature = "gradle",
feature = "nuget",
feature = "deno",
feature = "github-actions",
feature = "gitlab-ci",
feature = "swift",
feature = "pypi"
))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BareVersionAgreementExpectation {
Checked { test_partial: bool },
LatentOnly,
DeliberateApproximation,
}
#[cfg(any(
feature = "cargo",
feature = "npm",
feature = "go",
feature = "bundler",
feature = "dart",
feature = "maven",
feature = "composer",
feature = "gradle",
feature = "nuget",
feature = "deno",
feature = "github-actions",
feature = "gitlab-ci",
feature = "swift",
feature = "pypi"
))]
fn bare_version_agreement_expectation(
id: deps_core::EcosystemId,
) -> BareVersionAgreementExpectation {
use BareVersionAgreementExpectation::{Checked, DeliberateApproximation, LatentOnly};
match id {
deps_core::EcosystemId::Cargo
| deps_core::EcosystemId::Npm
| deps_core::EcosystemId::Composer
| deps_core::EcosystemId::Deno
| deps_core::EcosystemId::GithubActions
| deps_core::EcosystemId::GitlabCi => Checked { test_partial: true },
deps_core::EcosystemId::Go
| deps_core::EcosystemId::Bundler
| deps_core::EcosystemId::Dart
| deps_core::EcosystemId::Maven
| deps_core::EcosystemId::Gradle => Checked {
test_partial: false,
},
deps_core::EcosystemId::Swift => LatentOnly,
deps_core::EcosystemId::Pypi => LatentOnly,
deps_core::EcosystemId::NuGet => DeliberateApproximation,
}
}
#[cfg(any(
feature = "cargo",
feature = "npm",
feature = "go",
feature = "bundler",
feature = "dart",
feature = "maven",
feature = "composer",
feature = "gradle",
feature = "nuget",
feature = "deno",
feature = "github-actions",
feature = "gitlab-ci",
feature = "swift",
feature = "pypi"
))]
#[test]
fn test_concrete_pin_version_agrees_with_formatter_for_bare_version() {
use deps_core::{ConcreteVersion, VersionReq};
let registry = Arc::new(EcosystemRegistry::new());
let cache = Arc::new(HttpCache::new());
register_ecosystems(®istry, Arc::clone(&cache), &test_runtime());
const BARE_FULL_VERSION: &str = "1.2.3";
const BARE_PARTIAL_VERSION: &str = "1.2";
for str_id in registry.ecosystem_ids() {
let id: deps_core::EcosystemId = str_id.parse().unwrap_or_else(|_| {
panic!("registered ecosystem id {str_id:?} has no matching EcosystemId variant")
});
let ecosystem = registry
.get(str_id)
.unwrap_or_else(|| panic!("{str_id:?} just parsed from the registry's own ids"));
let formatter = ecosystem.formatter();
match bare_version_agreement_expectation(id) {
BareVersionAgreementExpectation::Checked { test_partial } => {
let bare_inputs: &[&str] = if test_partial {
&[BARE_FULL_VERSION, BARE_PARTIAL_VERSION]
} else {
&[BARE_FULL_VERSION]
};
for &bare in bare_inputs {
let deps_core_says_pin =
deps_core::lsp_helpers::concrete_pin_version(bare, id).is_some();
let formatter_says_pin =
formatter_treats_bare_version_as_exact_pin(formatter, bare);
assert_eq!(
deps_core_says_pin, formatter_says_pin,
"{id:?} ({bare:?}): deps-core's concrete_pin_version and the \
ecosystem's own compile_requirement/version_satisfies_requirement \
disagree on whether this bare requirement is an exact pin"
);
}
}
BareVersionAgreementExpectation::LatentOnly => {
let deps_core_says_pin =
deps_core::lsp_helpers::concrete_pin_version(BARE_FULL_VERSION, id)
.is_some();
let formatter_says_pin =
formatter_treats_bare_version_as_exact_pin(formatter, BARE_FULL_VERSION);
assert_ne!(
deps_core_says_pin, formatter_says_pin,
"{id:?}: this ecosystem is exempted as a latent-only mismatch, but its \
comparator no longer disagrees with deps-core's verdict — either the \
parser started emitting a bare requirement (making this a live bug, \
not a latent one) or the comparator changed; re-review this exemption \
in bare_version_agreement_expectation"
);
}
BareVersionAgreementExpectation::DeliberateApproximation => {
assert!(
deps_core::lsp_helpers::concrete_pin_version(BARE_FULL_VERSION, id)
.is_some(),
"{id:?}: deps-core should still report a bare full version as a pin \
(the deliberate approximation this exemption documents)"
);
assert!(
!formatter_treats_bare_version_as_exact_pin(formatter, BARE_FULL_VERSION),
"{id:?}: the ecosystem's own comparator no longer disagrees with a \
strict pin verdict — re-review whether this exemption (and the \
Concrete-policy approximation it documents) is still needed"
);
let requirement = VersionReq::new(BARE_FULL_VERSION);
assert!(
formatter.is_requirement_up_to_date(
&requirement,
&ConcreteVersion::from(BARE_FULL_VERSION)
),
"{id:?}: is_requirement_up_to_date should treat a bare floor as \
up to date when latest equals the floor — the pin-like precedent \
this exemption relies on"
);
assert!(
!formatter.is_requirement_up_to_date(
&requirement,
&ConcreteVersion::from("9.9.9")
),
"{id:?}: is_requirement_up_to_date should treat a bare floor as \
outdated once latest moves past it — confirming it is handled as a \
pin, not an auto-following range"
);
}
}
}
}
#[test]
fn test_select_latest_matching_resolves_advisory_deprecated_for_every_ecosystem() {
use deps_core::{RemovalStatus, Version, VersionReq};
use std::any::Any;
struct StatusVersion {
version: deps_core::ConcreteVersion,
status: RemovalStatus,
}
impl Version for StatusVersion {
fn version_string(&self) -> &deps_core::ConcreteVersion {
&self.version
}
fn removal_status(&self) -> RemovalStatus {
self.status
}
fn as_any(&self) -> &dyn Any {
self
}
}
fn fixture(status: RemovalStatus) -> Vec<Box<dyn Version>> {
vec![
Box::new(StatusVersion {
version: "2.0.0".into(),
status,
}),
Box::new(StatusVersion {
version: "1.2.3".into(),
status,
}),
]
}
struct PrereleaseOnlyVersion {
version: deps_core::ConcreteVersion,
}
impl Version for PrereleaseOnlyVersion {
fn version_string(&self) -> &deps_core::ConcreteVersion {
&self.version
}
fn is_prerelease(&self) -> bool {
true
}
fn as_any(&self) -> &dyn Any {
self
}
}
fn prerelease_only_fixture() -> Vec<Box<dyn Version>> {
vec![
Box::new(PrereleaseOnlyVersion {
version: "2.0.0-beta2".into(),
}),
Box::new(PrereleaseOnlyVersion {
version: "2.0.0-beta1".into(),
}),
]
}
let registry = Arc::new(EcosystemRegistry::new());
let cache = Arc::new(HttpCache::new());
register_ecosystems(®istry, Arc::clone(&cache), &test_runtime());
let req = VersionReq::new("*");
for id in registry.ecosystem_ids() {
let ecosystem = registry.get(id).expect("id came from ecosystem_ids()");
let ecosystem_registry = ecosystem.registry();
let control =
ecosystem_registry.select_latest_matching(&fixture(RemovalStatus::Available), &req);
assert!(
control.is_some(),
"{id}: control fixture (all Available) must resolve under a wildcard \
requirement — a `None` here means the fixture itself doesn't fit this \
ecosystem's matcher, not that the advisory flag broke anything"
);
let subject = ecosystem_registry
.select_latest_matching(&fixture(RemovalStatus::AdvisoryDeprecated), &req);
assert!(
subject.is_some(),
"{id}: an advisory-only flag must not hide an existing package under a \
wildcard requirement (#347)"
);
if matches!(id, "go") {
continue;
}
let prerelease_subject =
ecosystem_registry.select_latest_matching(&prerelease_only_fixture(), &req);
assert!(
prerelease_subject.is_some(),
"{id}: a package whose only releases so far are prerelease must still \
resolve under a wildcard requirement (#421)"
);
}
}
}