Skip to content

Commit a093332

Browse files
authored
Merge pull request #10201 from Turbo87/utoipa-router
utoipa: Add annotations to (almost) all endpoints
2 parents 18a9bb1 + dd69739 commit a093332

27 files changed

+1243
-209
lines changed

Diff for: src/controllers/category.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@ use diesel::QueryDsl;
1212
use diesel_async::RunQueryDsl;
1313
use http::request::Parts;
1414

15-
/// Handles the `GET /categories` route.
15+
/// List all categories.
16+
#[utoipa::path(
17+
get,
18+
path = "/api/v1/categories",
19+
operation_id = "list_categories",
20+
tag = "categories",
21+
responses((status = 200, description = "Successful Response")),
22+
)]
1623
pub async fn index(app: AppState, req: Parts) -> AppResult<ErasedJson> {
1724
// FIXME: There are 69 categories, 47 top level. This isn't going to
1825
// grow by an OoM. We need a limit for /summary, but we don't need
@@ -41,7 +48,14 @@ pub async fn index(app: AppState, req: Parts) -> AppResult<ErasedJson> {
4148
}))
4249
}
4350

44-
/// Handles the `GET /categories/:category_id` route.
51+
/// Get category metadata.
52+
#[utoipa::path(
53+
get,
54+
path = "/api/v1/categories/{category}",
55+
operation_id = "get_category",
56+
tag = "categories",
57+
responses((status = 200, description = "Successful Response")),
58+
)]
4559
pub async fn show(state: AppState, Path(slug): Path<String>) -> AppResult<ErasedJson> {
4660
let mut conn = state.db_read().await?;
4761

@@ -74,7 +88,14 @@ pub async fn show(state: AppState, Path(slug): Path<String>) -> AppResult<Erased
7488
Ok(json!({ "category": cat_with_subcats }))
7589
}
7690

77-
/// Handles the `GET /category_slugs` route.
91+
/// List all available category slugs.
92+
#[utoipa::path(
93+
get,
94+
path = "/api/v1/category_slugs",
95+
operation_id = "list_category_slugs",
96+
tag = "categories",
97+
responses((status = 200, description = "Successful Response")),
98+
)]
7899
pub async fn slugs(state: AppState) -> AppResult<ErasedJson> {
79100
let mut conn = state.db_read().await?;
80101

Diff for: src/controllers/crate_owner_invitation.rs

+32-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ use http::request::Parts;
2323
use indexmap::IndexMap;
2424
use std::collections::{HashMap, HashSet};
2525

26-
/// Handles the `GET /api/v1/me/crate_owner_invitations` route.
26+
/// List all crate owner invitations for the authenticated user.
27+
#[utoipa::path(
28+
get,
29+
path = "/api/v1/me/crate_owner_invitations",
30+
operation_id = "list_crate_owner_invitations_for_user",
31+
tag = "owners",
32+
responses((status = 200, description = "Successful Response")),
33+
)]
2734
pub async fn list(app: AppState, req: Parts) -> AppResult<ErasedJson> {
2835
let mut conn = app.db_read().await?;
2936
let auth = AuthCheck::only_cookie().check(&req, &mut conn).await?;
@@ -61,7 +68,14 @@ pub async fn list(app: AppState, req: Parts) -> AppResult<ErasedJson> {
6168
}))
6269
}
6370

64-
/// Handles the `GET /api/private/crate_owner_invitations` route.
71+
/// List all crate owner invitations for a crate or user.
72+
#[utoipa::path(
73+
get,
74+
path = "/api/private/crate_owner_invitations",
75+
operation_id = "list_crate_owner_invitations",
76+
tag = "owners",
77+
responses((status = 200, description = "Successful Response")),
78+
)]
6579
pub async fn private_list(app: AppState, req: Parts) -> AppResult<Json<PrivateListResponse>> {
6680
let mut conn = app.db_read().await?;
6781
let auth = AuthCheck::only_cookie().check(&req, &mut conn).await?;
@@ -265,7 +279,14 @@ struct OwnerInvitation {
265279
crate_owner_invite: InvitationResponse,
266280
}
267281

268-
/// Handles the `PUT /api/v1/me/crate_owner_invitations/:crate_id` route.
282+
/// Accept or decline a crate owner invitation.
283+
#[utoipa::path(
284+
put,
285+
path = "/api/v1/me/crate_owner_invitations/{crate_id}",
286+
operation_id = "handle_crate_owner_invitation",
287+
tag = "owners",
288+
responses((status = 200, description = "Successful Response")),
289+
)]
269290
pub async fn handle_invite(state: AppState, req: BytesRequest) -> AppResult<ErasedJson> {
270291
let (parts, body) = req.0.into_parts();
271292

@@ -293,7 +314,14 @@ pub async fn handle_invite(state: AppState, req: BytesRequest) -> AppResult<Eras
293314
Ok(json!({ "crate_owner_invitation": crate_invite }))
294315
}
295316

