Skip to content

Commit 9a52a81

Browse files
committed
Add support for new feature syntax (RFC 3143)
1 parent f76943f commit 9a52a81

File tree

6 files changed

+150
-7
lines changed

6 files changed

+150
-7
lines changed

src/controllers/krate/publish.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use flate2::read::GzDecoder;
44
use hex::ToHex;
55
use sha2::{Digest, Sha256};
6+
use std::collections::HashMap;
67
use std::io::Read;
78
use std::sync::Arc;
89
use swirl::Job;
@@ -213,15 +214,29 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
213214
.uploader()
214215
.upload_crate(&app, tarball, &krate, vers)?;
215216

217+
let (features, features2): (HashMap<_, _>, HashMap<_, _>) =
218+
features.into_iter().partition(|(_k, vals)| {
219+
!vals
220+
.iter()
221+
.any(|v| v.starts_with("dep:") || v.contains("?/"))
222+
});
223+
let (features2, v) = if features2.is_empty() {
224+
(None, None)
225+
} else {
226+
(Some(features2), Some(2))
227+
};
228+
216229
// Register this crate in our local git repo.
217230
let git_crate = git::Crate {
218231
name: name.0,
219232
vers: vers.to_string(),
220233
cksum: hex_cksum,
221234
features,
235+
features2,
222236
deps: git_deps,
223237
yanked: Some(false),
224238
links,
239+
v,
225240
};
226241
git::add_crate(git_crate).enqueue(&conn)?;
227242

src/git.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,13 @@ pub struct Crate {
6262
pub deps: Vec<Dependency>,
6363
pub cksum: String,
6464
pub features: HashMap<String, Vec<String>>,
65+
#[serde(skip_serializing_if = "Option::is_none")]
66+
pub features2: Option<HashMap<String, Vec<String>>>,
6567
pub yanked: Option<bool>,
6668
#[serde(default)]
6769
pub links: Option<String>,
70+
#[serde(skip_serializing_if = "Option::is_none")]
71+
pub v: Option<u32>,
6872
}
6973

7074
#[derive(Serialize, Deserialize, Debug)]

src/models/krate.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,12 +297,15 @@ impl Crate {
297297

298298
/// Validates a whole feature string, `features = ["THIS", "ALL/THIS"]`.
299299
pub fn valid_feature(name: &str) -> bool {
300-
let mut parts = name.split('/');
301-
let name_part = parts.next_back(); // required
302-
let prefix_part = parts.next_back(); // optional
303-
parts.next().is_none()
304-
&& name_part.map_or(false, Crate::valid_feature_name)
305-
&& prefix_part.map_or(true, Crate::valid_feature_prefix)
300+
match name.find('/') {
301+
Some(pos) => {
302+
let (dep, dep_feat) = name.split_at(pos);
303+
let dep_feat = &dep_feat[1..];
304+
let dep = dep.strip_suffix('?').unwrap_or(dep);
305+
Crate::valid_feature_prefix(dep) && Crate::valid_feature_name(dep_feat)
306+
}
307+
None => Crate::valid_feature_name(name.strip_prefix("dep:").unwrap_or(name)),
308+
}
306309
}
307310

308311
/// Return both the newest (most recently updated) and
@@ -498,6 +501,11 @@ mod tests {
498501
assert!(Crate::valid_feature("c++20"));
499502
assert!(Crate::valid_feature("krate/c++20"));
500503
assert!(!Crate::valid_feature("c++20/wow"));
504+
assert!(Crate::valid_feature("foo?/bar"));
505+
assert!(Crate::valid_feature("dep:foo"));
506+
assert!(!Crate::valid_feature("dep:foo?/bar"));
507+
assert!(!Crate::valid_feature("foo/?bar"));
508+
assert!(!Crate::valid_feature("foo?bar"));
501509
}
502510
}
503511

src/tests/builders/publish.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub struct PublishBuilder {
3636
readme: Option<String>,
3737
tarball: Vec<u8>,
3838
version: semver::Version,
39+
features: HashMap<u::EncodableFeatureName, Vec<u::EncodableFeature>>,
3940
}
4041

4142
impl PublishBuilder {
@@ -55,6 +56,7 @@ impl PublishBuilder {
5556
readme: None,
5657
tarball: EMPTY_TARBALL_BYTES.to_vec(),
5758
version: semver::Version::parse("1.0.0").unwrap(),
59+
features: HashMap::new(),
5860
}
5961
}
6062

@@ -166,11 +168,22 @@ impl PublishBuilder {
166168
self
167169
}
168170

