cargo_util_schemas/
index.rs

1use crate::manifest::RustVersion;
2use semver::Version;
3use serde::{Deserialize, Serialize};
4use std::{borrow::Cow, collections::BTreeMap};
5
6/// A single line in the index representing a single version of a package.
7#[derive(Deserialize, Serialize)]
8#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
9pub struct IndexPackage<'a> {
10    /// Name of the package.
11    #[serde(borrow)]
12    pub name: Cow<'a, str>,
13    /// The version of this dependency.
14    pub vers: Version,
15    /// All kinds of direct dependencies of the package, including dev and
16    /// build dependencies.
17    #[serde(borrow)]
18    pub deps: Vec<RegistryDependency<'a>>,
19    /// Set of features defined for the package, i.e., `[features]` table.
20    #[serde(default)]
21    pub features: BTreeMap<Cow<'a, str>, Vec<Cow<'a, str>>>,
22    /// This field contains features with new, extended syntax. Specifically,
23    /// namespaced features (`dep:`) and weak dependencies (`pkg?/feat`).
24    ///
25    /// This is separated from `features` because versions older than 1.19
26    /// will fail to load due to not being able to parse the new syntax, even
27    /// with a `Cargo.lock` file.
28    pub features2: Option<BTreeMap<Cow<'a, str>, Vec<Cow<'a, str>>>>,
29    /// Checksum for verifying the integrity of the corresponding downloaded package.
30    pub cksum: String,
31    /// If `true`, Cargo will skip this version when resolving.
32    ///
33    /// This was added in 2014. Everything in the crates.io index has this set
34    /// now, so this probably doesn't need to be an option anymore.
35    pub yanked: Option<bool>,
36    /// Native library name this package links to.
37    ///
38    /// Added early 2018 (see <https://github.com/rust-lang/cargo/pull/4978>),
39    /// can be `None` if published before then.
40    pub links: Option<Cow<'a, str>>,
41    /// Required version of rust
42    ///
43    /// Corresponds to `package.rust-version`.
44    ///
45    /// Added in 2023 (see <https://github.com/rust-lang/crates.io/pull/6267>),
46    /// can be `None` if published before then or if not set in the manifest.
47    #[cfg_attr(feature = "unstable-schema", schemars(with = "Option<String>"))]
48    pub rust_version: Option<RustVersion>,
49    /// The publish time for the package.  Unstable.
50    ///
51    /// In ISO8601 with UTC timezone (e.g. 2025-11-12T19:30:12Z)
52    pub pubtime: Option<String>,
53    /// The schema version for this entry.
54    ///
55    /// If this is None, it defaults to version `1`. Entries with unknown
56    /// versions are ignored.
57    ///
58    /// Version `2` schema adds the `features2` field.
59    ///
60    /// Version `3` schema adds `artifact`, `bindep_targes`, and `lib` for
61    /// artifact dependencies support.
62    ///
63    /// This provides a method to safely introduce changes to index entries
64    /// and allow older versions of cargo to ignore newer entries it doesn't
65    /// understand. This is honored as of 1.51, so unfortunately older
66    /// versions will ignore it, and potentially misinterpret version 2 and
67    /// newer entries.
68    ///
69    /// The intent is that versions older than 1.51 will work with a
70    /// pre-existing `Cargo.lock`, but they may not correctly process `cargo
71    /// update` or build a lock from scratch. In that case, cargo may
72    /// incorrectly select a new package that uses a new index schema. A
73    /// workaround is to downgrade any packages that are incompatible with the
74    /// `--precise` flag of `cargo update`.
75    pub v: Option<u32>,
76}
77
78/// A dependency as encoded in the [`IndexPackage`] index JSON.
79#[derive(Deserialize, Serialize, Clone)]
80#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
81pub struct RegistryDependency<'a> {
82    /// Name of the dependency. If the dependency is renamed, the original
83    /// would be stored in [`RegistryDependency::package`].
84    #[serde(borrow)]
85    pub name: Cow<'a, str>,
86    /// The SemVer requirement for this dependency.
87    #[serde(borrow)]
88    pub req: Cow<'a, str>,
89    /// Set of features enabled for this dependency.
90    #[serde(default)]
91    pub features: Vec<Cow<'a, str>>,
92    /// Whether or not this is an optional dependency.
93    #[serde(default)]
94    pub optional: bool,
95    /// Whether or not default features are enabled.
96    #[serde(default = "default_true")]
97    pub default_features: bool,
98    /// The target platform for this dependency.
99    pub target: Option<Cow<'a, str>>,
100    /// The dependency kind. "dev", "build", and "normal".
101    pub kind: Option<Cow<'a, str>>,
102    /// The URL of the index of the registry where this dependency is from.
103    /// `None` if it is from the same index.
104    pub registry: Option<Cow<'a, str>>,
105    /// The original name if the dependency is renamed.
106    pub package: Option<Cow<'a, str>>,
107    /// Whether or not this is a public dependency. Unstable. See [RFC 1977].
108    ///
109    /// [RFC 1977]: https://rust-lang.github.io/rfcs/1977-public-private-dependencies.html
110    pub public: Option<bool>,
111    /// The artifacts to build from this dependency.
112    pub artifact: Option<Vec<Cow<'a, str>>>,
113    /// The target for bindep.
114    pub bindep_target: Option<Cow<'a, str>>,
115    /// Whether or not this is a library dependency.
116    #[serde(default)]
117    pub lib: bool,
118}
119
120fn default_true() -> bool {
121    true
122}
123
124#[test]
125fn escaped_char_in_index_json_blob() {
126    let _: IndexPackage<'_> = serde_json::from_str(
127        r#"{"name":"a","vers":"0.0.1","deps":[],"cksum":"bae3","features":{}}"#,
128    )
129    .unwrap();
130    let _: IndexPackage<'_> = serde_json::from_str(
131        r#"{"name":"a","vers":"0.0.1","deps":[],"cksum":"bae3","features":{"test":["k","q"]},"links":"a-sys"}"#
132    ).unwrap();
133
134    // Now we add escaped cher all the places they can go
135    // these are not valid, but it should error later than json parsing
136    let _: IndexPackage<'_> = serde_json::from_str(
137        r#"{
138        "name":"This name has a escaped cher in it \n\t\" ",
139        "vers":"0.0.1",
140        "deps":[{
141            "name": " \n\t\" ",
142            "req": " \n\t\" ",
143            "features": [" \n\t\" "],
144            "optional": true,
145            "default_features": true,
146            "target": " \n\t\" ",
147            "kind": " \n\t\" ",
148            "registry": " \n\t\" "
149        }],
150        "cksum":"bae3",
151        "features":{"test \n\t\" ":["k \n\t\" ","q \n\t\" "]},
152        "links":" \n\t\" "}"#,
153    )
154    .unwrap();
155}
156
157#[cfg(feature = "unstable-schema")]
158#[test]
159fn dump_index_schema() {
160    let schema = schemars::schema_for!(crate::index::IndexPackage<'_>);
161    let dump = serde_json::to_string_pretty(&schema).unwrap();
162    snapbox::assert_data_eq!(dump, snapbox::file!("../index.schema.json").raw());
163}