296-
/// Handles the `PUT /api/v1/me/crate_owner_invitations/accept/:token` route.
317+
/// Accept a crate owner invitation with a token.
318+
#[utoipa::path(
319+
put,
320+
path = "/api/v1/me/crate_owner_invitations/accept/{token}",
321+
operation_id = "accept_crate_owner_invitation_with_token",
322+
tag = "owners",
323+
responses((status = 200, description = "Successful Response")),
324+
)]
297325
pub async fn handle_invite_with_token(
298326
state: AppState,
299327
Path(token): Path<String>,

Diff for: src/controllers/keyword.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ pub struct IndexQuery {
1515
sort: Option<String>,
1616
}
1717

18-
/// Handles the `GET /keywords` route.
18+
/// List all keywords.
19+
#[utoipa::path(
20+
get,
21+
path = "/api/v1/keywords",
22+
operation_id = "list_keywords",
23+
tag = "keywords",
24+
responses((status = 200, description = "Successful Response")),
25+
)]
1926
pub async fn index(state: AppState, qp: Query<IndexQuery>, req: Parts) -> AppResult<ErasedJson> {
2027
use crate::schema::keywords;
2128

@@ -42,7 +49,14 @@ pub async fn index(state: AppState, qp: Query<IndexQuery>, req: Parts) -> AppRes
4249
}))
4350
}
4451

