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, IndexMetadata, IndexMetadataRef, InstalledDist, RequestedDist,
8 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 build_options: &'a BuildOptions,
121 capabilities: &'a IndexCapabilities,
122}
123
124impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
125 pub fn new(
127 fetcher: DistributionDatabase<'a, Context>,
128 flat_index: &'a FlatIndex,
129 tags: Option<&'a Tags>,
130 requires_python: &'a RequiresPython,
131 allowed_yanks: AllowedYanks,
132 hasher: &'a HashStrategy,
133 exclude_newer: ExcludeNewer,
134 build_options: &'a BuildOptions,
135 capabilities: &'a IndexCapabilities,
136 ) -> Self {
137 Self {
138 fetcher,
139 flat_index: flat_index.clone(),
140 tags: tags.cloned(),
141 requires_python: requires_python.clone(),
142 allowed_yanks,
143 hasher: hasher.clone(),
144 exclude_newer,
145 build_options,
146 capabilities,
147 }
148 }
149}
150
151impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Context> {
152 async fn get_package_versions<'io>(
154 &'io self,
155 package_name: &'io PackageName,
156 index: Option<&'io IndexMetadata>,
157 ) -> PackageVersionsResult {
158 let result = self
159 .fetcher
160 .client()
161 .manual(|client, semaphore| {
162 client.simple_detail(
163 package_name,
164 index.map(IndexMetadataRef::from),
165 self.capabilities,
166 semaphore,
167 )
168 })
169 .await;
170
171 let flat_index = index.is_none().then_some(&self.flat_index);
173
174 match result {
175 Ok(results) => Ok(VersionsResponse::Found(
176 results
177 .into_iter()
178 .map(|(index, metadata)| match metadata {
179 MetadataFormat::Simple(metadata) => VersionMap::from_simple_metadata(
180 metadata,
181 package_name,
182 index,
183 self.tags.as_ref(),
184 &self.requires_python,
185 &self.allowed_yanks,
186 &self.hasher,
187 Some(&self.exclude_newer),
188 flat_index
189 .and_then(|flat_index| flat_index.get(package_name))
190 .cloned(),
191 self.build_options,
192 ),
193 MetadataFormat::Flat(metadata) => VersionMap::from_flat_metadata(
194 metadata,
195 self.tags.as_ref(),
196 &self.hasher,
197 self.build_options,
198 ),
199 })
200 .collect(),
201 )),
202 Err(err) => match err.kind() {
203 uv_client::ErrorKind::RemotePackageNotFound(_) => {
204 if let Some(flat_index) = flat_index
205 .and_then(|flat_index| flat_index.get(package_name))
206 .cloned()
207 {
208 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
209 } else {
210 Ok(VersionsResponse::NotFound)
211 }
212 }
213 uv_client::ErrorKind::NoIndex(_) => {
214 if let Some(flat_index) = flat_index
215 .and_then(|flat_index| flat_index.get(package_name))
216 .cloned()
217 {
218 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
219 } else if flat_index.is_some_and(FlatIndex::offline) {
220 Ok(VersionsResponse::Offline)
221 } else {
222 Ok(VersionsResponse::NoIndex)
223 }
224 }
225 uv_client::ErrorKind::Offline(_) => {
226 if let Some(flat_index) = flat_index
227 .and_then(|flat_index| flat_index.get(package_name))
228 .cloned()
229 {
230 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
231 } else {
232 Ok(VersionsResponse::Offline)
233 }
234 }
235 _ => Err(err),
236 },
237 }
238 }
239
240 async fn get_or_build_wheel_metadata<'io>(&'io self, dist: &'io Dist) -> WheelMetadataResult {
242 match self
243 .fetcher
244 .get_or_build_wheel_metadata(dist, self.hasher.get(dist))
245 .await
246 {
247 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
248 Err(err) => match err {
249 uv_distribution::Error::Client(client) => {
250 let retries = client.retries();
251 match client.into_kind() {
252 uv_client::ErrorKind::Offline(_) => {
253 Ok(MetadataResponse::Unavailable(MetadataUnavailable::Offline))
254 }
255 uv_client::ErrorKind::MetadataParseError(_, _, err) => {
256 Ok(MetadataResponse::Unavailable(
257 MetadataUnavailable::InvalidMetadata(Arc::new(*err)),
258 ))
259 }
260 uv_client::ErrorKind::Metadata(_, err) => {
261 Ok(MetadataResponse::Unavailable(
262 MetadataUnavailable::InvalidStructure(Arc::new(err)),
263 ))
264 }
265 kind => Err(uv_client::Error::new(kind, retries).into()),
266 }
267 }
268 uv_distribution::Error::WheelMetadataVersionMismatch { .. } => {
269 Ok(MetadataResponse::Unavailable(
270 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
271 ))
272 }
273 uv_distribution::Error::WheelMetadataNameMismatch { .. } => {
274 Ok(MetadataResponse::Unavailable(
275 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
276 ))
277 }
278 uv_distribution::Error::Metadata(err) => Ok(MetadataResponse::Unavailable(
279 MetadataUnavailable::InvalidMetadata(Arc::new(err)),
280 )),
281 uv_distribution::Error::WheelMetadata(_, err) => Ok(MetadataResponse::Unavailable(
282 MetadataUnavailable::InvalidStructure(Arc::new(*err)),
283 )),
284 uv_distribution::Error::RequiresPython(requires_python, version) => {
285 Ok(MetadataResponse::Unavailable(
286 MetadataUnavailable::RequiresPython(requires_python, version),
287 ))
288 }
289 err => Ok(MetadataResponse::Error(
290 Box::new(RequestedDist::Installable(dist.clone())),
291 Arc::new(err),
292 )),
293 },
294 }
295 }
296
297 async fn get_installed_metadata<'io>(
299 &'io self,
300 dist: &'io InstalledDist,
301 ) -> WheelMetadataResult {
302 match self.fetcher.get_installed_metadata(dist).await {
303 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
304 Err(err) => Ok(MetadataResponse::Error(
305 Box::new(RequestedDist::Installed(dist.clone())),
306 Arc::new(err),
307 )),
308 }
309 }
310
311 fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
313 Self {
314 fetcher: self.fetcher.with_reporter(reporter),
315 ..self
316 }
317 }
318}