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