45-
/// Handles the `GET /keywords/:keyword_id` route.
52+
/// Get keyword metadata.
53+
#[utoipa::path(
54+
get,
55+
path = "/api/v1/keywords/{keyword}",
56+
operation_id = "get_keyword",
57+
tag = "keywords",
58+
responses((status = 200, description = "Successful Response")),
59+
)]
4660
pub async fn show(Path(name): Path<String>, state: AppState) -> AppResult<ErasedJson> {
4761
let mut conn = state.db_read().await?;
4862
let kw = Keyword::find_by_keyword(&mut conn, &name).await?;

Diff for: src/controllers/krate.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
mod delete;
1+
pub mod delete;
22
pub mod downloads;
33
pub mod follow;
44
pub mod metadata;
55
pub mod owners;
66
pub mod publish;
77
pub mod search;
88
pub mod versions;
9-
10-
pub use delete::delete;

Diff for: src/controllers/krate/delete.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,22 @@ use http::StatusCode;
1818
const DOWNLOADS_PER_MONTH_LIMIT: u64 = 100;
1919
const AVAILABLE_AFTER: TimeDelta = TimeDelta::hours(24);
2020

21-
/// Deletes a crate from the database, index and storage.
21+
/// Delete a crate.
22+
///
23+
/// The crate is immediately deleted from the database, and with a small delay
24+
/// from the git and sparse index, and the crate file storage.
2225
///
2326
/// The crate can only be deleted by the owner of the crate, and only if the
2427
/// crate has been published for less than 72 hours, or if the crate has a
2528
/// single owner, has been downloaded less than 100 times for each month it has
2629
/// been published, and is not depended upon by any other crate on crates.io.
30+
#[utoipa::path(
31+
delete,
32+
path = "/api/v1/crates/{name}",
33+
operation_id = "delete_crate",
34+
tag = "crates",
35+
responses((status = 200, description = "Successful Response")),
36+
)]
2737
pub async fn delete(
2838
Path(name): Path<String>,
2939
parts: Parts,

Diff for: src/controllers/krate/downloads.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,18 @@ use diesel::prelude::*;
1616
use diesel_async::RunQueryDsl;
1717
use std::cmp;
1818

19-
/// Handles the `GET /crates/:crate_id/downloads` route.
19+
/// Get the download counts for a crate.
20+
///
21+
/// This includes the per-day downloads for the last 90 days and for the
22+
/// latest 5 versions plus the sum of the rest.
23+
#[utoipa::path(
24+
get,
25+
path = "/api/v1/crates/{name}/downloads",
26+
operation_id = "get_crate_downloads",
27+
tag = "crates",
28+
responses((status = 200, description = "Successful Response")),
29+
)]
30+
2031
pub async fn downloads(state: AppState, Path(crate_name): Path<String>) -> AppResult<ErasedJson> {
2132
let mut conn = state.db_read().await?;
2233

Diff for: src/controllers/krate/follow.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ async fn follow_target(
2929
Ok(Follow { user_id, crate_id })
3030
}
3131

32-
/// Handles the `PUT /crates/:crate_id/follow` route.
32+
/// Follow a crate.
33+
#[utoipa::path(
34+
put,
35+
path = "/api/v1/crates/{name}/follow",
36+
operation_id = "follow_crate",
37+
tag = "crates",
38+
responses((status = 200, description = "Successful Response")),
39+
)]
3340
pub async fn follow(
3441
app: AppState,
3542
Path(crate_name): Path<String>,
@@ -47,7 +54,14 @@ pub async fn follow(
4754
ok_true()
4855
}
4956

50-
/// Handles the `DELETE /crates/:crate_id/follow` route.
57+
/// Unfollow a crate.
58+
#[utoipa::path(
59+
delete,
60+
path = "/api/v1/crates/{name}/follow",
61+
operation_id = "unfollow_crate",
62+
tag = "crates",
63+
responses((status = 200, description = "Successful Response")),
64+
)]
5165
pub async fn unfollow(
5266
app: AppState,
5367
Path(crate_name): Path<String>,
@@ -61,7 +75,14 @@ pub async fn unfollow(
6175
ok_true()
6276
}
6377

64-
/// Handles the `GET /crates/:crate_id/following` route.
78+
/// Check if a crate is followed.
79+
#[utoipa::path(
80+
get,
81+
path = "/api/v1/crates/{name}/following",
82+
operation_id = "get_following_crate",
83+
tag = "crates",
84+
responses((status = 200, description = "Successful Response")),
85+
)]
6586
pub async fn following(
6687
app: AppState,
6788
Path(crate_name): Path<String>,

Diff for: src/controllers/krate/metadata.rs

+35-4
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,29 @@ use http::request::Parts;
2626
use std::cmp::Reverse;
2727
use std::str::FromStr;
2828

29-
/// Handles the `GET /crates/new` special case.
29+
/// Get crate metadata (for the `new` crate).
30+
///
31+
/// This endpoint works around a small limitation in `axum` and is delegating
32+
/// to the `GET /api/v1/crates/{name}` endpoint internally.
33+
#[utoipa::path(
34+
get,
35+
path = "/api/v1/crates/new",
36+
operation_id = "crates_show_new",
37+
tag = "crates",
38+
responses((status = 200, description = "Successful Response")),
39+
)]
3040
pub async fn show_new(app: AppState, req: Parts) -> AppResult<ErasedJson> {
3141
show(app, Path("new".to_string()), req).await
3242
}
3343

34-
/// Handles the `GET /crates/:crate_id` route.
44+
/// Get crate metadata.
45+
#[utoipa::path(
46+
get,
47+
path = "/api/v1/crates/{name}",
48+
operation_id = "get_crate",
49+
tag = "crates",
50+
responses((status = 200, description = "Successful Response")),
51+
)]
3552
pub async fn show(app: AppState, Path(name): Path<String>, req: Parts) -> AppResult<ErasedJson> {
3653
let mut conn = app.db_read().await?;
3754

@@ -227,7 +244,14 @@ impl FromStr for ShowIncludeMode {
227244
}
228245
}
229246

230-
/// Handles the `GET /crates/:crate_id/:version/readme` route.
247+
/// Get the readme of a crate version.
248+
#[utoipa::path(
249+
get,
250+
path = "/api/v1/crates/{name}/{version}/readme",
251+
operation_id = "get_version_readme",
252+
tag = "versions",
253+
responses((status = 200, description = "Successful Response")),
254+
)]
231255
pub async fn readme(
232256
app: AppState,
233257
Path((crate_name, version)): Path<(String, String)>,
@@ -241,7 +265,14 @@ pub async fn readme(
241265
}
242266
}
243267

