Skip to main content

cargo/sources/
source.rs

1//! [`Source`] trait for sources of Cargo packages.
2
3use std::collections::hash_map::HashMap;
4use std::fmt;
5use std::rc::Rc;
6
7use crate::core::SourceId;
8use crate::core::{Dependency, Package, PackageId};
9use crate::sources::IndexSummary;
10use crate::util::CargoResult;
11
12/// An abstraction of different sources of Cargo packages.
13///
14/// The [`Source`] trait generalizes the API to interact with these providers.
15/// For example,
16///
17/// * [`Source::query`] is for querying package metadata on a given
18///   [`Dependency`] requested by a Cargo manifest.
19/// * [`Source::download`] is for fetching the full package information on
20///   given names and versions.
21/// * [`Source::source_id`] is for defining an unique identifier of a source to
22///   distinguish one source from another, keeping Cargo safe from [dependency
23///   confusion attack].
24///
25/// Normally, developers don't need to implement their own [`Source`]s. Cargo
26/// provides several kinds of sources implementations that should cover almost
27/// all use cases. See [`crate::sources`] for implementations provided by Cargo.
28///
29/// [dependency confusion attack]: https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
30#[async_trait::async_trait(?Send)]
31pub trait Source {
32    /// Returns the [`SourceId`] corresponding to this source.
33    fn source_id(&self) -> SourceId;
34
35    /// Returns the replaced [`SourceId`] corresponding to this source.
36    fn replaced_source_id(&self) -> SourceId {
37        self.source_id()
38    }
39
40    /// Returns whether or not this source will return [`IndexSummary`] items with
41    /// checksums listed.
42    fn supports_checksums(&self) -> bool;
43
44    /// Returns whether or not this source will return [`IndexSummary`] items with
45    /// the `precise` field in the [`SourceId`] listed.
46    fn requires_precise(&self) -> bool;
47
48    /// Attempts to find the packages that match a dependency request.
49    ///
50    /// The `f` argument is expected to get called when any [`IndexSummary`] becomes available.
51    async fn query(
52        &self,
53        dep: &Dependency,
54        kind: QueryKind,
55        f: &mut dyn FnMut(IndexSummary),
56    ) -> CargoResult<()>;
57
58    /// Gathers the result from [`Source::query`] as a list of [`IndexSummary`] items
59    /// when they become available.
60    async fn query_vec(&self, dep: &Dependency, kind: QueryKind) -> CargoResult<Vec<IndexSummary>> {
61        let mut ret = Vec::new();
62        self.query(dep, kind, &mut |s| ret.push(s))
63            .await
64            .map(|()| ret)
65    }
66
67    /// Ensure that the source is fully up-to-date for the current session on the next query.
68    fn invalidate_cache(&self);
69
70    /// If quiet, the source should not display any progress or status messages.
71    fn set_quiet(&mut self, quiet: bool);
72
73    /// Starts the process to fetch a [`Package`] for the given [`PackageId`].
74    ///
75    /// If the source already has the package available on disk, then it
76    /// should return immediately with [`MaybePackage::Ready`] with the
77    /// [`Package`]. Otherwise it should return a [`MaybePackage::Download`]
78    /// to indicate the URL to download the package (this is for remote
79    /// registry sources only).
80    ///
81    /// In the case where [`MaybePackage::Download`] is returned, then the
82    /// package downloader will call [`Source::finish_download`] after the
83    /// download has finished.
84    fn download(&self, package: PackageId) -> CargoResult<MaybePackage>;
85
86    /// Gives the source the downloaded `.crate` file.
87    ///
88    /// When a source has returned [`MaybePackage::Download`] in the
89    /// [`Source::download`] method, then this function will be called with
90    /// the results of the download of the given URL. The source is
91    /// responsible for saving to disk, and returning the appropriate
92    /// [`Package`].
93    fn finish_download(&self, pkg_id: PackageId, contents: Vec<u8>) -> CargoResult<Package>;
94
95    /// Generates a unique string which represents the fingerprint of the
96    /// current state of the source.
97    ///
98    /// This fingerprint is used to determine the "freshness" of the source
99    /// later on. It must be guaranteed that the fingerprint of a source is
100    /// constant if and only if the output product will remain constant.
101    ///
102    /// The `pkg` argument is the package which this fingerprint should only be
103    /// interested in for when this source may contain multiple packages.
104    fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
105
106    /// If this source supports it, verifies the source of the package
107    /// specified.
108    ///
109    /// Note that the source may also have performed other checksum-based
110    /// verification during the `download` step, but this is intended to be run
111    /// just before a crate is compiled so it may perform more expensive checks
112    /// which may not be cacheable.
113    fn verify(&self, _pkg: PackageId) -> CargoResult<()> {
114        Ok(())
115    }
116
117    /// Describes this source in a human readable fashion, used for display in
118    /// resolver error messages currently.
119    fn describe(&self) -> String;
120
121    /// Returns whether a source is being replaced by another here.
122    ///
123    /// Builtin replacement of `crates.io` doesn't count as replacement here.
124    fn is_replaced(&self) -> bool {
125        false
126    }
127
128    /// Add a number of crates that should be whitelisted for showing up during
129    /// queries, even if they are yanked. Currently only applies to registry
130    /// sources.
131    fn add_to_yanked_whitelist(&self, pkgs: &[PackageId]);
132
133    /// Query if a package is yanked. Only registry sources can mark packages
134    /// as yanked. This ignores the yanked whitelist.
135    async fn is_yanked(&self, pkg: PackageId) -> CargoResult<bool>;
136}
137
138/// Defines how a dependency query will be performed for a [`Source`].
139#[derive(Copy, Clone, PartialEq, Eq)]
140pub enum QueryKind {
141    /// A query for packages exactly matching the given dependency requirement.
142    ///
143    /// Each source gets to define what `exact` means for it.
144    Exact,
145    /// A query for packages close to the given dependency requirement.
146    ///
147    /// Each source gets to define what `close` means for it.
148    ///
149    /// Path/Git sources may return all dependencies that are at that URI,
150    /// whereas an `Registry` source may return dependencies that are yanked or invalid.
151    RejectedVersions,
152    /// A query for packages close to the given dependency requirement.
153    ///
154    /// Each source gets to define what `close` means for it.
155    ///
156    /// Path/Git sources may return all dependencies that are at that URI,
157    /// whereas an `Registry` source may return dependencies that have the same
158    /// canonicalization.
159    AlternativeNames,
160    /// Match a dependency in all ways and will normalize the package name.
161    /// Each source defines what normalizing means.
162    Normalized,
163}
164
165/// A download status that represents if a [`Package`] has already been
166/// downloaded, or if not then a location to download.
167pub enum MaybePackage {
168    /// The [`Package`] is already downloaded.
169    Ready(Package),
170    /// Not yet downloaded. Here is the URL to download the [`Package`] from.
171    Download {
172        /// URL to download the content.
173        url: String,
174        /// Text to display to the user of what is being downloaded.
175        descriptor: String,
176        /// Authorization data that may be required to attach when downloading.
177        authorization: Option<String>,
178    },
179}
180
181/// A blanket implementation forwards all methods to [`Source`].
182#[async_trait::async_trait(?Send)]
183impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T {
184    fn source_id(&self) -> SourceId {
185        (**self).source_id()
186    }
187
188    fn replaced_source_id(&self) -> SourceId {
189        (**self).replaced_source_id()
190    }
191
192    fn supports_checksums(&self) -> bool {
193        (**self).supports_checksums()
194    }
195
196    fn requires_precise(&self) -> bool {
197        (**self).requires_precise()
198    }
199
200    async fn query(
201        &self,
202        dep: &Dependency,
203        kind: QueryKind,
204        f: &mut dyn FnMut(IndexSummary),
205    ) -> CargoResult<()> {
206        (**self).query(dep, kind, f).await
207    }
208
209    fn invalidate_cache(&self) {
210        (**self).invalidate_cache()
211    }
212
213    fn set_quiet(&mut self, quiet: bool) {
214        (**self).set_quiet(quiet)
215    }
216
217    fn download(&self, id: PackageId) -> CargoResult<MaybePackage> {
218        (**self).download(id)
219    }
220
221    fn finish_download(&self, id: PackageId, data: Vec<u8>) -> CargoResult<Package> {
222        (**self).finish_download(id, data)
223    }
224
225    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
226        (**self).fingerprint(pkg)
227    }
228
229    fn verify(&self, pkg: PackageId) -> CargoResult<()> {
230        (**self).verify(pkg)
231    }
232
233    fn describe(&self) -> String {
234        (**self).describe()
235    }
236
237    fn is_replaced(&self) -> bool {
238        (**self).is_replaced()
239    }
240
241    fn add_to_yanked_whitelist(&self, pkgs: &[PackageId]) {
242        (**self).add_to_yanked_whitelist(pkgs);
243    }
244
245    async fn is_yanked(&self, pkg: PackageId) -> CargoResult<bool> {
246        (**self).is_yanked(pkg).await
247    }
248}
249
250/// A [`HashMap`] of [`SourceId`] to `Box<Source>`.
251#[derive(Default)]
252pub struct SourceMap<'src> {
253    map: HashMap<SourceId, Rc<dyn Source + 'src>>,
254}
255
256// `impl Debug` on source requires specialization, if even desirable at all.
257impl<'src> fmt::Debug for SourceMap<'src> {
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        write!(f, "SourceMap ")?;
260        f.debug_set().entries(self.map.keys()).finish()
261    }
262}
263
264impl<'src> SourceMap<'src> {
265    /// Creates an empty map.
266    pub fn new() -> SourceMap<'src> {
267        SourceMap {
268            map: HashMap::new(),
269        }
270    }
271
272    /// Like `HashMap::get`.
273    pub fn get(&self, id: SourceId) -> Option<&Rc<dyn Source + 'src>> {
274        self.map.get(&id)
275    }
276
277    /// Like `HashMap::insert`, but derives the [`SourceId`] key from the [`Source`].
278    pub fn insert(&mut self, source: Box<dyn Source + 'src>) {
279        let id = source.source_id();
280        self.map.insert(id, source.into());
281    }
282
283    /// Like `HashMap::len`.
284    pub fn len(&self) -> usize {
285        self.map.len()
286    }
287
288    /// Like `HashMap::iter`.
289    pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a SourceId, &'a (dyn Source + 'src))> {
290        self.map.iter().map(|(a, b)| (a, &**b))
291    }
292
293    /// Merge the given map into self.
294    pub fn add_source_map(&mut self, other: SourceMap<'src>) {
295        for (key, value) in other.map {
296            self.map.entry(key).or_insert(value);
297        }
298    }
299}