171+
// Adds a feature.
172+
pub fn feature(mut self, name: &str, values: &[&str]) -> Self {
173+
let values = values
174+
.iter()
175+
.map(|s| u::EncodableFeature(s.to_string()))
176+
.collect();
177+
self.features
178+
.insert(u::EncodableFeatureName(name.to_string()), values);
179+
self
180+
}
181+
169182
pub fn build(self) -> (String, Vec<u8>) {
170183
let new_crate = u::EncodableCrateUpload {
171184
name: u::EncodableCrateName(self.krate_name.clone()),
172185
vers: u::EncodableCrateVersion(self.version),
173-
features: HashMap::new(),
186+
features: self.features,
174187
deps: self.deps,
175188
description: self.desc,
176189
homepage: None,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
[
2+
{
3+
"request": {
4+
"uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo/foo-1.0.0.crate",
5+
"method": "PUT",
6+
"headers": [
7+
[
8+
"accept",
9+
"*/*"
10+
],
11+
[
12+
"content-length",
13+
"35"
14+
],
15+
[
16+
"host",
17+
"alexcrichton-test.s3.amazonaws.com"
18+
],
19+
[
20+
"accept-encoding",
21+
"gzip"
22+
],
23+
[
24+
"content-type",
25+
"application/x-tar"
26+
],
27+
[
28+
"authorization",
29+
"AWS AKIAICL5IWUZYWWKA7JA:uDc39eNdF6CcwB+q+JwKsoDLQc4="
30+
],
31+
[
32+
"date",
33+
"Fri, 15 Sep 2017 07:53:06 -0700"
34+
]
35+
],
36+
"body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA="
37+
},
38+
"response": {
39+
"status": 200,
40+
"headers": [
41+
[
42+
"x-amz-request-id",
43+
"26589A5E52F8395C"
44+
],
45+
[
46+
"ETag",
47+
"\"f9016ad360cebb4fe2e6e96e5949f022\""
48+
],
49+
[
50+
"date",
51+
"Fri, 15 Sep 2017 14:53:07 GMT"
52+
],
53+
[
54+
"content-length",
55+
"0"
56+
],
57+
[
58+
"x-amz-id-2",
59+
"JdIvnNTw53aqXjBIqBLNuN4kxf/w1XWX+xuIiGBDYy7yzOSDuAMtBSrTW4ZWetcCIdqCUHuQ51A="
60+
],
61+
[
62+
"Server",
63+
"AmazonS3"
64+
]
65+
],
66+
"body": ""
67+
}
68+
}
69+
]

src/tests/krate/publish.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use flate2::Compression;
1313
use http::StatusCode;
1414
use std::collections::HashMap;
1515
use std::io::Read;
16+
use std::iter::FromIterator;
1617
use std::time::Duration;
1718
use std::{io, thread};
1819

@@ -954,3 +955,36 @@ fn publish_rate_limit_doesnt_affect_existing_crates() {
954955
token.enqueue_publish(new_version).good();
955956
app.run_pending_background_jobs();
956957
}
958+
959+
#[test]
960+
fn features_version_2() {
961+
let (app, _, user, token) = TestApp::full().with_token();
962+
963+
app.db(|conn| {
964+
// Insert a crate directly into the database so that foo_new can depend on it
965+
CrateBuilder::new("bar", user.as_model().id).expect_build(conn);
966+
});
967+
968+
let dependency = DependencyBuilder::new("bar");
969+
970+
let crate_to_publish = PublishBuilder::new("foo")
971+
.version("1.0.0")
972+
.dependency(dependency)
973+
.feature("new_feat", &["dep:bar", "bar?/feat"])
974+
.feature("old_feat", &[]);
975+
token.enqueue_publish(crate_to_publish).good();
976+
app.run_pending_background_jobs();
977+
978+
let crates = app.crates_from_index_head("3/f/foo");
979+
assert_eq!(crates.len(), 1);
980+
assert_eq!(crates[0].name, "foo");
981+
assert_eq!(crates[0].deps.len(), 1);
982+
assert_eq!(crates[0].v, Some(2));
983+
let features = HashMap::from_iter([("old_feat".to_string(), vec![])]);
984+
assert_eq!(crates[0].features, features);
985+
let features2 = HashMap::from_iter([(
986+
"new_feat".to_string(),
987+
vec!["dep:bar".to_string(), "bar?/feat".to_string()],
988+
)]);
989+
assert_eq!(crates[0].features2, Some(features2));
990+
}

0 commit comments

Comments
 (0)