Skip to content

Commit 8b42804

Browse files
authored
Merge pull request #9741 from eth3lbert/krate-default-version
Expose the `default_version` on the API
2 parents c7dc7e4 + 4256700 commit 8b42804

File tree

30 files changed

+249
-23
lines changed

30 files changed

+249
-23
lines changed

src/controllers/krate/metadata.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,19 @@ pub async fn show(app: AppState, Path(name): Path<String>, req: Parts) -> AppRes
4545
.transpose()?
4646
.unwrap_or_default();
4747

48-
let (krate, downloads): (Crate, i64) = Crate::by_name(&name)
49-
.inner_join(crate_downloads::table)
50-
.select((Crate::as_select(), crate_downloads::downloads))
51-
.first(conn)
52-
.optional()?
53-
.ok_or_else(|| crate_not_found(&name))?;
48+
let (krate, downloads, default_version): (Crate, i64, Option<String>) =
49+
Crate::by_name(&name)
50+
.inner_join(crate_downloads::table)
51+
.left_join(default_versions::table)
52+
.left_join(versions::table.on(default_versions::version_id.eq(versions::id)))
53+
.select((
54+
Crate::as_select(),
55+
crate_downloads::downloads,
56+
versions::num.nullable(),
57+
))
58+
.first(conn)
59+
.optional()?
60+
.ok_or_else(|| crate_not_found(&name))?;
5461

5562
let versions_publishers_and_audit_actions = if include.versions {
5663
let mut versions_and_publishers: Vec<(Version, Option<User>)> = krate
@@ -120,6 +127,7 @@ pub async fn show(app: AppState, Path(name): Path<String>, req: Parts) -> AppRes
120127

121128
let encodable_crate = EncodableCrate::from(
122129
krate.clone(),
130+
default_version.as_deref(),
123131
top_versions.as_ref(),
124132
ids,
125133
kws.as_deref(),

src/controllers/krate/publish.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
392392
.first(conn)
393393
.optional()?;
394394

395+
let mut default_version = None;
395396
// Upsert the `default_value` determined by the existing `default_value` and the
396397
// published version. Note that this could potentially write an outdated version
397398
// (although this should not happen regularly), as we might be comparing to an
@@ -412,6 +413,8 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
412413
.filter(default_versions::crate_id.eq(krate.id))
413414
.set(default_versions::version_id.eq(version.id))
414415
.execute(conn)?;
416+
} else {
417+
default_version = Some(existing_default_version.num.to_string());
415418
}
416419

417420
// Update the default version asynchronously in a background job
@@ -505,7 +508,15 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
505508
};
506509

507510
Ok(Json(GoodCrate {
508-
krate: EncodableCrate::from_minimal(krate, Some(&top_versions), None, false, downloads, None),
511+
krate: EncodableCrate::from_minimal(
512+
krate,
513+
default_version.or(Some(version_string)).as_deref(),
514+
Some(&top_versions),
515+
None,
516+
false,
517+
downloads,
518+
None,
519+
),
509520
warnings,
510521
}))
511522
})

src/controllers/krate/search.rs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,16 @@ pub async fn search(app: AppState, req: Parts) -> AppResult<Json<Value>> {
8787
crate_downloads::downloads,
8888
recent_crate_downloads::downloads.nullable(),
8989
0_f32.into_sql::<Float>(),
90+
versions::num.nullable(),
9091
);
9192

9293
let mut seek: Option<Seek> = None;
9394
let mut query = filter_params
9495
.make_query(&req, conn)?
9596
.inner_join(crate_downloads::table)
9697
.left_join(recent_crate_downloads::table)
98+
.left_join(default_versions::table)
99+
.left_join(versions::table.on(default_versions::version_id.eq(versions::id)))
97100
.select(selection);
98101

