Skip to main content

crates_index/
types.rs

1use crate::dedupe::DedupeContext;
2
3use crate::IndexConfig;
4use semver::Version as SemverVersion;
5use serde_derive::{Deserialize, Serialize};
6use smol_str::SmolStr;
7use std::collections::HashMap;
8use std::io;
9use std::path::Path;
10use std::sync::Arc;
11
12/// A single version of a crate (package) published to the index
13#[derive(Serialize, Deserialize, Clone, Debug)]
14pub struct Version {
15    name: SmolStr,
16    vers: SmolStr,
17    deps: Arc<[Dependency]>,
18    features: Arc<HashMap<String, Vec<String>>>,
19    /// It's wrapped in `Option<Box>` to reduce size of the struct when the field is unused (i.e. almost always)
20    /// <https://rust-lang.github.io/rfcs/3143-cargo-weak-namespaced-features.html#index-changes>
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    #[allow(clippy::box_collection)]
23    features2: Option<Box<HashMap<String, Vec<String>>>>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    links: Option<Box<SmolStr>>,
26    #[serde(default)]
27    rust_version: Option<SmolStr>,
28    #[serde(with = "hex")]
29    cksum: [u8; 32],
30    #[serde(default)]
31    yanked: bool,
32}
33
34impl Version {
35    /// Name of the crate
36    #[inline]
37    #[must_use]
38    pub fn name(&self) -> &str {
39        &self.name
40    }
41
42    /// Name of this version
43    #[inline]
44    #[must_use]
45    pub fn version(&self) -> &str {
46        &self.vers
47    }
48
49    /// Dependencies for this version
50    #[inline]
51    #[must_use]
52    pub fn dependencies(&self) -> &[Dependency] {
53        &self.deps
54    }
55
56    /// Checksum of the package for this version
57    ///
58    /// SHA256 of the .crate file
59    #[inline]
60    #[must_use]
61    pub fn checksum(&self) -> &[u8; 32] {
62        &self.cksum
63    }
64
65    /// Explicit features this crate has. This list is not exhaustive,
66    /// because any optional dependency becomes a feature automatically.
67    ///
68    /// `default` is a special feature name for implicitly enabled features.
69    #[inline]
70    #[must_use]
71    pub fn features(&self) -> &HashMap<String, Vec<String>> {
72        &self.features
73    }
74
75    /// combines features and features2
76    ///
77    /// dedupes dependencies and features
78    fn build_data(&mut self, dedupe: &mut DedupeContext) {
79        if let Some(features2) = self.features2.take() {
80            if let Some(f1) = Arc::get_mut(&mut self.features) {
81                for (key, mut val) in features2.into_iter() {
82                    f1.entry(key).or_insert_with(Vec::new).append(&mut val);
83                }
84            }
85        }
86
87        // Many versions have identical dependencies and features
88        dedupe.deps(&mut self.deps);
89        dedupe.features(&mut self.features);
90    }
91
92    /// Exclusivity flag. If this is a sys crate, it informs it
93    /// conflicts with any other crate with the same links string.
94    ///
95    /// It does not involve linker or libraries in any way.
96    #[inline]
97    #[must_use]
98    pub fn links(&self) -> Option<&str> {
99        self.links.as_ref().map(|s| s.as_str())
100    }
101
102    /// Whether this version was [yanked](http://doc.crates.io/crates-io.html#cargo-yank) from the
103    /// index
104    #[inline]
105    #[must_use]
106    pub fn is_yanked(&self) -> bool {
107        self.yanked
108    }
109
110    /// Required version of rust
111    ///
112    /// Corresponds to `package.rust-version`.
113    ///
114    /// Added in 2023 (see <https://github.com/rust-lang/crates.io/pull/6267>),
115    /// can be `None` if published before then or if not set in the manifest.
116    #[inline]
117    #[must_use]
118    pub fn rust_version(&self) -> Option<&str> {
119        self.rust_version.as_deref()
120    }
121
122    /// Where to find crate tarball
123    #[must_use]
124    pub fn download_url(&self, index: &IndexConfig) -> Option<String> {
125        index.download_url(&self.name, &self.vers)
126    }
127}
128
129/// A single dependency of a specific crate version
130#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
131pub struct Dependency {
132    name: SmolStr,
133    req: SmolStr,
134    /// Double indirection to remove size from this struct, since the features are rarely set
135    features: Box<Box<[String]>>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    package: Option<Box<SmolStr>>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    kind: Option<DependencyKind>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    registry: Option<SmolStr>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    target: Option<Box<SmolStr>>,
144    optional: bool,
145    default_features: bool,
146}
147
148impl Dependency {
149    /// Dependency's arbitrary nickname (it may be an alias). Use [`Dependency::crate_name`] for actual crate name.
150    #[inline]
151    #[must_use]
152    pub fn name(&self) -> &str {
153        &self.name
154    }
155
156    /// Semver version pattern
157    #[inline]
158    #[must_use]
159    pub fn requirement(&self) -> &str {
160        &self.req
161    }
162
163    /// Features unconditionally enabled when using this dependency,
164    /// in addition to [`Dependency::has_default_features`] and features enabled through
165    /// parent crate's feature list.
166    #[inline]
167    #[must_use]
168    pub fn features(&self) -> &[String] {
169        &self.features
170    }
171
172    /// If it's optional, it implies a feature of its [`Dependency::name`], and can be enabled through
173    /// the crate's features.
174    #[inline]
175    #[must_use]
176    pub fn is_optional(&self) -> bool {
177        self.optional
178    }
179
180    /// If `true` (default), enable `default` feature of this dependency
181    #[inline]
182    #[must_use]
183    pub fn has_default_features(&self) -> bool {
184        self.default_features
185    }
186
187    /// This dependency is only used when compiling for this `cfg` expression
188    #[inline]
189    #[must_use]
190    pub fn target(&self) -> Option<&str> {
191        self.target.as_ref().map(|s| s.as_str())
192    }
193
194    /// Dev or not
195    #[inline]
196    #[must_use]
197    pub fn kind(&self) -> DependencyKind {
198        self.kind.unwrap_or_default()
199    }
200
201    /// The registry URL, if available.
202    ///
203    /// Example: `https://github.com/rust-lang/crates.io-index.git`
204    #[inline]
205    #[must_use]
206    pub fn registry(&self) -> Option<&str> {
207        self.registry.as_deref()
208    }
209
210    /// Set if dependency's crate name is different from the `name` (alias)
211    #[inline]
212    #[must_use]
213    pub fn package(&self) -> Option<&str> {
214        self.package.as_ref().map(|s| s.as_str())
215    }
216
217    /// Returns the name of the crate providing the dependency.
218    /// This is equivalent to `name()` unless `self.package()`
219    /// is not `None`, in which case it's equal to `self.package()`.
220    ///
221    /// Basically, you can define a dependency in your `Cargo.toml`
222    /// like this:
223    ///
224    /// ```toml
225    /// serde_lib = {version = "1", package = "serde"}
226    /// ```
227    ///
228    /// ...which means that it uses the crate `serde` but imports
229    /// it under the name `serde_lib`.
230    #[inline]
231    #[must_use]
232    pub fn crate_name(&self) -> &str {
233        match self.package {
234            Some(ref s) => s,
235            None => self.name(),
236        }
237    }
238}
239
240/// Section in which this dependency was defined
241#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
242#[serde(rename_all = "lowercase")]
243pub enum DependencyKind {
244    /// Used at run time
245    #[default]
246    Normal,
247    /// Not fetched and not used, except for when used direclty in a workspace
248    Dev,
249    /// Used at build time, not available at run time
250    Build,
251}
252
253/// A whole crate with all its versions
254#[derive(Serialize, Deserialize, Clone, Debug)]
255pub struct Crate {
256    versions: Box<[Version]>,
257}
258
259impl Crate {
260    /// Parse crate file from in-memory JSON data
261    #[inline(never)]
262    pub(crate) fn from_slice_with_context(mut bytes: &[u8], dedupe: &mut DedupeContext) -> io::Result<Crate> {
263        // Trim last newline
264        while bytes.last() == Some(&b'\n') {
265            bytes = &bytes[..bytes.len() - 1];
266        }
267
268        #[inline(always)]
269        fn is_newline(&c: &u8) -> bool {
270            c == b'\n'
271        }
272        let num_versions = bytes.split(is_newline).count();
273        let mut versions = Vec::with_capacity(num_versions);
274        for line in bytes.split(is_newline) {
275            let mut version: Version = serde_json::from_slice(line).map_err(io::Error::other)?;
276
277            version.build_data(dedupe);
278
279            versions.push(version);
280        }
281        if versions.is_empty() {
282            return Err(io::ErrorKind::UnexpectedEof.into());
283        }
284        debug_assert_eq!(versions.len(), versions.capacity());
285        Ok(Crate {
286            versions: versions.into_boxed_slice(),
287        })
288    }
289
290    /// Parse crate index entry from a .cache file, this can fail for a number of reasons
291    ///
292    /// 1. There is no entry for this crate
293    /// 2. The entry was created with an older version than the one specified
294    /// 3. The entry is a newer version than what can be read, would only
295    ///    happen if a future version of cargo changed the format of the cache entries
296    /// 4. The cache entry is malformed somehow
297    #[inline(never)]
298    pub(crate) fn from_cache_slice(bytes: &[u8], index_version: Option<&str>) -> io::Result<Self> {
299        const CURRENT_CACHE_VERSION: u8 = 3;
300        const CURRENT_INDEX_FORMAT_VERSION: u32 = 2;
301
302        // See src/cargo/sources/registry/index.rs
303        let (first_byte, mut rest) = bytes.split_first().ok_or(io::ErrorKind::UnexpectedEof)?;
304
305        match *first_byte {
306            // This is the current 1.54.0 - 1.70.0+ version of cache entries
307            CURRENT_CACHE_VERSION => {
308                let index_v_bytes = rest.get(..4).ok_or(io::ErrorKind::UnexpectedEof)?;
309                let index_v = u32::from_le_bytes(index_v_bytes.try_into().unwrap());
310                if index_v != CURRENT_INDEX_FORMAT_VERSION {
311                    return Err(io::Error::new(
312                        io::ErrorKind::Unsupported,
313                        format!("wrong index format version: {index_v} (expected {CURRENT_INDEX_FORMAT_VERSION}))"),
314                    ));
315                }
316                rest = &rest[4..];
317            }
318            // This is only to support ancient <1.52.0 versions of cargo https://github.com/rust-lang/cargo/pull/9161
319            1 => {}
320            // Note that the change from 2 -> 3 was only to invalidate cache
321            // entries https://github.com/rust-lang/cargo/pull/9476 and
322            // version 2 entries should only be emitted by cargo 1.52.0 and 1.53.0,
323            // but rather than _potentially_ parse bad cache entries as noted in
324            // the PR we explicitly tell the user their version of cargo is suspect
325            // these versions are so old (and specific) it shouldn't affect really anyone
326            2 => {
327                return Err(io::Error::other("potentially invalid version 2 cache entry found"));
328            }
329            version => {
330                return Err(io::Error::new(
331                    io::ErrorKind::Unsupported,
332                    format!("cache version '{version}' not currently supported"),
333                ));
334            }
335        }
336
337        let mut iter = crate::split(rest, 0);
338        let update = iter.next().ok_or(io::ErrorKind::UnexpectedEof)?;
339        if let Some(index_version) = index_version {
340            if update != index_version.as_bytes() {
341                return Err(io::Error::other(format!(
342                    "cache out of date: current index ({index_version}) != cache ({})",
343                    String::from_utf8_lossy(update)
344                )));
345            }
346        }
347
348        Self::from_version_entries_iter(iter)
349    }
350
351    pub(crate) fn from_version_entries_iter<'a, I: Iterator<Item = &'a [u8]> + 'a>(mut iter: I) -> io::Result<Crate> {
352        let mut versions = Vec::new();
353
354        let mut dedupe = DedupeContext::new();
355
356        // Each entry is a tuple of (semver, version_json)
357        while let Some(_version) = iter.next() {
358            let version_slice = iter.next().ok_or(io::ErrorKind::UnexpectedEof)?;
359            let mut version: Version =
360                serde_json::from_slice(version_slice).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
361
362            version.build_data(&mut dedupe);
363
364            versions.push(version);
365        }
366
367        Ok(Self {
368            versions: versions.into_boxed_slice(),
369        })
370    }
371
372    /// Writes a cache entry to disk in the same format as cargo
373    #[cfg(feature = "sparse")]
374    pub(crate) fn write_cache_entry(&self, path: &Path, version: &str) -> io::Result<()> {
375        const CURRENT_CACHE_VERSION: u8 = 3;
376        const CURRENT_INDEX_FORMAT_VERSION: u32 = 2;
377
378        let mut v = Vec::new();
379        v.push(CURRENT_CACHE_VERSION);
380        v.extend_from_slice(&CURRENT_INDEX_FORMAT_VERSION.to_le_bytes());
381        v.extend_from_slice(version.as_bytes());
382        v.push(0);
383
384        for version in self.versions() {
385            v.extend_from_slice(version.version().as_bytes());
386            v.push(0);
387            v.append(&mut serde_json::to_vec(version).unwrap());
388            v.push(0);
389        }
390
391        std::fs::write(path, v)
392    }
393
394    /// All versions of this crate sorted chronologically by date originally published
395    ///
396    /// Warning: may be yanked or duplicate
397    #[inline]
398    #[must_use]
399    pub fn versions(&self) -> &[Version] {
400        &self.versions
401    }
402
403    /// The highest version as per semantic versioning specification
404    ///
405    /// Warning: may be pre-release or yanked
406    #[must_use]
407    pub fn highest_version(&self) -> &Version {
408        self.versions
409            .iter()
410            .max_by_key(|v| SemverVersion::parse(&v.vers).ok())
411            // Safety: Versions inside the index will always adhere to
412            // semantic versioning. If a crate is inside the index, at
413            // least one version is available.
414            .unwrap()
415    }
416
417    /// Returns crate version with the highest version number according to semver,
418    /// but excludes pre-release and yanked versions.
419    ///
420    /// 0.x.y versions are included.
421    ///
422    /// May return `None` if the crate has only pre-release or yanked versions.
423    #[must_use]
424    pub fn highest_normal_version(&self) -> Option<&Version> {
425        self.versions
426            .iter()
427            .filter(|v| !v.is_yanked())
428            .filter_map(|v| Some((v, SemverVersion::parse(&v.vers).ok()?)))
429            .filter(|(_, sem)| sem.pre.is_empty())
430            .max_by(|a, b| a.1.cmp(&b.1))
431            .map(|(v, _)| v)
432    }
433
434    /// Crate's unique registry name. Case-sensitive, mostly.
435    #[inline]
436    #[must_use]
437    pub fn name(&self) -> &str {
438        self.versions[0].name()
439    }
440
441    /// The last release by date, even if it's yanked or less than highest version.
442    ///
443    /// See [`Crate::highest_normal_version`]
444    #[inline]
445    #[must_use]
446    pub fn most_recent_version(&self) -> &Version {
447        &self.versions[self.versions.len() - 1]
448    }
449
450    /// First version ever published. May be yanked.
451    ///
452    /// It is not guaranteed to be the lowest version number.
453    #[inline]
454    #[must_use]
455    pub fn earliest_version(&self) -> &Version {
456        &self.versions[0]
457    }
458
459    /// Unconstrained Latest version
460    ///
461    /// Warning: may not be the highest version and may be yanked
462    #[cold]
463    #[doc(hidden)]
464    #[deprecated(note = "use most_recent_version")]
465    #[must_use]
466    pub fn latest_version(&self) -> &Version {
467        self.most_recent_version()
468    }
469
470    /// Returns the highest version as per semantic versioning specification,
471    /// filtering out versions with pre-release identifiers.
472    ///
473    /// Warning: may be yanked
474    #[cold]
475    #[doc(hidden)]
476    #[deprecated(note = "use highest_normal_version")]
477    #[must_use]
478    pub fn highest_stable_version(&self) -> Option<&Version> {
479        self.versions
480            .iter()
481            .filter_map(|v| Some((v, SemverVersion::parse(&v.vers).ok()?)))
482            .filter(|(_, sem)| sem.pre.is_empty())
483            .max_by(|a, b| a.1.cmp(&b.1))
484            .map(|(v, _)| v)
485    }
486
487    /// Parse an index file with all of crate's versions.
488    ///
489    /// The file must contain at least one version.
490    #[inline]
491    pub fn new<P: AsRef<Path>>(index_path: P) -> io::Result<Crate> {
492        let lines = std::fs::read(index_path)?;
493        Self::from_slice(&lines)
494    }
495
496    /// Parse crate file from in-memory JSON-lines data
497    #[inline]
498    pub fn from_slice(bytes: &[u8]) -> io::Result<Crate> {
499        let mut dedupe = DedupeContext::new();
500        Self::from_slice_with_context(bytes, &mut dedupe)
501    }
502}