Skip to content

Commit 6735663

Browse files
authored
Merge pull request #10208 from Turbo87/path-structs
Extract `CratePath` and `CrateVersionPath` structs
2 parents 796a568 + 37ae592 commit 6735663

File tree

13 files changed

+508
-172
lines changed

13 files changed

+508
-172
lines changed

src/controllers/krate.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
use crate::models::Crate;
2+
use crate::util::errors::{crate_not_found, AppResult};
3+
use axum::extract::{FromRequestParts, Path};
4+
use crates_io_database::schema::crates;
5+
use diesel::{OptionalExtension, QueryDsl};
6+
use diesel_async::{AsyncPgConnection, RunQueryDsl};
7+
use utoipa::IntoParams;
8+
19
pub mod delete;
210
pub mod downloads;
311
pub mod follow;
@@ -6,3 +14,38 @@ pub mod owners;
614
pub mod publish;
715
pub mod search;
816
pub mod versions;
17+
18+
#[derive(Deserialize, FromRequestParts, IntoParams)]
19+
#[into_params(parameter_in = Path)]
20+
#[from_request(via(Path))]
21+
pub struct CratePath {
22+
/// Name of the crate
23+
pub name: String,
24+
}
25+
26+
impl CratePath {
27+
pub async fn load_crate(&self, conn: &mut AsyncPgConnection) -> AppResult<Crate> {
28+
load_crate(conn, &self.name).await
29+
}
30+
31+
pub async fn load_crate_id(&self, conn: &mut AsyncPgConnection) -> AppResult<i32> {
32+
load_crate_id(conn, &self.name).await
33+
}
34+
}
35+
36+
pub async fn load_crate(conn: &mut AsyncPgConnection, name: &str) -> AppResult<Crate> {
37+
Crate::by_name(name)
38+
.first(conn)
39+
.await
40+
.optional()?
41+
.ok_or_else(|| crate_not_found(name))
42+
}
43+
44+
pub async fn load_crate_id(conn: &mut AsyncPgConnection, name: &str) -> AppResult<i32> {
45+
Crate::by_name(name)
46+
.select(crates::id)
47+
.first(conn)
48+
.await
49+
.optional()?
50+
.ok_or_else(|| crate_not_found(name))
51+
}