99102
if let Some(q_string) = &filter_params.q_string {
@@ -113,6 +116,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult<Json<Value>> {
113116
crate_downloads::downloads,
114117
recent_crate_downloads::downloads.nullable(),
115118
rank.clone(),
119+
versions::num.nullable(),
116120
));
117121
seek = Some(Seek::Relevance);
118122
query = query.then_order_by(rank.desc())
@@ -123,6 +127,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult<Json<Value>> {
123127
crate_downloads::downloads,
124128
recent_crate_downloads::downloads.nullable(),
125129
0_f32.into_sql::<Float>(),
130+
versions::num.nullable(),
126131
));
127132
seek = Some(Seek::Query);
128133
}
@@ -225,16 +230,19 @@ pub async fn search(app: AppState, req: Parts) -> AppResult<Json<Value>> {
225230

226231
let crates = versions
227232
.zip(data)
228-
.map(|(max_version, (krate, perfect_match, total, recent, _))| {
229-
EncodableCrate::from_minimal(
230-
krate,
231-
Some(&max_version),
232-
Some(vec![]),
233-
perfect_match,
234-
total,
235-
Some(recent.unwrap_or(0)),
236-
)
237-
})
233+
.map(
234+
|(max_version, (krate, perfect_match, total, recent, _, default_version))| {
235+
EncodableCrate::from_minimal(
236+
krate,
237+
default_version.as_deref(),
238+
Some(&max_version),
239+
Some(vec![]),
240+
perfect_match,
241+
total,
242+
Some(recent.unwrap_or(0)),
243+
)
244+
},
245+
)
238246
.collect::<Vec<_>>();
239247

240248
Ok(Json(json!({
@@ -614,6 +622,7 @@ mod seek {
614622
downloads,
615623
recent_downloads,
616624
rank,
625+
_,
617626
) = *record;
618627

619628
match *self {
@@ -636,11 +645,18 @@ mod seek {
636645
}
637646
}
638647

639-
type Record = (Crate, bool, i64, Option<i64>, f32);
648+
type Record = (Crate, bool, i64, Option<i64>, f32, Option<String>);
640649

641650
type QuerySource = LeftJoinQuerySource<
642-
InnerJoinQuerySource<crates::table, crate_downloads::table>,
643-
recent_crate_downloads::table,
651+
LeftJoinQuerySource<
652+
LeftJoinQuerySource<
653+
InnerJoinQuerySource<crates::table, crate_downloads::table>,
654+
recent_crate_downloads::table,
655+
>,
656+
default_versions::table,
657+
>,
658+
versions::table,
659+
diesel::dsl::Eq<default_versions::version_id, versions::id>,
644660
>;
645661

646662
type BoxedCondition<'a> = Box<

src/controllers/summary.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::app::AppState;
22
use crate::models::{Category, Crate, CrateVersions, Keyword, TopVersions, Version};
3-
use crate::schema::{crate_downloads, crates, keywords, metadata, recent_crate_downloads};
3+
use crate::schema::{
4+
crate_downloads, crates, default_versions, keywords, metadata, recent_crate_downloads, versions,
5+
};
46
use crate::tasks::spawn_blocking;
57
use crate::util::diesel::Conn;
68
use crate::util::errors::AppResult;
@@ -25,7 +27,7 @@ pub async fn summary(state: AppState) -> AppResult<Json<Value>> {
2527

2628
fn encode_crates(
2729
conn: &mut impl Conn,
28-
data: Vec<(Crate, i64, Option<i64>)>,
30+
data: Vec<(Crate, i64, Option<i64>, Option<String>)>,
2931
) -> AppResult<Vec<EncodableCrate>> {
3032
let krates = data.iter().map(|(c, ..)| c).collect::<Vec<_>>();
3133
let versions: Vec<Version> = krates.versions().load(conn)?;
@@ -34,9 +36,10 @@ pub async fn summary(state: AppState) -> AppResult<Json<Value>> {
3436
.into_iter()
3537
.map(TopVersions::from_versions)
3638
.zip(data)
37-
.map(|(top_versions, (krate, total, recent))| {
39+
.map(|(top_versions, (krate, total, recent, default_version))| {
3840
Ok(EncodableCrate::from_minimal(
3941
krate,
42+
default_version.as_deref(),
4043
Some(&top_versions),
4144
None,
4245
false,
@@ -51,18 +54,23 @@ pub async fn summary(state: AppState) -> AppResult<Json<Value>> {
5154
Crate::as_select(),
5255
crate_downloads::downloads,
5356
recent_crate_downloads::downloads.nullable(),
57+
versions::num.nullable(),
5458
);
5559

5660
let new_crates = crates::table
5761
.inner_join(crate_downloads::table)
5862
.left_join(recent_crate_downloads::table)
63+
.left_join(default_versions::table)
64+
.left_join(versions::table.on(default_versions::version_id.eq(versions::id)))
5965
.order(crates::created_at.desc())
6066
.select(selection)
6167
.limit(10)
6268
.load(conn)?;
6369
let just_updated = crates::table
6470
.inner_join(crate_downloads::table)
6571
.left_join(recent_crate_downloads::table)
72+
.left_join(default_versions::table)
73+
.left_join(versions::table.on(default_versions::version_id.eq(versions::id)))
6674
.filter(crates::updated_at.ne(crates::created_at))
6775
.order(crates::updated_at.desc())
6876
.select(selection)
@@ -72,6 +80,8 @@ pub async fn summary(state: AppState) -> AppResult<Json<Value>> {
7280
let mut most_downloaded_query = crates::table
7381
.inner_join(crate_downloads::table)
7482
.left_join(recent_crate_downloads::table)
83+
.left_join(default_versions::table)
84+
.left_join(versions::table.on(default_versions::version_id.eq(versions::id)))
7585
.into_boxed();
7686
if !config.excluded_crate_names.is_empty() {
7787
most_downloaded_query =
@@ -86,6 +96,8 @@ pub async fn summary(state: AppState) -> AppResult<Json<Value>> {
8696
let mut most_recently_downloaded_query = crates::table
8797
.inner_join(crate_downloads::table)
8898
.inner_join(recent_crate_downloads::table)
99+
.left_join(default_versions::table)
100+
.left_join(versions::table.on(default_versions::version_id.eq(versions::id)))
89101
.into_boxed();
90102
if !config.excluded_crate_names.is_empty() {
91103
most_recently_downloaded_query = most_recently_downloaded_query

src/tests/krate/publish/basics.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,37 @@ async fn new_krate_twice() {
112112
"###);
113113
}
114114

115+
// This is similar to the `new_krate_twice` case, but the versions are published in reverse order.
116+
// The primary purpose is to verify that the `default_version` we provide is as expected.
117+
#[tokio::test(flavor = "multi_thread")]
118+
async fn new_krate_twice_alt() {
119+
let (app, _, _, token) = TestApp::full().with_token();
120+
121+
let crate_to_publish =
122+
PublishBuilder::new("foo_twice", "2.0.0").description("2.0.0 description");
123+
token.publish_crate(crate_to_publish).await.good();
124+
125+
let crate_to_publish = PublishBuilder::new("foo_twice", "0.99.0");
126+
let response = token.publish_crate(crate_to_publish).await;
127+
assert_eq!(response.status(), StatusCode::OK);
128+
assert_json_snapshot!(response.json(), {
129+
".crate.created_at" => "[datetime]",
130+
".crate.updated_at" => "[datetime]",
131+
});
132+
133+
let crates = app.crates_from_index_head("foo_twice");
134+
assert_json_snapshot!(crates);
135+
136+
assert_snapshot!(app.stored_files().await.join("\n"), @r###"
137+
crates/foo_twice/foo_twice-0.99.0.crate
138+
crates/foo_twice/foo_twice-2.0.0.crate
139+
index/fo/o_/foo_twice
140+
rss/crates.xml
141+
rss/crates/foo_twice.xml
142+
rss/updates.xml
143+
"###);
144+
}
145+
115146
#[tokio::test(flavor = "multi_thread")]
116147
async fn new_krate_duplicate_version() {
117148
let (app, _, user, token) = TestApp::full().with_token();

src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ expression: response.json()
77
"badges": null,
88
"categories": null,
99
"created_at": "[datetime]",
10+
"default_version": "1.0.0",
1011
"description": "description",
1112
"documentation": null,
1213
"downloads": 0,

src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ expression: response.json()
77
"badges": null,
88
"categories": null,
99
"created_at": "[datetime]",
10+
"default_version": "2.0.0",
1011
"description": "2.0.0 description",
1112
"documentation": null,
1213
"downloads": 0,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: src/tests/krate/publish/basics.rs
3+
expression: crates
4+
---
5+
[
6+
{
7+
"name": "foo_twice",
8+
"vers": "2.0.0",
9+
"deps": [],
10+
"cksum": "d6e88a7d30b9e5c3d268ede9a9937b62815e45a06fd2c572d602e0705ab6513d",
11+
"features": {},
12+
"yanked": false
13+
},
14+
{
15+
"name": "foo_twice",
16+
"vers": "0.99.0",
17+
"deps": [],
18+
"cksum": "45b0b19cd0280034e07820789d9bb6e4016526eba85c75fc697d49ec99fd2550",
19+
"features": {},
20+
"yanked": false
21+
}
22+
]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
source: src/tests/krate/publish/basics.rs
3+
expression: response.json()
4+
---
5+
{
6+
"crate": {
7+
"badges": null,
8+
"categories": null,
9+
"created_at": "[datetime]",
10+
"default_version": "2.0.0",
11+
"description": "description",
12+
"documentation": null,
13+
"downloads": 0,
14+
"exact_match": false,
15+
"homepage": null,
16+
"id": "foo_twice",
17+
"keywords": null,
18+
"links": {
19+
"owner_team": "/api/v1/crates/foo_twice/owner_team",
20+
"owner_user": "/api/v1/crates/foo_twice/owner_user",
21+
"owners": "/api/v1/crates/foo_twice/owners",
22+
"reverse_dependencies": "/api/v1/crates/foo_twice/reverse_dependencies",
23+
"version_downloads": "/api/v1/crates/foo_twice/downloads",
24+
"versions": "/api/v1/crates/foo_twice/versions"
25+
},
26+
"max_stable_version": "2.0.0",
27+
"max_version": "2.0.0",
28+
"name": "foo_twice",
29+
"newest_version": "0.99.0",
30+
"recent_downloads": null,
31+
"repository": null,
32+
"updated_at": "[datetime]",
33+
"versions": null
34+
},
35+
"warnings": {
36+
"invalid_badges": [],
37+
"invalid_categories": [],
38+
"other": []
39+
}
40+
}

src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_weird_version.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ expression: response.json()
77
"badges": null,
88
"categories": null,
99
"created_at": "[datetime]",
10+
"default_version": "0.0.0-pre",
1011
"description": "description",
1112
"documentation": null,
1213
"downloads": 0,

0 commit comments

Comments
 (0)