Skip to content

Commit 5c68d55

Browse files
authored
Merge pull request #6518 from Turbo87/build-metadata-block
Prevent similar versions that only differ in build metadata from being published
2 parents 0f95c06 + 456fb9b commit 5c68d55

9 files changed

+359
-5
lines changed

src/models/version.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::util::errors::{cargo_err, AppResult};
77

88
use crate::models::{Crate, Dependency, User};
99
use crate::schema::*;
10+
use crate::sql::split_part;
1011

1112
// Queryable has a custom implementation below
1213
#[derive(Clone, Identifiable, Associations, Debug, Queryable, Deserialize, Serialize)]
@@ -168,14 +169,16 @@ impl NewVersion {
168169
use diesel::{insert_into, select};
169170

170171
conn.transaction(|conn| {
172+
let num_no_build = strip_build_metadata(&self.num);
173+
171174
let already_uploaded = versions
172175
.filter(crate_id.eq(self.crate_id))
173-
.filter(num.eq(&self.num));
176+
.filter(split_part(num, "+", 1).eq(num_no_build));
177+
174178
if select(exists(already_uploaded)).get_result(conn)? {
175179
return Err(cargo_err(&format_args!(
176-
"crate version `{}` is already \
177-
uploaded",
178-
self.num
180+
"crate version `{}` is already uploaded",
181+
num_no_build
179182
)));
180183
}
181184

@@ -219,6 +222,13 @@ fn validate_license_expr(s: &str) -> AppResult<()> {
219222
Ok(())
220223
}
221224

225+
fn strip_build_metadata(version: &str) -> &str {
226+
version
227+
.split_once('+')
228+
.map(|parts| parts.0)
229+
.unwrap_or(version)
230+
}
231+
222232
#[cfg(test)]
223233
mod tests {
224234
use super::{validate_license_expr, TopVersions};

src/sql.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use diesel::sql_types::{Date, Double, Interval, SingleValue, Text, Timestamp};
1+
use diesel::sql_types::{Date, Double, Integer, Interval, SingleValue, Text, Timestamp};
22

33
sql_function!(#[aggregate] fn array_agg<T: SingleValue>(x: T) -> Array<T>);
44
sql_function!(fn canon_crate_name(x: Text) -> Text);
@@ -12,3 +12,4 @@ sql_function! {
1212
sql_function!(fn floor(x: Double) -> Integer);
1313
sql_function!(fn greatest<T: SingleValue>(x: T, y: T) -> T);
1414
sql_function!(fn least<T: SingleValue>(x: T, y: T) -> T);
15+
sql_function!(fn split_part(string: Text, delimiter: Text, n: Integer) -> Text);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[
2+
{
3+
"request": {
4+
"uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo/foo-1.0.0+foo.crate",
5+
"method": "PUT",
6+
"headers": [
7+
[
8+
"accept",
9+
"*/*"
10+
],
11+
[
12+
"accept-encoding",
13+
"gzip"
14+
],
15+
[
16+
"content-length",
17+
"35"
18+
],
19+
[
20+
"content-type",
21+
"application/gzip"
22+
]
23+
],
24+
"body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA="
25+
},
26+
"response": {
27+
"status": 200,
28+
"headers": [],
29+
"body": ""
30+
}
31+
},
32+
{
33+
"request": {
34+
"uri": "http://alexcrichton-test.s3.amazonaws.com/3/f/foo",
35+
"method": "PUT",
36+
"headers": [
37+
[
38+
"accept",
39+
"*/*"
40+
],
41+
[
42+
"accept-encoding",
43+
"gzip"
44+
],
45+
[
46+
"content-length",
47+
"148"
48+
],
49+
[
50+
"content-type",
51+
"text/plain"
52+
]
53+
],
54+
"body": "{\"name\":\"foo\",\"vers\":\"1.0.0+foo\",\"deps\":[],\"cksum\":\"acb5604b126ac894c1eb11c4575bf2072fea61232a888e453770c79d7ed56419\",\"features\":{},\"yanked\":false}\n"
55+
},
56+
"response": {
57+
"status": 200,
58+
"headers": [],
59+
"body": ""
60+
}
61+
}
62+
]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[
2+
{
3+
"request": {
4+
"uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo/foo-1.0.0-beta.1.crate",
5+
"method": "PUT",
6+
"headers": [
7+
[
8+
"accept",
9+
"*/*"
10+
],
11+
[
12+
"accept-encoding",
13+
"gzip"
14+
],
15+
[
16+
"content-length",
17+
"35"
18+
],
19+
[
20+
"content-type",
21+
"application/gzip"
22+
]
23+
],
24+
"body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA="
25+
},
26+
"response": {
27+
"status": 200,
28+
"headers": [],
29+
"body": ""
30+
}
31+
},
32+
{
33+
"request": {
34+
"uri": "http://alexcrichton-test.s3.amazonaws.com/3/f/foo",
35+
"method": "PUT",
36+
"headers": [
37+
[
38+
"accept",
39+
"*/*"
40+
],
41+
[
42+
"accept-encoding",
43+
"gzip"
44+
],
45+
[
46+
"content-length",
47+
"151"
48+
],
49+
[
50+
"content-type",
51+
"text/plain"
52+
]
53+
],
54+
"body": "{\"name\":\"foo\",\"vers\":\"1.0.0-beta.1\",\"deps\":[],\"cksum\":\"acb5604b126ac894c1eb11c4575bf2072fea61232a888e453770c79d7ed56419\",\"features\":{},\"yanked\":false}\n"
55+
},
56+
"response": {
57+
"status": 200,
58+
"headers": [],
59+
"body": ""
60+
}
61+
}
62+
]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[
2+
{
3+
"request": {
4+
"uri": "http://alexcrichton-test.s3.amazonaws.com/crates/foo/foo-1.0.0+foo.crate",
5+
"method": "PUT",
6+
"headers": [
7+
[
8+
"accept",
9+
"*/*"
10+
],
11+
[
12+
"accept-encoding",
13+
"gzip"
14+
],
15+
[
16+
"content-length",
17+
"35"
18+
],
19+
[
20+
"content-type",
21+
"application/gzip"
22+
]
23+
],
24+
"body": "H4sIAAAAAAAA/+3AAQEAAACCIP+vbkhQwKsBLq+17wAEAAA="
25+
},
26+
"response": {
27+
"status": 200,
28+
"headers": [],
29+
"body": ""
30+
}
31+
},
32+
{
33+
"request": {
34+
"uri": "http://alexcrichton-test.s3.amazonaws.com/3/f/foo",
35+
"method": "PUT",
36+
"headers": [
37+
[
38+
"accept",
39+
"*/*"
40+
],
41+
[
42+
"accept-encoding",
43+
"gzip"
44+
],
45+
[
46+
"content-length",
47+
"148"
48+
],
49+
[
50+
"content-type",
51+
"text/plain"
52+
]
53+
],
54+
"body": "{\"name\":\"foo\",\"vers\":\"1.0.0+foo\",\"deps\":[],\"cksum\":\"acb5604b126ac894c1eb11c4575bf2072fea61232a888e453770c79d7ed56419\",\"features\":{},\"yanked\":false}\n"
55+
},
56+
"response": {
57+
"status": 200,
58+
"headers": [],
59+
"body": ""
60+
}
61+
}
62+
]

src/tests/krate/publish.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::builders::{CrateBuilder, DependencyBuilder, PublishBuilder};
22
use crate::new_category;
3+
use crate::util::insta::assert_yaml_snapshot;
34
use crate::util::{RequestHelper, TestApp};
45
use cargo_registry::controllers::krate::publish::{
56
missing_metadata_error_message, MISSING_RIGHTS_ERROR_MESSAGE, WILDCARD_ERROR_MESSAGE,
@@ -1035,3 +1036,54 @@ fn empty_payload() {
10351036
json!({ "errors": [{ "detail": "invalid metadata length" }] })
10361037
);
10371038
}
1039+
1040+
fn version_with_build_metadata(v1: &str, v2: &str, expected_error: &str) {
1041+
let (_app, _anon, _cookie, token) = TestApp::full().with_token();
1042+
1043+
let response = token.publish_crate(PublishBuilder::new("foo").version(v1));
1044+
assert_eq!(response.status(), StatusCode::OK);
1045+
assert_yaml_snapshot!(response.into_json(), {
1046+
".crate.created_at" => "[datetime]",
1047+
".crate.updated_at" => "[datetime]",
1048+
});
1049+
1050+
let response = token.publish_crate(PublishBuilder::new("foo").version(v2));
1051+
assert_eq!(response.status(), StatusCode::OK);
1052+
assert_eq!(
1053+
response.into_json(),
1054+
json!({ "errors": [{ "detail": expected_error }] })
1055+
);
1056+
}
1057+
1058+
#[test]
1059+
fn version_with_build_metadata_1() {
1060+
insta::with_settings!({ snapshot_suffix => "build_metadata_1" }, {
1061+
version_with_build_metadata(
1062+
"1.0.0+foo",
1063+
"1.0.0+bar",
1064+
"crate version `1.0.0` is already uploaded",
1065+
);
1066+
});
1067+
}
1068+
1069+
#[test]
1070+
fn version_with_build_metadata_2() {
1071+
insta::with_settings!({ snapshot_suffix => "build_metadata_2" }, {
1072+
version_with_build_metadata(
1073+
"1.0.0-beta.1",
1074+
"1.0.0-beta.1+2",
1075+
"crate version `1.0.0-beta.1` is already uploaded",
1076+
);
1077+
});
1078+
}
1079+
1080+
#[test]
1081+
fn version_with_build_metadata_3() {
1082+
insta::with_settings!({ snapshot_suffix => "build_metadata_3" }, {
1083+
version_with_build_metadata(
1084+
"1.0.0+foo",
1085+
"1.0.0",
1086+
"crate version `1.0.0` is already uploaded",
1087+
);
1088+
});
1089+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
source: src/tests/krate/publish.rs
3+
expression: response.into_json()
4+
---
5+
crate:
6+
badges: ~
7+
categories: ~
8+
created_at: "[datetime]"
9+
description: description
10+
documentation: ~
11+
downloads: 0
12+
exact_match: false
13+
homepage: ~
14+
id: foo
15+
keywords: ~
16+
links:
17+
owner_team: /api/v1/crates/foo/owner_team
18+
owner_user: /api/v1/crates/foo/owner_user
19+
owners: /api/v1/crates/foo/owners
20+
reverse_dependencies: /api/v1/crates/foo/reverse_dependencies
21+
version_downloads: /api/v1/crates/foo/downloads
22+
versions: /api/v1/crates/foo/versions
23+
max_stable_version: 1.0.0+foo
24+
max_version: 1.0.0+foo
25+
name: foo
26+
newest_version: 1.0.0+foo
27+
recent_downloads: ~
28+
repository: ~
29+
updated_at: "[datetime]"
30+
versions: ~
31+
warnings:
32+
invalid_badges: []
33+
invalid_categories: []
34+
other: []
35+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
source: src/tests/krate/publish.rs
3+
expression: response.into_json()
4+
---
5+
crate:
6+
badges: ~
7+
categories: ~
8+
created_at: "[datetime]"
9+
description: description
10+
documentation: ~
11+
downloads: 0
12+
exact_match: false
13+
homepage: ~
14+
id: foo
15+
keywords: ~
16+
links:
17+
owner_team: /api/v1/crates/foo/owner_team
18+
owner_user: /api/v1/crates/foo/owner_user
19+
owners: /api/v1/crates/foo/owners
20+
reverse_dependencies: /api/v1/crates/foo/reverse_dependencies
21+
version_downloads: /api/v1/crates/foo/downloads
22+
versions: /api/v1/crates/foo/versions
23+
max_stable_version: ~
24+
max_version: 1.0.0-beta.1
25+
name: foo
26+
newest_version: 1.0.0-beta.1
27+
recent_downloads: ~
28+
repository: ~
29+
updated_at: "[datetime]"
30+
versions: ~
31+
warnings:
32+
invalid_badges: []
33+
invalid_categories: []
34+
other: []
35+

0 commit comments

Comments
 (0)