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