Skip to content

Commit 37eeab7

Browse files
authored
feat(schema): Expose IndexPackage, the description of a package within a Registry Index (#15770)
### What does this PR try to resolve? ref #14834 In this pull request, I moved `IndexPackage` to `cargo-util-schemas` to enable third-party applications and `cargo-dev-registry` to utilize this common struct. For instance, crates.io has its own `IndexPackage` located at https://github.com/rust-lang/crates.io/blob/005667f0416d03932a50309db6cc672945b033fe/crates/crates_io_index/data.rs#L5. By moving it to `cargo-util-schemas`, we allow different applications to use this struct more easily. ### How to test and review this PR? It shouldn't break any tests; it's just a refactoring.
2 parents c629f5b + 8811325 commit 37eeab7

File tree

7 files changed

+443
-238
lines changed

7 files changed

+443
-238
lines changed

crates/cargo-util-schemas/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ workspace = true
2525

2626
[dev-dependencies]
2727
snapbox.workspace = true
28+
serde_json.workspace = true
2829

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

crates/cargo-util-schemas/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//! > ecosystem. This crate follows semver compatibility for its APIs.
1010
1111
pub mod core;
12+
pub mod index;
1213
pub mod manifest;
1314
pub mod messages;
1415
#[cfg(feature = "unstable-schema")]

src/cargo/ops/cargo_package/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId};
1919
use crate::ops::lockfile::LOCKFILE_NAME;
2020
use crate::ops::registry::{RegistryOrIndex, infer_registry};
2121
use crate::sources::path::PathEntry;
22-
use crate::sources::registry::index::{IndexPackage, RegistryDependency};
2322
use crate::sources::{CRATES_IO_REGISTRY, PathSource};
2423
use crate::util::FileLock;
2524
use crate::util::Filesystem;
@@ -35,6 +34,7 @@ use crate::util::toml::prepare_for_publish;
3534
use crate::{drop_println, ops};
3635
use anyhow::{Context as _, bail};
3736
use cargo_util::paths;
37+
use cargo_util_schemas::index::{IndexPackage, RegistryDependency};
3838
use cargo_util_schemas::messages;
3939
use flate2::{Compression, GzBuilder};
4040
use tar::{Builder, EntryType, Header, HeaderMode};

0 commit comments

Comments
 (0)