1use std::future::Future;
2use std::sync::Arc;
3use uv_client::MetadataFormat;
4use uv_configuration::BuildOptions;
5use uv_distribution::{ArchiveMetadata, DistributionDatabase, Reporter};
6use uv_distribution_types::{
7 Dist, IndexCapabilities, IndexLocations, IndexMetadata, IndexMetadataRef, InstalledDist,
8 RequestedDist, RequiresPython,
9};
10use uv_normalize::PackageName;
11use uv_pep440::{Version, VersionSpecifiers};
12use uv_platform_tags::Tags;
13use uv_static::EnvVars;
14use uv_types::{BuildContext, HashStrategy};
15
16use crate::ExcludeNewer;
17use crate::flat_index::FlatIndex;
18use crate::version_map::VersionMap;
19use crate::yanks::AllowedYanks;
20
21pub type PackageVersionsResult = Result<VersionsResponse, uv_client::Error>;
22pub type WheelMetadataResult = Result<MetadataResponse, uv_distribution::Error>;
23
24#[derive(Debug)]
26pub enum VersionsResponse {
27 Found(Vec<VersionMap>),
29 NotFound,
31 NoIndex,
33 Offline,
35}
36
37#[derive(Debug)]
38pub enum MetadataResponse {
39 Found(ArchiveMetadata),
41 Unavailable(MetadataUnavailable),
43 Error(Box<RequestedDist>, Arc<uv_distribution::Error>),
45}
46
47#[derive(Debug, Clone)]
52pub enum MetadataUnavailable {
53 Offline,
55 InvalidMetadata(Arc<uv_pypi_types::MetadataError>),
57 InconsistentMetadata(Arc<uv_distribution::Error>),
59 InvalidStructure(Arc<uv_metadata::Error>),
61 RequiresPython(VersionSpecifiers, Version),
64}
65
66impl MetadataUnavailable {
67 pub(crate) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70 match self {
71 Self::Offline => None,
72 Self::InvalidMetadata(err) => Some(err),
73 Self::InconsistentMetadata(err) => Some(err),
74 Self::InvalidStructure(err) => Some(err),
75 Self::RequiresPython(_, _) => None,
76 }
77 }
78}
79
80pub trait ResolverProvider {
81 fn get_package_versions<'io>(
83 &'io self,
84 package_name: &'io PackageName,
85 index: Option<&'io IndexMetadata>,
86 ) -> impl Future<Output = PackageVersionsResult> + 'io;
87
88 fn get_or_build_wheel_metadata<'io>(
94 &'io self,
95 dist: &'io Dist,
96 ) -> impl Future<Output = WheelMetadataResult> + 'io;
97
98 fn get_installed_metadata<'io>(
100 &'io self,
101 dist: &'io InstalledDist,
102 ) -> impl Future<Output = WheelMetadataResult> + 'io;
103
104 #[must_use]
106 fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self;
107}
108
109pub struct DefaultResolverProvider<'a, Context: BuildContext> {
112 fetcher: DistributionDatabase<'a, Context>,
114 flat_index: FlatIndex,
116 tags: Option<Tags>,
117 requires_python: RequiresPython,
118 allowed_yanks: AllowedYanks,
119 hasher: HashStrategy,
120 exclude_newer: ExcludeNewer,
121 available_version_cutoff: Option<jiff::Timestamp>,
122 index_locations: &'a IndexLocations,
123 build_options: &'a BuildOptions,
124 capabilities: &'a IndexCapabilities,
125}
126
127impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
128 pub fn new(
130 fetcher: DistributionDatabase<'a, Context>,
131 flat_index: &'a FlatIndex,
132 tags: Option<&'a Tags>,
133 requires_python: &'a RequiresPython,
134 allowed_yanks: AllowedYanks,
135 hasher: &'a HashStrategy,
136 exclude_newer: ExcludeNewer,
137 index_locations: &'a IndexLocations,
138 build_options: &'a BuildOptions,
139 capabilities: &'a IndexCapabilities,
140 ) -> Self {
141 Self {
142 fetcher,
143 flat_index: flat_index.clone(),
144 tags: tags.cloned(),
145 requires_python: requires_python.clone(),
146 allowed_yanks,
147 hasher: hasher.clone(),
148 exclude_newer,
149 available_version_cutoff: std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
150 .ok()
151 .and_then(|value| value.parse().ok()),
152 index_locations,
153 build_options,
154 capabilities,
155 }
156 }
157
158 fn effective_exclude_newer(
159 &self,
160 package_name: &PackageName,
161 index: &uv_distribution_types::IndexUrl,
162 ) -> Option<jiff::Timestamp> {
163 self.exclude_newer.exclude_newer_package_for_index(
164 package_name,
165 self.index_locations.exclude_newer_for(index),
166 )
167 }
168}
169
170impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Context> {
171 async fn get_package_versions<'io>(
173 &'io self,
174 package_name: &'io PackageName,
175 index: Option<&'io IndexMetadata>,
176 ) -> PackageVersionsResult {
177 let result = self
178 .fetcher
179 .client()
180 .manual(|client, semaphore| {
181 client.simple_detail(
182 package_name,
183 index.map(IndexMetadataRef::from),
184 self.capabilities,
185 semaphore,
186 )
187 })
188 .await;
189
190 let flat_index = index.is_none().then_some(&self.flat_index);
192
193 match result {
194 Ok(results) => Ok(VersionsResponse::Found(
195 results
196 .into_iter()
197 .map(|(index, metadata)| {
198 let included_version_cutoff =
199 self.effective_exclude_newer(package_name, index);
200 let available_version_cutoff = included_version_cutoff
201 .is_none()
202 .then_some(self.available_version_cutoff)
203 .flatten();
204
205 match metadata {
206 MetadataFormat::Simple(metadata) => VersionMap::from_simple_metadata(
207 metadata,
208 package_name,
209 index,
210 self.tags.as_ref(),
211 &self.requires_python,
212 &self.allowed_yanks,
213 &self.hasher,
214 included_version_cutoff,
215 available_version_cutoff,
216 flat_index
217 .and_then(|flat_index| flat_index.get(package_name))
218 .cloned(),
219 self.build_options,
220 ),
221 MetadataFormat::Flat(metadata) => VersionMap::from_flat_metadata(
222 metadata,
223 self.tags.as_ref(),
224 &self.hasher,
225 self.build_options,
226 ),
227 }
228 })
229 .collect(),
230 )),
231 Err(err) => match err.kind() {
232 uv_client::ErrorKind::RemotePackageNotFound(_) => {
233 if let Some(flat_index) = flat_index
234 .and_then(|flat_index| flat_index.get(package_name))
235 .cloned()
236 {
237 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
238 } else {
239 Ok(VersionsResponse::NotFound)
240 }
241 }
242 uv_client::ErrorKind::NoIndex(_) => {
243 if let Some(flat_index) = flat_index
244 .and_then(|flat_index| flat_index.get(package_name))
245 .cloned()
246 {
247 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
248 } else if flat_index.is_some_and(FlatIndex::offline) {
249 Ok(VersionsResponse::Offline)
250 } else {
251 Ok(VersionsResponse::NoIndex)
252 }
253 }
254 uv_client::ErrorKind::Offline(_) => {
255 if let Some(flat_index) = flat_index
256 .and_then(|flat_index| flat_index.get(package_name))
257 .cloned()
258 {
259 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
260 } else {
261 Ok(VersionsResponse::Offline)
262 }
263 }
264 _ => Err(err),
265 },
266 }
267 }
268
269 async fn get_or_build_wheel_metadata<'io>(&'io self, dist: &'io Dist) -> WheelMetadataResult {
271 match self
272 .fetcher
273 .get_or_build_wheel_metadata(dist, self.hasher.get(dist))
274 .await
275 {
276 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
277 Err(err) => match err {
278 uv_distribution::Error::Client(client) => {
279 let retries = client.retries();
280 let duration = client.duration();
281 match client.into_kind() {
282 uv_client::ErrorKind::Offline(_) => {
283 Ok(MetadataResponse::Unavailable(MetadataUnavailable::Offline))
284 }
285 uv_client::ErrorKind::MetadataParseError(_, _, err) => {
286 Ok(MetadataResponse::Unavailable(
287 MetadataUnavailable::InvalidMetadata(Arc::new(*err)),
288 ))
289 }
290 uv_client::ErrorKind::Metadata(_, err) => {
291 Ok(MetadataResponse::Unavailable(
292 MetadataUnavailable::InvalidStructure(Arc::new(err)),
293 ))
294 }
295 kind => Err(uv_client::Error::new(kind, retries, duration).into()),
296 }
297 }
298 uv_distribution::Error::WheelMetadataVersionMismatch { .. } => {
299 Ok(MetadataResponse::Unavailable(
300 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
301 ))
302 }
303 uv_distribution::Error::WheelMetadataNameMismatch { .. } => {
304 Ok(MetadataResponse::Unavailable(
305 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
306 ))
307 }
308 uv_distribution::Error::Metadata(err) => Ok(MetadataResponse::Unavailable(
309 MetadataUnavailable::InvalidMetadata(Arc::new(err)),
310 )),
311 uv_distribution::Error::WheelMetadata(_, err) => Ok(MetadataResponse::Unavailable(
312 MetadataUnavailable::InvalidStructure(Arc::new(*err)),
313 )),
314 uv_distribution::Error::RequiresPython(requires_python, version) => {
315 Ok(MetadataResponse::Unavailable(
316 MetadataUnavailable::RequiresPython(requires_python, version),
317 ))
318 }
319 err => Ok(MetadataResponse::Error(
320 Box::new(RequestedDist::Installable(dist.clone())),
321 Arc::new(err),
322 )),
323 },
324 }
325 }
326
327 async fn get_installed_metadata<'io>(
329 &'io self,
330 dist: &'io InstalledDist,
331 ) -> WheelMetadataResult {
332 match self.fetcher.get_installed_metadata(dist).await {
333 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
334 Err(err) => Ok(MetadataResponse::Error(
335 Box::new(RequestedDist::Installed(dist.clone())),
336 Arc::new(err),
337 )),
338 }
339 }
340
341 fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
343 Self {
344 fetcher: self.fetcher.with_reporter(reporter),
345 ..self
346 }
347 }
348}