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)]
242#[serde(rename_all = "lowercase")]
243pub enum DependencyKind {
244    /// Used at run time
245    Normal,
246    /// Not fetched and not used, except for when used direclty in a workspace
247    Dev,
248    /// Used at build time, not available at run time
249    Build,
250}
251
252impl Default for DependencyKind {
253    fn default() -> Self {
254        Self::Normal
255    }
256}
257
258/// A whole crate with all its versions
259#[derive(Serialize, Deserialize, Clone, Debug)]
260pub struct Crate {
261    versions: Box<[Version]>,
262}
263
264impl Crate {
265    /// Parse crate file from in-memory JSON data
266    #[inline(never)]
267    pub(crate) fn from_slice_with_context(mut bytes: &[u8], dedupe: &mut DedupeContext) -> io::Result<Crate> {
268        // Trim last newline
269        while bytes.last() == Some(&b'\n') {
270            bytes = &bytes[..bytes.len() - 1];
271        }
272
273        #[inline(always)]
274        fn is_newline(&c: &u8) -> bool {
275            c == b'\n'
276        }
277        let num_versions = bytes.split(is_newline).count();
278        let mut versions = Vec::with_capacity(num_versions);
279        for line in bytes.split(is_newline) {
280            let mut version: Version =
281                serde_json::from_slice(line).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
282
283            version.build_data(dedupe);
284
285            versions.push(version);
286        }
287        if versions.is_empty() {
288            return Err(io::ErrorKind::UnexpectedEof.into());
289        }
290        debug_assert_eq!(versions.len(), versions.capacity());
291        Ok(Crate {
292            versions: versions.into_boxed_slice(),
293        })
294    }
295
296    /// Parse crate index entry from a .cache file, this can fail for a number of reasons
297    ///
298    /// 1. There is no entry for this crate
299    /// 2. The entry was created with an older version than the one specified
300    /// 3. The entry is a newer version than what can be read, would only
301    ///    happen if a future version of cargo changed the format of the cache entries
302    /// 4. The cache entry is malformed somehow
303    #[inline(never)]
304    pub(crate) fn from_cache_slice(bytes: &[u8], index_version: Option<&str>) -> io::Result<Self> {
305        const CURRENT_CACHE_VERSION: u8 = 3;
306        const CURRENT_INDEX_FORMAT_VERSION: u32 = 2;
307
308        // See src/cargo/sources/registry/index.rs
309        let (first_byte, mut rest) = bytes.split_first().ok_or(io::ErrorKind::UnexpectedEof)?;
310
311        match *first_byte {
312            // This is the current 1.54.0 - 1.70.0+ version of cache entries
313            CURRENT_CACHE_VERSION => {
314                let index_v_bytes = rest.get(..4).ok_or(io::ErrorKind::UnexpectedEof)?;
315                let index_v = u32::from_le_bytes(index_v_bytes.try_into().unwrap());
316                if index_v != CURRENT_INDEX_FORMAT_VERSION {
317                    return Err(io::Error::new(
318                        io::ErrorKind::Unsupported,
319                        format!("wrong index format version: {index_v} (expected {CURRENT_INDEX_FORMAT_VERSION}))"),
320                    ));
321                }
322                rest = &rest[4..];
323            }
324            // This is only to support ancient <1.52.0 versions of cargo https://github.com/rust-lang/cargo/pull/9161
325            1 => {}
326            // Note that the change from 2 -> 3 was only to invalidate cache
327            // entries https://github.com/rust-lang/cargo/pull/9476 and
328            // version 2 entries should only be emitted by cargo 1.52.0 and 1.53.0,
329            // but rather than _potentially_ parse bad cache entries as noted in
330            // the PR we explicitly tell the user their version of cargo is suspect
331            // these versions are so old (and specific) it shouldn't affect really anyone
332            2 => {
333                return Err(io::Error::new(
334                    io::ErrorKind::Other,
335                    "potentially invalid version 2 cache entry found",
336                ));
337            }
338            version => {
339                return Err(io::Error::new(
340                    io::ErrorKind::Unsupported,
341                    format!("cache version '{version}' not currently supported"),
342                ));
343            }
344        }
345
346        let mut iter = crate::split(rest, 0);
347        let update = iter.next().ok_or(io::ErrorKind::UnexpectedEof)?;
348        if let Some(index_version) = index_version {
349            if update != index_version.as_bytes() {
350                return Err(io::Error::new(
351                    io::ErrorKind::Other,
352                    format!(
353                        "cache out of date: current index ({index_version}) != cache ({})",
354                        String::from_utf8_lossy(update)
355                    ),
356                ));
357            }
358        }
359
360        Self::from_version_entries_iter(iter)
361    }
362
363    pub(crate) fn from_version_entries_iter<'a, I: Iterator<Item = &'a [u8]> + 'a>(mut iter: I) -> io::Result<Crate> {
364        let mut versions = Vec::new();
365
366        let mut dedupe = DedupeContext::new();
367
368        // Each entry is a tuple of (semver, version_json)
369        while let Some(_version) = iter.next() {
370            let version_slice = iter.next().ok_or(io::ErrorKind::UnexpectedEof)?;
371            let mut version: Version =
372                serde_json::from_slice(version_slice).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
373
374            version.build_data(&mut dedupe);
375
376            versions.push(version);
377        }
378
379        Ok(Self {
380            versions: versions.into_boxed_slice(),
381        })
382    }
383
384    /// Writes a cache entry to disk in the same format as cargo
385    #[cfg(feature = "sparse")]
386    pub(crate) fn write_cache_entry(&self, path: &Path, version: &str) -> io::Result<()> {
387        const CURRENT_CACHE_VERSION: u8 = 3;
388        const CURRENT_INDEX_FORMAT_VERSION: u32 = 2;
389
390        let mut v = Vec::new();
391        v.push(CURRENT_CACHE_VERSION);
392        v.extend_from_slice(&CURRENT_INDEX_FORMAT_VERSION.to_le_bytes());
393        v.extend_from_slice(version.as_bytes());
394        v.push(0);
395
396        for version in self.versions() {
397            v.extend_from_slice(version.version().as_bytes());
398            v.push(0);
399            v.append(&mut serde_json::to_vec(version).unwrap());
400            v.push(0);
401        }
402
403        std::fs::write(path, v)
404    }
405
406    /// All versions of this crate sorted chronologically by date originally published
407    ///
408    /// Warning: may be yanked or duplicate
409    #[inline]
410    #[must_use]
411    pub fn versions(&self) -> &[Version] {
412        &self.versions
413    }
414
415    /// The highest version as per semantic versioning specification
416    ///
417    /// Warning: may be pre-release or yanked
418    #[must_use]
419    pub fn highest_version(&self) -> &Version {
420        self.versions
421            .iter()
422            .max_by_key(|v| SemverVersion::parse(&v.vers).ok())
423            // Safety: Versions inside the index will always adhere to
424            // semantic versioning. If a crate is inside the index, at
425            // least one version is available.
426            .unwrap()
427    }
428
429    /// Returns crate version with the highest version number according to semver,
430    /// but excludes pre-release and yanked versions.
431    ///
432    /// 0.x.y versions are included.
433    ///
434    /// May return `None` if the crate has only pre-release or yanked versions.
435    #[must_use]
436    pub fn highest_normal_version(&self) -> Option<&Version> {
437        self.versions
438            .iter()
439            .filter(|v| !v.is_yanked())
440            .filter_map(|v| Some((v, SemverVersion::parse(&v.vers).ok()?)))
441            .filter(|(_, sem)| sem.pre.is_empty())
442            .max_by(|a, b| a.1.cmp(&b.1))
443            .map(|(v, _)| v)
444    }
445
446    /// Crate's unique registry name. Case-sensitive, mostly.
447    #[inline]
448    #[must_use]
449    pub fn name(&self) -> &str {
450        self.versions[0].name()
451    }
452
453    /// The last release by date, even if it's yanked or less than highest version.
454    ///
455    /// See [`Crate::highest_normal_version`]
456    #[inline]
457    #[must_use]
458    pub fn most_recent_version(&self) -> &Version {
459        &self.versions[self.versions.len() - 1]
460    }
461
462    /// First version ever published. May be yanked.
463    ///
464    /// It is not guaranteed to be the lowest version number.
465    #[inline]
466    #[must_use]
467    pub fn earliest_version(&self) -> &Version {
468        &self.versions[0]
469    }
470
471    /// Unconstrained Latest version
472    ///
473    /// Warning: may not be the highest version and may be yanked
474    #[cold]
475    #[doc(hidden)]
476    #[deprecated(note = "use most_recent_version")]
477    #[must_use]
478    pub fn latest_version(&self) -> &Version {
479        self.most_recent_version()
480    }
481
482    /// Returns the highest version as per semantic versioning specification,
483    /// filtering out versions with pre-release identifiers.
484    ///
485    /// Warning: may be yanked
486    #[cold]
487    #[doc(hidden)]
488    #[deprecated(note = "use highest_normal_version")]
489    #[must_use]
490    pub fn highest_stable_version(&self) -> Option<&Version> {
491        self.versions
492            .iter()
493            .filter_map(|v| Some((v, SemverVersion::parse(&v.vers).ok()?)))
494            .filter(|(_, sem)| sem.pre.is_empty())
495            .max_by(|a, b| a.1.cmp(&b.1))
496            .map(|(v, _)| v)
497    }
498
499    /// Parse an index file with all of crate's versions.
500    ///
501    /// The file must contain at least one version.
502    #[inline]
503    pub fn new<P: AsRef<Path>>(index_path: P) -> io::Result<Crate> {
504        let lines = std::fs::read(index_path)?;
505        Self::from_slice(&lines)
506    }
507
508    /// Parse crate file from in-memory JSON-lines data
509    #[inline]
510    pub fn from_slice(bytes: &[u8]) -> io::Result<Crate> {
511        let mut dedupe = DedupeContext::new();
512        Self::from_slice_with_context(bytes, &mut dedupe)
513    }
514}