diff --git a/crates/uv-installer/src/site_packages.rs b/crates/uv-installer/src/site_packages.rs index 953e302a9..065b81bf9 100644 --- a/crates/uv-installer/src/site_packages.rs +++ b/crates/uv-installer/src/site_packages.rs @@ -215,7 +215,7 @@ impl SitePackages { // Determine the dependencies for the given package. let Ok(metadata) = distribution.metadata() else { - diagnostics.push(SitePackagesDiagnostic::IncompletePackage { + diagnostics.push(SitePackagesDiagnostic::MetadataUnavailable { package: package.clone(), path: distribution.path().to_owned(), }); @@ -405,7 +405,7 @@ impl IntoIterator for SitePackages { #[derive(Debug)] pub enum SitePackagesDiagnostic { - IncompletePackage { + MetadataUnavailable { /// The package that is missing metadata. package: PackageName, /// The path to the package. @@ -445,7 +445,7 @@ impl Diagnostic for SitePackagesDiagnostic { /// Convert the diagnostic into a user-facing message. fn message(&self) -> String { match self { - Self::IncompletePackage { package, path } => format!( + Self::MetadataUnavailable { package, path } => format!( "The package `{package}` is broken or incomplete (unable to read `METADATA`). Consider recreating the virtualenv, or removing the package directory at: {}.", path.display(), ), Self::IncompatiblePythonVersion { @@ -482,7 +482,7 @@ impl Diagnostic for SitePackagesDiagnostic { /// Returns `true` if the [`PackageName`] is involved in this diagnostic. fn includes(&self, name: &PackageName) -> bool { match self { - Self::IncompletePackage { package, .. } => name == package, + Self::MetadataUnavailable { package, .. } => name == package, Self::IncompatiblePythonVersion { package, .. } => name == package, Self::MissingDependency { package, .. } => name == package, Self::IncompatibleDependency { diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 5c1da7aa4..027115b38 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -24,7 +24,7 @@ use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter use crate::python_requirement::PythonRequirement; use crate::resolution::ConflictingDistributionError; use crate::resolver::{ - IncompletePackage, ResolverEnvironment, UnavailablePackage, UnavailableReason, + MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason, }; use crate::Options; @@ -145,7 +145,7 @@ pub struct NoSolutionError { index_locations: IndexLocations, index_capabilities: IndexCapabilities, unavailable_packages: FxHashMap, - incomplete_packages: FxHashMap>, + incomplete_packages: FxHashMap>, fork_urls: ForkUrls, env: ResolverEnvironment, workspace_members: BTreeSet, @@ -163,7 +163,7 @@ impl NoSolutionError { index_locations: IndexLocations, index_capabilities: IndexCapabilities, unavailable_packages: FxHashMap, - incomplete_packages: FxHashMap>, + incomplete_packages: FxHashMap>, fork_urls: ForkUrls, env: ResolverEnvironment, workspace_members: BTreeSet, diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index a3f035814..3e90c6ae8 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -18,7 +18,7 @@ use crate::error::ErrorTree; use crate::fork_urls::ForkUrls; use crate::prerelease::AllowPrerelease; use crate::python_requirement::{PythonRequirement, PythonRequirementSource}; -use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason}; +use crate::resolver::{MetadataUnavailable, UnavailablePackage, UnavailableReason}; use crate::{Flexibility, Options, RequiresPython, ResolverEnvironment}; use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; @@ -548,7 +548,7 @@ impl PubGrubReportFormatter<'_> { index_capabilities: &IndexCapabilities, available_indexes: &FxHashMap>, unavailable_packages: &FxHashMap, - incomplete_packages: &FxHashMap>, + incomplete_packages: &FxHashMap>, fork_urls: &ForkUrls, env: &ResolverEnvironment, workspace_members: &BTreeSet, @@ -679,7 +679,7 @@ impl PubGrubReportFormatter<'_> { index_capabilities: &IndexCapabilities, available_indexes: &FxHashMap>, unavailable_packages: &FxHashMap, - incomplete_packages: &FxHashMap>, + incomplete_packages: &FxHashMap>, hints: &mut IndexSet, ) { let no_find_links = index_locations.flat_indexes().peekable().peek().is_none(); @@ -694,11 +694,6 @@ impl PubGrubReportFormatter<'_> { Some(UnavailablePackage::Offline) => { hints.insert(PubGrubHint::Offline); } - Some(UnavailablePackage::MissingMetadata) => { - hints.insert(PubGrubHint::MissingPackageMetadata { - package: package.clone(), - }); - } Some(UnavailablePackage::InvalidMetadata(reason)) => { hints.insert(PubGrubHint::InvalidPackageMetadata { package: package.clone(), @@ -720,37 +715,31 @@ impl PubGrubReportFormatter<'_> { for (version, incomplete) in versions.iter().rev() { if set.contains(version) { match incomplete { - IncompletePackage::Offline => { + MetadataUnavailable::Offline => { hints.insert(PubGrubHint::Offline); } - IncompletePackage::MissingMetadata => { - hints.insert(PubGrubHint::MissingVersionMetadata { - package: package.clone(), - version: version.clone(), - }); - } - IncompletePackage::InvalidMetadata(reason) => { + MetadataUnavailable::InvalidMetadata(reason) => { hints.insert(PubGrubHint::InvalidVersionMetadata { package: package.clone(), version: version.clone(), - reason: reason.clone(), + reason: reason.to_string(), }); } - IncompletePackage::InconsistentMetadata(reason) => { + MetadataUnavailable::InconsistentMetadata(reason) => { hints.insert(PubGrubHint::InconsistentVersionMetadata { package: package.clone(), version: version.clone(), - reason: reason.clone(), + reason: reason.to_string(), }); } - IncompletePackage::InvalidStructure(reason) => { + MetadataUnavailable::InvalidStructure(reason) => { hints.insert(PubGrubHint::InvalidVersionStructure { package: package.clone(), version: version.clone(), - reason: reason.clone(), + reason: reason.to_string(), }); } - IncompletePackage::RequiresPython(requires_python, python_version) => { + MetadataUnavailable::RequiresPython(requires_python, python_version) => { hints.insert(PubGrubHint::IncompatibleBuildRequirement { package: package.clone(), version: version.clone(), @@ -882,8 +871,6 @@ pub(crate) enum PubGrubHint { NoIndex, /// A package was not found in the registry, but network access was disabled. Offline, - /// Metadata for a package could not be found. - MissingPackageMetadata { package: PubGrubPackage }, /// Metadata for a package could not be parsed. InvalidPackageMetadata { package: PubGrubPackage, @@ -896,12 +883,6 @@ pub(crate) enum PubGrubHint { // excluded from `PartialEq` and `Hash` reason: String, }, - /// Metadata for a package version could not be found. - MissingVersionMetadata { - package: PubGrubPackage, - // excluded from `PartialEq` and `Hash` - version: Version, - }, /// Metadata for a package version could not be parsed. InvalidVersionMetadata { package: PubGrubPackage, @@ -992,18 +973,12 @@ enum PubGrubHintCore { }, NoIndex, Offline, - MissingPackageMetadata { - package: PubGrubPackage, - }, InvalidPackageMetadata { package: PubGrubPackage, }, InvalidPackageStructure { package: PubGrubPackage, }, - MissingVersionMetadata { - package: PubGrubPackage, - }, InvalidVersionMetadata { package: PubGrubPackage, }, @@ -1052,18 +1027,12 @@ impl From for PubGrubHintCore { } PubGrubHint::NoIndex => Self::NoIndex, PubGrubHint::Offline => Self::Offline, - PubGrubHint::MissingPackageMetadata { package, .. } => { - Self::MissingPackageMetadata { package } - } PubGrubHint::InvalidPackageMetadata { package, .. } => { Self::InvalidPackageMetadata { package } } PubGrubHint::InvalidPackageStructure { package, .. } => { Self::InvalidPackageStructure { package } } - PubGrubHint::MissingVersionMetadata { package, .. } => { - Self::MissingVersionMetadata { package } - } PubGrubHint::InvalidVersionMetadata { package, .. } => { Self::InvalidVersionMetadata { package } } @@ -1162,15 +1131,6 @@ impl std::fmt::Display for PubGrubHint { ":".bold(), ) } - Self::MissingPackageMetadata { package } => { - write!( - f, - "{}{} Metadata for `{}` could not be found, as the wheel is missing a `METADATA` file", - "hint".bold().cyan(), - ":".bold(), - package.bold() - ) - } Self::InvalidPackageMetadata { package, reason } => { write!( f, @@ -1191,16 +1151,6 @@ impl std::fmt::Display for PubGrubHint { textwrap::indent(reason, " ") ) } - Self::MissingVersionMetadata { package, version } => { - write!( - f, - "{}{} Metadata for `{}` ({}) could not be found, as the wheel is missing a `METADATA` file", - "hint".bold().cyan(), - ":".bold(), - package.cyan(), - format!("v{version}").cyan(), - ) - } Self::InvalidVersionMetadata { package, version, diff --git a/crates/uv-resolver/src/resolver/availability.rs b/crates/uv-resolver/src/resolver/availability.rs index c3e9067d9..ba207056a 100644 --- a/crates/uv-resolver/src/resolver/availability.rs +++ b/crates/uv-resolver/src/resolver/availability.rs @@ -1,5 +1,6 @@ use std::fmt::{Display, Formatter}; +use crate::resolver::MetadataUnavailable; use uv_distribution_types::IncompatibleDist; use uv_pep440::{Version, VersionSpecifiers}; @@ -21,17 +22,15 @@ impl Display for UnavailableReason { } } -/// The package version is unavailable and cannot be used. Unlike [`PackageUnavailable`], this +/// The package version is unavailable and cannot be used. Unlike [`MetadataUnavailable`], this /// applies to a single version of the package. /// -/// Most variant are from [`MetadataResponse`] without the error source (since we don't format -/// the source). +/// Most variant are from [`MetadataResponse`] without the error source, since we don't format +/// the source and we want to merge unavailable messages across versions. #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) enum UnavailableVersion { /// Version is incompatible because it has no usable distributions IncompatibleDist(IncompatibleDist), - /// The wheel metadata was not found. - MissingMetadata, /// The wheel metadata was found, but could not be parsed. InvalidMetadata, /// The wheel metadata was found, but the metadata was inconsistent. @@ -49,7 +48,6 @@ impl UnavailableVersion { pub(crate) fn message(&self) -> String { match self { UnavailableVersion::IncompatibleDist(invalid_dist) => format!("{invalid_dist}"), - UnavailableVersion::MissingMetadata => "not include a `METADATA` file".into(), UnavailableVersion::InvalidMetadata => "invalid metadata".into(), UnavailableVersion::InconsistentMetadata => "inconsistent metadata".into(), UnavailableVersion::InvalidStructure => "an invalid package format".into(), @@ -63,7 +61,6 @@ impl UnavailableVersion { pub(crate) fn singular_message(&self) -> String { match self { UnavailableVersion::IncompatibleDist(invalid_dist) => invalid_dist.singular_message(), - UnavailableVersion::MissingMetadata => format!("does {self}"), UnavailableVersion::InvalidMetadata => format!("has {self}"), UnavailableVersion::InconsistentMetadata => format!("has {self}"), UnavailableVersion::InvalidStructure => format!("has {self}"), @@ -75,7 +72,6 @@ impl UnavailableVersion { pub(crate) fn plural_message(&self) -> String { match self { UnavailableVersion::IncompatibleDist(invalid_dist) => invalid_dist.plural_message(), - UnavailableVersion::MissingMetadata => format!("do {self}"), UnavailableVersion::InvalidMetadata => format!("have {self}"), UnavailableVersion::InconsistentMetadata => format!("have {self}"), UnavailableVersion::InvalidStructure => format!("have {self}"), @@ -91,6 +87,22 @@ impl Display for UnavailableVersion { } } +impl From<&MetadataUnavailable> for UnavailableVersion { + fn from(reason: &MetadataUnavailable) -> Self { + match reason { + MetadataUnavailable::Offline => UnavailableVersion::Offline, + MetadataUnavailable::InvalidMetadata(_) => UnavailableVersion::InvalidMetadata, + MetadataUnavailable::InconsistentMetadata(_) => { + UnavailableVersion::InconsistentMetadata + } + MetadataUnavailable::InvalidStructure(_) => UnavailableVersion::InvalidStructure, + MetadataUnavailable::RequiresPython(requires_python, _python_version) => { + UnavailableVersion::RequiresPython(requires_python.clone()) + } + } + } +} + /// The package is unavailable and cannot be used. #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) enum UnavailablePackage { @@ -100,8 +112,6 @@ pub(crate) enum UnavailablePackage { Offline, /// The package was not found in the registry. NotFound, - /// The package metadata was not found. - MissingMetadata, /// The package metadata was found, but could not be parsed. InvalidMetadata(String), /// The package has an invalid structure. @@ -114,7 +124,6 @@ impl UnavailablePackage { UnavailablePackage::NoIndex => "not found in the provided package locations", UnavailablePackage::Offline => "not found in the cache", UnavailablePackage::NotFound => "not found in the package registry", - UnavailablePackage::MissingMetadata => "not include a `METADATA` file", UnavailablePackage::InvalidMetadata(_) => "invalid metadata", UnavailablePackage::InvalidStructure(_) => "an invalid package format", } @@ -125,7 +134,6 @@ impl UnavailablePackage { UnavailablePackage::NoIndex => format!("was {self}"), UnavailablePackage::Offline => format!("was {self}"), UnavailablePackage::NotFound => format!("was {self}"), - UnavailablePackage::MissingMetadata => format!("does {self}"), UnavailablePackage::InvalidMetadata(_) => format!("has {self}"), UnavailablePackage::InvalidStructure(_) => format!("has {self}"), } @@ -138,22 +146,20 @@ impl Display for UnavailablePackage { } } -/// The package is unavailable at specific versions. -#[derive(Debug, Clone)] -pub(crate) enum IncompletePackage { - /// Network requests were disabled (i.e., `--offline`), and the wheel metadata was not found in the cache. - Offline, - /// The wheel metadata was not found. - MissingMetadata, - /// The wheel metadata was found, but could not be parsed. - InvalidMetadata(String), - /// The wheel metadata was found, but the metadata was inconsistent. - InconsistentMetadata(String), - /// The wheel has an invalid structure. - InvalidStructure(String), - /// The source distribution has a `requires-python` requirement that is not met by the installed - /// Python version (and static metadata is not available). - RequiresPython(VersionSpecifiers, Version), +impl From<&MetadataUnavailable> for UnavailablePackage { + fn from(reason: &MetadataUnavailable) -> Self { + match reason { + MetadataUnavailable::Offline => Self::Offline, + MetadataUnavailable::InvalidMetadata(err) => Self::InvalidMetadata(err.to_string()), + MetadataUnavailable::InconsistentMetadata(err) => { + Self::InvalidMetadata(err.to_string()) + } + MetadataUnavailable::InvalidStructure(err) => Self::InvalidStructure(err.to_string()), + MetadataUnavailable::RequiresPython(..) => { + unreachable!("`requires-python` is only known upfront for registry distributions") + } + } + } } #[derive(Debug, Clone)] diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 05f96399a..fe87350e1 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -55,7 +55,7 @@ use crate::python_requirement::PythonRequirement; use crate::resolution::ResolverOutput; use crate::resolution_mode::ResolutionStrategy; pub(crate) use crate::resolver::availability::{ - IncompletePackage, ResolverVersion, UnavailablePackage, UnavailableReason, UnavailableVersion, + ResolverVersion, UnavailablePackage, UnavailableReason, UnavailableVersion, }; use crate::resolver::batch_prefetch::BatchPrefetcher; pub use crate::resolver::derivation::DerivationChainBuilder; @@ -64,6 +64,7 @@ pub use crate::resolver::environment::ResolverEnvironment; pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet}; pub(crate) use crate::resolver::urls::Urls; use crate::universal_marker::{ConflictMarker, UniversalMarker}; +pub(crate) use provider::MetadataUnavailable; pub use crate::resolver::index::InMemoryIndex; use crate::resolver::indexes::Indexes; @@ -118,7 +119,7 @@ struct ResolverState { /// Incompatibilities for packages that are entirely unavailable. unavailable_packages: DashMap, /// Incompatibilities for packages that are unavailable at specific versions. - incomplete_packages: DashMap>, + incomplete_packages: DashMap>, /// The options that were used to configure this resolver. options: Options, /// The reporter to use for this resolver. @@ -354,6 +355,7 @@ impl ResolverState ResolverState &archive.metadata, - MetadataResponse::Offline => { + MetadataResponse::Unavailable(reason) => { self.unavailable_packages - .insert(name.clone(), UnavailablePackage::Offline); + .insert(name.clone(), reason.into()); return Ok(None); } - MetadataResponse::MissingMetadata => { - self.unavailable_packages - .insert(name.clone(), UnavailablePackage::MissingMetadata); - return Ok(None); - } - MetadataResponse::InvalidMetadata(err) => { - self.unavailable_packages.insert( - name.clone(), - UnavailablePackage::InvalidMetadata(err.to_string()), - ); - return Ok(None); - } - MetadataResponse::InconsistentMetadata(err) => { - self.unavailable_packages.insert( - name.clone(), - UnavailablePackage::InvalidMetadata(err.to_string()), - ); - return Ok(None); - } - MetadataResponse::InvalidStructure(err) => { - self.unavailable_packages.insert( - name.clone(), - UnavailablePackage::InvalidStructure(err.to_string()), - ); - return Ok(None); - } - MetadataResponse::RequiresPython(..) => { - unreachable!("`requires-python` is only known upfront for registry distributions") - } MetadataResponse::Error(dist, err) => { // TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't // critical since we fetch URL dependencies _prior_ to invoking the resolver. @@ -1318,82 +1291,20 @@ impl ResolverState &archive.metadata, - MetadataResponse::Offline => { + MetadataResponse::Unavailable(reason) => { + let unavailable_version = UnavailableVersion::from(reason); + let message = unavailable_version.singular_message(); + if let Some(err) = reason.source() { + // Show the detailed error for metadata parse errors. + warn!("{name} {message}: {err}"); + } else { + warn!("{name} {message}"); + } self.incomplete_packages .entry(name.clone()) .or_default() - .insert(version.clone(), IncompletePackage::Offline); - return Ok(Dependencies::Unavailable(UnavailableVersion::Offline)); - } - MetadataResponse::MissingMetadata => { - self.incomplete_packages - .entry(name.clone()) - .or_default() - .insert(version.clone(), IncompletePackage::MissingMetadata); - return Ok(Dependencies::Unavailable( - UnavailableVersion::MissingMetadata, - )); - } - MetadataResponse::InvalidMetadata(err) => { - warn!("Unable to extract metadata for {name}: {err}"); - self.incomplete_packages - .entry(name.clone()) - .or_default() - .insert( - version.clone(), - IncompletePackage::InvalidMetadata(err.to_string()), - ); - return Ok(Dependencies::Unavailable( - UnavailableVersion::InvalidMetadata, - )); - } - MetadataResponse::InconsistentMetadata(err) => { - warn!("Unable to extract metadata for {name}: {err}"); - self.incomplete_packages - .entry(name.clone()) - .or_default() - .insert( - version.clone(), - IncompletePackage::InconsistentMetadata(err.to_string()), - ); - return Ok(Dependencies::Unavailable( - UnavailableVersion::InconsistentMetadata, - )); - } - MetadataResponse::InvalidStructure(err) => { - warn!("Unable to extract metadata for {name}: {err}"); - self.incomplete_packages - .entry(name.clone()) - .or_default() - .insert( - version.clone(), - IncompletePackage::InvalidStructure(err.to_string()), - ); - return Ok(Dependencies::Unavailable( - UnavailableVersion::InvalidStructure, - )); - } - MetadataResponse::RequiresPython(requires_python, python_version) => { - warn!( - "Unable to extract metadata for {name}: {}", - uv_distribution::Error::RequiresPython( - requires_python.clone(), - python_version.clone() - ) - ); - self.incomplete_packages - .entry(name.clone()) - .or_default() - .insert( - version.clone(), - IncompletePackage::RequiresPython( - requires_python.clone(), - python_version.clone(), - ), - ); - return Ok(Dependencies::Unavailable( - UnavailableVersion::RequiresPython(requires_python.clone()), - )); + .insert(version.clone(), reason.clone()); + return Ok(Dependencies::Unavailable(unavailable_version)); } MetadataResponse::Error(dist, err) => { let chain = DerivationChainBuilder::from_state(id, version, pubgrub) @@ -1856,37 +1767,20 @@ impl ResolverState { - trace!("Received built distribution metadata for: {dist}"); - match &metadata { - MetadataResponse::InvalidMetadata(err) => { - warn!("Unable to extract metadata for {dist}: {err}"); + Some(Response::Dist { dist, metadata }) => { + let dist_kind = match dist { + Dist::Built(_) => "built", + Dist::Source(_) => "source", + }; + trace!("Received {dist_kind} distribution metadata for: {dist}"); + if let MetadataResponse::Unavailable(reason) = &metadata { + let message = UnavailableVersion::from(reason).singular_message(); + if let Some(err) = reason.source() { + // Show the detailed error for metadata parse errors. + warn!("{dist} {message}: {err}"); + } else { + warn!("{dist} {message}"); } - MetadataResponse::InvalidStructure(err) => { - warn!("Unable to extract metadata for {dist}: {err}"); - } - _ => {} - } - self.index - .distributions() - .done(dist.version_id(), Arc::new(metadata)); - } - Some(Response::Dist { - dist: Dist::Source(dist), - metadata, - }) => { - trace!("Received source distribution metadata for: {dist}"); - match &metadata { - MetadataResponse::InvalidMetadata(err) => { - warn!("Unable to extract metadata for {dist}: {err}"); - } - MetadataResponse::InvalidStructure(err) => { - warn!("Unable to extract metadata for {dist}: {err}"); - } - _ => {} } self.index .distributions() diff --git a/crates/uv-resolver/src/resolver/provider.rs b/crates/uv-resolver/src/resolver/provider.rs index 943dfa864..29e023846 100644 --- a/crates/uv-resolver/src/resolver/provider.rs +++ b/crates/uv-resolver/src/resolver/provider.rs @@ -34,21 +34,43 @@ pub enum VersionsResponse { pub enum MetadataResponse { /// The wheel metadata was found and parsed successfully. Found(ArchiveMetadata), - /// The wheel metadata was not found. - MissingMetadata, - /// The wheel metadata was found, but could not be parsed. - InvalidMetadata(Box), - /// The wheel metadata was found, but the metadata was inconsistent. - InconsistentMetadata(Box), - /// The wheel has an invalid structure. - InvalidStructure(Box), + /// A non-fatal error. + Unavailable(MetadataUnavailable), + /// The distribution could not be built or downloaded, a fatal error. + Error(Box, Arc), +} + +/// Non-fatal metadata fetching error. +/// +/// This is also the unavailability reasons for a package, while version unavailability is separate +/// in [`UnavailableVersion`]. +#[derive(Debug, Clone)] +pub enum MetadataUnavailable { /// The wheel metadata was not found in the cache and the network is not available. Offline, + /// The wheel metadata was found, but could not be parsed. + InvalidMetadata(Arc), + /// The wheel metadata was found, but the metadata was inconsistent. + InconsistentMetadata(Arc), + /// The wheel has an invalid structure. + InvalidStructure(Arc), /// The source distribution has a `requires-python` requirement that is not met by the installed /// Python version (and static metadata is not available). RequiresPython(VersionSpecifiers, Version), - /// The distribution could not be built or downloaded. - Error(Box, Arc), +} + +impl MetadataUnavailable { + /// Like [`std::error::Error::source`], but we don't want to derive the std error since our + /// formatting system is more custom. + pub(crate) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + MetadataUnavailable::Offline => None, + MetadataUnavailable::InvalidMetadata(err) => Some(err), + MetadataUnavailable::InconsistentMetadata(err) => Some(err), + MetadataUnavailable::InvalidStructure(err) => Some(err), + MetadataUnavailable::RequiresPython(_, _) => None, + } + } } pub trait ResolverProvider { @@ -189,29 +211,39 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a, Ok(metadata) => Ok(MetadataResponse::Found(metadata)), Err(err) => match err { uv_distribution::Error::Client(client) => match client.into_kind() { - uv_client::ErrorKind::Offline(_) => Ok(MetadataResponse::Offline), + uv_client::ErrorKind::Offline(_) => { + Ok(MetadataResponse::Unavailable(MetadataUnavailable::Offline)) + } uv_client::ErrorKind::MetadataParseError(_, _, err) => { - Ok(MetadataResponse::InvalidMetadata(err)) - } - uv_client::ErrorKind::Metadata(_, err) => { - Ok(MetadataResponse::InvalidStructure(Box::new(err))) + Ok(MetadataResponse::Unavailable( + MetadataUnavailable::InvalidMetadata(Arc::new(*err)), + )) } + uv_client::ErrorKind::Metadata(_, err) => Ok(MetadataResponse::Unavailable( + MetadataUnavailable::InvalidStructure(Arc::new(err)), + )), kind => Err(uv_client::Error::from(kind).into()), }, uv_distribution::Error::WheelMetadataVersionMismatch { .. } => { - Ok(MetadataResponse::InconsistentMetadata(Box::new(err))) + Ok(MetadataResponse::Unavailable( + MetadataUnavailable::InconsistentMetadata(Arc::new(err)), + )) } uv_distribution::Error::WheelMetadataNameMismatch { .. } => { - Ok(MetadataResponse::InconsistentMetadata(Box::new(err))) - } - uv_distribution::Error::Metadata(err) => { - Ok(MetadataResponse::InvalidMetadata(Box::new(err))) - } - uv_distribution::Error::WheelMetadata(_, err) => { - Ok(MetadataResponse::InvalidStructure(err)) + Ok(MetadataResponse::Unavailable( + MetadataUnavailable::InconsistentMetadata(Arc::new(err)), + )) } + uv_distribution::Error::Metadata(err) => Ok(MetadataResponse::Unavailable( + MetadataUnavailable::InvalidMetadata(Arc::new(err)), + )), + uv_distribution::Error::WheelMetadata(_, err) => Ok(MetadataResponse::Unavailable( + MetadataUnavailable::InvalidStructure(Arc::new(*err)), + )), uv_distribution::Error::RequiresPython(requires_python, version) => { - Ok(MetadataResponse::RequiresPython(requires_python, version)) + Ok(MetadataResponse::Unavailable( + MetadataUnavailable::RequiresPython(requires_python, version), + )) } err => Ok(MetadataResponse::Error( Box::new(dist.clone()),