244-
/// Handles the `GET /crates/:crate_id/reverse_dependencies` route.
268+
/// List reverse dependencies of a crate.
269+
#[utoipa::path(
270+
get,
271+
path = "/api/v1/crates/{name}/reverse_dependencies",
272+
operation_id = "list_reverse_dependencies",
273+
tag = "crates",
274+
responses((status = 200, description = "Successful Response")),
275+
)]
245276
pub async fn reverse_dependencies(
246277
app: AppState,
247278
Path(name): Path<String>,

Diff for: src/controllers/krate/owners.rs

+40-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ use http::request::Parts;
1717
use http::StatusCode;
1818
use secrecy::{ExposeSecret, SecretString};
1919

20-
/// Handles the `GET /crates/:crate_id/owners` route.
20+
/// List crate owners.
21+
#[utoipa::path(
22+
get,
23+
path = "/api/v1/crates/{name}/owners",
24+
operation_id = "list_owners",
25+
tag = "owners",
26+
responses((status = 200, description = "Successful Response")),
27+
)]
2128
pub async fn owners(state: AppState, Path(crate_name): Path<String>) -> AppResult<ErasedJson> {
2229
let mut conn = state.db_read().await?;
2330

@@ -37,7 +44,14 @@ pub async fn owners(state: AppState, Path(crate_name): Path<String>) -> AppResul
3744
Ok(json!({ "users": owners }))
3845
}
3946

40-
/// Handles the `GET /crates/:crate_id/owner_team` route.
47+
/// List team owners of a crate.
48+
#[utoipa::path(
49+
get,
50+
path = "/api/v1/crates/{name}/owner_team",
51+
operation_id = "get_team_owners",
52+
tag = "owners",
53+
responses((status = 200, description = "Successful Response")),
54+
)]
4155
pub async fn owner_team(state: AppState, Path(crate_name): Path<String>) -> AppResult<ErasedJson> {
4256
let mut conn = state.db_read().await?;
4357
let krate: Crate = Crate::by_name(&crate_name)
@@ -55,7 +69,14 @@ pub async fn owner_team(state: AppState, Path(crate_name): Path<String>) -> AppR
5569
Ok(json!({ "teams": owners }))
5670
}
5771

58-
/// Handles the `GET /crates/:crate_id/owner_user` route.
72+
/// List user owners of a crate.
73+
#[utoipa::path(
74+
get,
75+
path = "/api/v1/crates/{name}/owner_user",
76+
operation_id = "get_user_owners",
77+
tag = "owners",
78+
responses((status = 200, description = "Successful Response")),
79+
)]
5980
pub async fn owner_user(state: AppState, Path(crate_name): Path<String>) -> AppResult<ErasedJson> {
6081
let mut conn = state.db_read().await?;
6182

@@ -74,7 +95,14 @@ pub async fn owner_user(state: AppState, Path(crate_name): Path<String>) -> AppR
7495
Ok(json!({ "users": owners }))
7596
}
7697

77-
/// Handles the `PUT /crates/:crate_id/owners` route.
98+
/// Add crate owners.
99+
#[utoipa::path(
100+
put,
101+
path = "/api/v1/crates/{name}/owners",
102+
operation_id = "add_owners",
103+
tag = "owners",
104+
responses((status = 200, description = "Successful Response")),
105+
)]
78106
pub async fn add_owners(
79107
app: AppState,
80108
Path(crate_name): Path<String>,
@@ -84,7 +112,14 @@ pub async fn add_owners(
84112
modify_owners(app, crate_name, parts, body, true).await
85113
}
86114

87-
/// Handles the `DELETE /crates/:crate_id/owners` route.
115+
/// Remove crate owners.
116+
#[utoipa::path(
117+
delete,
118+
path = "/api/v1/crates/{name}/owners",
119+
operation_id = "delete_owners",
120+
tag = "owners",
121+
responses((status = 200, description = "Successful Response")),
122+
)]
88123
pub async fn remove_owners(
89124
app: AppState,
90125
Path(crate_name): Path<String>,

0 commit comments

Comments
 (0)