src/controllers/krate/delete.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::app::AppState;
22
use crate::auth::AuthCheck;
3-
use crate::models::{Crate, NewDeletedCrate, Rights};
3+
use crate::controllers::krate::CratePath;
4+
use crate::models::{NewDeletedCrate, Rights};
45
use crate::schema::{crate_downloads, crates, dependencies};
5-
use crate::util::errors::{crate_not_found, custom, AppResult, BoxedAppError};
6+
use crate::util::errors::{custom, AppResult, BoxedAppError};
67
use crate::worker::jobs;
7-
use axum::extract::Path;
88
use bigdecimal::ToPrimitive;
99
use chrono::{TimeDelta, Utc};
1010
use crates_io_database::schema::deleted_crates;
@@ -30,22 +30,18 @@ const AVAILABLE_AFTER: TimeDelta = TimeDelta::hours(24);
3030
#[utoipa::path(
3131
delete,
3232
path = "/api/v1/crates/{name}",
33+
params(CratePath),
3334
tag = "crates",
3435
responses((status = 200, description = "Successful Response")),
3536
)]
36-
pub async fn delete_crate(
37-
Path(name): Path<String>,
38-
parts: Parts,
39-
app: AppState,
40-
) -> AppResult<StatusCode> {
37+
pub async fn delete_crate(path: CratePath, parts: Parts, app: AppState) -> AppResult<StatusCode> {
4138
let mut conn = app.db_write().await?;
4239

4340
// Check that the user is authenticated
4441
let auth = AuthCheck::only_cookie().check(&parts, &mut conn).await?;
4542

4643
// Check that the crate exists
47-
let krate = find_crate(&mut conn, &name).await?;
48-
let krate = krate.ok_or_else(|| crate_not_found(&name))?;
44+
let krate = path.load_crate(&mut conn).await?;
4945

5046
// Check that the user is an owner of the crate (team owners are not allowed to delete crates)
5147
let user = auth.user();
@@ -106,7 +102,7 @@ pub async fn delete_crate(
106102

107103
let git_index_job = jobs::SyncToGitIndex::new(&krate.name);
108104
let sparse_index_job = jobs::SyncToSparseIndex::new(&krate.name);
109-
let delete_from_storage_job = jobs::DeleteCrateFromStorage::new(name);
105+
let delete_from_storage_job = jobs::DeleteCrateFromStorage::new(path.name);
110106

111107
tokio::try_join!(
112108
git_index_job.enqueue(conn),
@@ -123,10 +119,6 @@ pub async fn delete_crate(
123119
Ok(StatusCode::NO_CONTENT)
124120
}
125121

126-
async fn find_crate(conn: &mut AsyncPgConnection, name: &str) -> QueryResult<Option<Crate>> {
127-
Crate::by_name(name).first(conn).await.optional()
128-
}
129-
130122
async fn get_crate_downloads(conn: &mut AsyncPgConnection, crate_id: i32) -> QueryResult<u64> {
131123
let downloads = crate_downloads::table
132124
.find(crate_id)

src/controllers/krate/downloads.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
//! download counts are located in `version::downloads`.
55
66
use crate::app::AppState;
7-
use crate::models::{Crate, Version, VersionDownload};
8-
use crate::schema::{crates, version_downloads, versions};
7+
use crate::controllers::krate::CratePath;
8+
use crate::models::{Version, VersionDownload};
9+
use crate::schema::{version_downloads, versions};
910
use crate::sql::to_char;
10-
use crate::util::errors::{crate_not_found, AppResult};
11+
use crate::util::errors::AppResult;
1112
use crate::views::EncodableVersionDownload;
12-
use axum::extract::Path;
1313
use axum_extra::json;
1414
use axum_extra::response::ErasedJson;
1515
use diesel::prelude::*;
@@ -23,25 +23,18 @@ use std::cmp;
2323
#[utoipa::path(
2424
get,
2525
path = "/api/v1/crates/{name}/downloads",
26+
params(CratePath),
2627
tag = "crates",
2728
responses((status = 200, description = "Successful Response")),
2829
)]
2930

30-
pub async fn get_crate_downloads(
31-
state: AppState,
32-
Path(crate_name): Path<String>,
33-
) -> AppResult<ErasedJson> {
31+
pub async fn get_crate_downloads(state: AppState, path: CratePath) -> AppResult<ErasedJson> {
3432
let mut conn = state.db_read().await?;
3533

3634
use diesel::dsl::*;
3735
use diesel::sql_types::BigInt;
3836

39-
let crate_id: i32 = Crate::by_name(&crate_name)
40-
.select(crates::id)
41-
.first(&mut conn)
42-
.await
43-
.optional()?
44-
.ok_or_else(|| crate_not_found(&crate_name))?;
37+
let crate_id: i32 = path.load_crate_id(&mut conn).await?;
4538

4639
let mut versions: Vec<Version> = versions::table
4740
.filter(versions::crate_id.eq(crate_id))

src/controllers/krate/follow.rs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
use crate::app::AppState;
44
use crate::auth::AuthCheck;
55
use crate::controllers::helpers::ok_true;
6+
use crate::controllers::krate::CratePath;
67
use crate::models::{Crate, Follow};
78
use crate::schema::*;
89
use crate::util::errors::{crate_not_found, AppResult};
9-
use axum::extract::Path;
1010
use axum::response::Response;
1111
use axum_extra::json;
1212
use axum_extra::response::ErasedJson;
@@ -33,17 +33,14 @@ async fn follow_target(
3333
#[utoipa::path(
3434
put,
3535
path = "/api/v1/crates/{name}/follow",
36+
params(CratePath),
3637
tag = "crates",
3738
responses((status = 200, description = "Successful Response")),
3839
)]
39-
pub async fn follow_crate(
40-
app: AppState,
41-
Path(crate_name): Path<String>,
42-
req: Parts,
43-
) -> AppResult<Response> {
40+
pub async fn follow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult<Response> {
4441
let mut conn = app.db_write().await?;
4542
let user_id = AuthCheck::default().check(&req, &mut conn).await?.user_id();
46-
let follow = follow_target(&crate_name, &mut conn, user_id).await?;
43+
let follow = follow_target(&path.name, &mut conn, user_id).await?;
4744
diesel::insert_into(follows::table)
4845
.values(&follow)
4946
.on_conflict_do_nothing()
@@ -57,17 +54,14 @@ pub async fn follow_crate(
5754
#[utoipa::path(
5855
delete,
5956
path = "/api/v1/crates/{name}/follow",
57+
params(CratePath),
6058
tag = "crates",
6159
responses((status = 200, description = "Successful Response")),
6260
)]
63-
pub async fn unfollow_crate(
64-
app: AppState,
65-
Path(crate_name): Path<String>,
66-
req: Parts,
67-
) -> AppResult<Response> {
61+
pub async fn unfollow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult<Response> {
6862
let mut conn = app.db_write().await?;
6963
let user_id = AuthCheck::default().check(&req, &mut conn).await?.user_id();
70-
let follow = follow_target(&crate_name, &mut conn, user_id).await?;
64+
let follow = follow_target(&path.name, &mut conn, user_id).await?;
7165
diesel::delete(&follow).execute(&mut conn).await?;
7266

7367
ok_true()
@@ -77,12 +71,13 @@ pub async fn unfollow_crate(
7771
#[utoipa::path(
7872
get,
7973
path = "/api/v1/crates/{name}/following",
74+
params(CratePath),
8075
tag = "crates",
8176
responses((status = 200, description = "Successful Response")),
8277
)]
8378
pub async fn get_following_crate(
8479
app: AppState,
85-
Path(crate_name): Path<String>,
80+
path: CratePath,
8681
req: Parts,
8782
) -> AppResult<ErasedJson> {
8883
use diesel::dsl::exists;
@@ -93,7 +88,7 @@ pub async fn get_following_crate(
9388
.await?
9489
.user_id();
9590

96-
let follow = follow_target(&crate_name, &mut conn, user_id).await?;
91+
let follow = follow_target(&path.name, &mut conn, user_id).await?;
9792
let following = diesel::select(exists(follows::table.find(follow.id())))
9893
.get_result::<bool>(&mut conn)
9994
.await?;

src/controllers/krate/metadata.rs

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
77
use crate::app::AppState;
88
use crate::controllers::helpers::pagination::PaginationOptions;
9+
use crate::controllers::krate::CratePath;
10+
use crate::controllers::version::CrateVersionPath;
911
use crate::models::{
1012
Category, Crate, CrateCategory, CrateKeyword, CrateName, Keyword, RecentCrateDownloads, User,
1113
Version, VersionOwnerAction,
@@ -16,7 +18,6 @@ use crate::util::{redirect, RequestUtils};
1618
use crate::views::{
1719
EncodableCategory, EncodableCrate, EncodableDependency, EncodableKeyword, EncodableVersion,
1820
};
19-
use axum::extract::Path;
2021
use axum::response::{IntoResponse, Response};
2122
use axum_extra::json;
2223
use axum_extra::response::ErasedJson;
@@ -37,21 +38,19 @@ use std::str::FromStr;
3738
responses((status = 200, description = "Successful Response")),
3839
)]
3940
pub async fn find_new_crate(app: AppState, req: Parts) -> AppResult<ErasedJson> {
40-
find_crate(app, Path("new".to_string()), req).await
41+
let name = "new".to_string();
42+
find_crate(app, CratePath { name }, req).await
4143
}
4244

4345
/// Get crate metadata.
4446
#[utoipa::path(
4547
get,
4648
path = "/api/v1/crates/{name}",
49+
params(CratePath),
4750
tag = "crates",
4851
responses((status = 200, description = "Successful Response")),
4952
)]
50-
pub async fn find_crate(
51-
app: AppState,
52-
Path(name): Path<String>,
53-
req: Parts,
54-
) -> AppResult<ErasedJson> {
53+
pub async fn find_crate(app: AppState, path: CratePath, req: Parts) -> AppResult<ErasedJson> {
5554
let mut conn = app.db_read().await?;
5655

5756
let include = req
@@ -62,7 +61,7 @@ pub async fn find_crate(
6261
.unwrap_or_default();
6362

6463
let (krate, downloads, default_version, yanked): (Crate, i64, Option<String>, Option<bool>) =
65-
Crate::by_name(&name)
64+
Crate::by_name(&path.name)
6665
.inner_join(crate_downloads::table)
6766
.left_join(default_versions::table)
6867
.left_join(versions::table.on(default_versions::version_id.eq(versions::id)))
@@ -75,7 +74,7 @@ pub async fn find_crate(
7574
.first(&mut conn)
7675
.await
7776
.optional()?
78-
.ok_or_else(|| crate_not_found(&name))?;
77+
.ok_or_else(|| crate_not_found(&path.name))?;
7978

8079
let versions_publishers_and_audit_actions = if include.versions {
8180
let mut versions_and_publishers: Vec<(Version, Option<User>)> =
@@ -250,15 +249,12 @@ impl FromStr for ShowIncludeMode {
250249
#[utoipa::path(
251250
get,
252251
path = "/api/v1/crates/{name}/{version}/readme",
252+
params(CrateVersionPath),
253253
tag = "versions",
254254
responses((status = 200, description = "Successful Response")),
255255
)]
256-
pub async fn get_version_readme(
257-
app: AppState,
258-
Path((crate_name, version)): Path<(String, String)>,
259-
req: Parts,
260-
) -> Response {
261-
let redirect_url = app.storage.readme_location(&crate_name, &version);
256+
pub async fn get_version_readme(app: AppState, path: CrateVersionPath, req: Parts) -> Response {
257+
let redirect_url = app.storage.readme_location(&path.name, &path.version);
262258
if req.wants_json() {
263259
json!({ "url": redirect_url }).into_response()
264260
} else {
@@ -270,23 +266,20 @@ pub async fn get_version_readme(
270266
#[utoipa::path(
271267
get,
272268
path = "/api/v1/crates/{name}/reverse_dependencies",
269+
params(CratePath),
273270
tag = "crates",
274271
responses((status = 200, description = "Successful Response")),
275272
)]
276273
pub async fn list_reverse_dependencies(
277274
app: AppState,
278-
Path(name): Path<String>,
275+
path: CratePath,
279276
req: Parts,
280277
) -> AppResult<ErasedJson> {
281278
let mut conn = app.db_read().await?;
282279

283280
let pagination_options = PaginationOptions::builder().gather(&req)?;
284281

285-
let krate: Crate = Crate::by_name(&name)
286-
.first(&mut conn)
287-
.await
288-
.optional()?
289-
.ok_or_else(|| crate_not_found(&name))?;
282+
let krate = path.load_crate(&mut conn).await?;
290283

291284
let (rev_deps, total) = krate
292285
.reverse_dependencies(&mut conn, pagination_options)

0 commit comments

Comments
 (0)