Skip to content

Commit

Permalink
Merge pull request #10201 from Turbo87/utoipa-router
Browse files Browse the repository at this point in the history
utoipa: Add annotations to (almost) all endpoints
  • Loading branch information
Turbo87 authored Dec 15, 2024
2 parents 18a9bb1 + dd69739 commit a093332
Show file tree
Hide file tree
Showing 27 changed files with 1,243 additions and 209 deletions.
27 changes: 24 additions & 3 deletions src/controllers/category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ use diesel::QueryDsl;
use diesel_async::RunQueryDsl;
use http::request::Parts;

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

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

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

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

Expand Down
36 changes: 32 additions & 4 deletions src/controllers/crate_owner_invitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ use http::request::Parts;
use indexmap::IndexMap;
use std::collections::{HashMap, HashSet};

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

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

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

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

/// Handles the `PUT /api/v1/me/crate_owner_invitations/accept/:token` route.
/// Accept a crate owner invitation with a token.
#[utoipa::path(
put,
path = "/api/v1/me/crate_owner_invitations/accept/{token}",
operation_id = "accept_crate_owner_invitation_with_token",
tag = "owners",
responses((status = 200, description = "Successful Response")),
)]
pub async fn handle_invite_with_token(
state: AppState,
Path(token): Path<String>,
Expand Down
18 changes: 16 additions & 2 deletions src/controllers/keyword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ pub struct IndexQuery {
sort: Option<String>,
}

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

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

/// Handles the `GET /keywords/:keyword_id` route.
/// Get keyword metadata.
#[utoipa::path(
get,
path = "/api/v1/keywords/{keyword}",
operation_id = "get_keyword",
tag = "keywords",
responses((status = 200, description = "Successful Response")),
)]
pub async fn show(Path(name): Path<String>, state: AppState) -> AppResult<ErasedJson> {
let mut conn = state.db_read().await?;
let kw = Keyword::find_by_keyword(&mut conn, &name).await?;
Expand Down
4 changes: 1 addition & 3 deletions src/controllers/krate.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
mod delete;
pub mod delete;
pub mod downloads;
pub mod follow;
pub mod metadata;
pub mod owners;
pub mod publish;
pub mod search;
pub mod versions;

pub use delete::delete;
12 changes: 11 additions & 1 deletion src/controllers/krate/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,22 @@ use http::StatusCode;
const DOWNLOADS_PER_MONTH_LIMIT: u64 = 100;
const AVAILABLE_AFTER: TimeDelta = TimeDelta::hours(24);

/// Deletes a crate from the database, index and storage.
/// Delete a crate.
///
/// The crate is immediately deleted from the database, and with a small delay
/// from the git and sparse index, and the crate file storage.
///
/// The crate can only be deleted by the owner of the crate, and only if the
/// crate has been published for less than 72 hours, or if the crate has a
/// single owner, has been downloaded less than 100 times for each month it has
/// been published, and is not depended upon by any other crate on crates.io.
#[utoipa::path(
delete,
path = "/api/v1/crates/{name}",
operation_id = "delete_crate",
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn delete(
Path(name): Path<String>,
parts: Parts,
Expand Down
13 changes: 12 additions & 1 deletion src/controllers/krate/downloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@ use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use std::cmp;

/// Handles the `GET /crates/:crate_id/downloads` route.
/// Get the download counts for a crate.
///
/// This includes the per-day downloads for the last 90 days and for the
/// latest 5 versions plus the sum of the rest.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/downloads",
operation_id = "get_crate_downloads",
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]

pub async fn downloads(state: AppState, Path(crate_name): Path<String>) -> AppResult<ErasedJson> {
let mut conn = state.db_read().await?;

Expand Down
27 changes: 24 additions & 3 deletions src/controllers/krate/follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ async fn follow_target(
Ok(Follow { user_id, crate_id })
}

/// Handles the `PUT /crates/:crate_id/follow` route.
/// Follow a crate.
#[utoipa::path(
put,
path = "/api/v1/crates/{name}/follow",
operation_id = "follow_crate",
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn follow(
app: AppState,
Path(crate_name): Path<String>,
Expand All @@ -47,7 +54,14 @@ pub async fn follow(
ok_true()
}

/// Handles the `DELETE /crates/:crate_id/follow` route.
/// Unfollow a crate.
#[utoipa::path(
delete,
path = "/api/v1/crates/{name}/follow",
operation_id = "unfollow_crate",
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn unfollow(
app: AppState,
Path(crate_name): Path<String>,
Expand All @@ -61,7 +75,14 @@ pub async fn unfollow(
ok_true()
}

/// Handles the `GET /crates/:crate_id/following` route.
/// Check if a crate is followed.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/following",
operation_id = "get_following_crate",
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn following(
app: AppState,
Path(crate_name): Path<String>,
Expand Down
39 changes: 35 additions & 4 deletions src/controllers/krate/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,29 @@ use http::request::Parts;
use std::cmp::Reverse;
use std::str::FromStr;

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

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

Expand Down Expand Up @@ -227,7 +244,14 @@ impl FromStr for ShowIncludeMode {
}
}

/// Handles the `GET /crates/:crate_id/:version/readme` route.
/// Get the readme of a crate version.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/{version}/readme",
operation_id = "get_version_readme",
tag = "versions",
responses((status = 200, description = "Successful Response")),
)]
pub async fn readme(
app: AppState,
Path((crate_name, version)): Path<(String, String)>,
Expand All @@ -241,7 +265,14 @@ pub async fn readme(
}
}

/// Handles the `GET /crates/:crate_id/reverse_dependencies` route.
/// List reverse dependencies of a crate.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/reverse_dependencies",
operation_id = "list_reverse_dependencies",
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn reverse_dependencies(
app: AppState,
Path(name): Path<String>,
Expand Down
45 changes: 40 additions & 5 deletions src/controllers/krate/owners.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ use http::request::Parts;
use http::StatusCode;
use secrecy::{ExposeSecret, SecretString};

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

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

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

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

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

/// Handles the `PUT /crates/:crate_id/owners` route.
/// Add crate owners.
#[utoipa::path(
put,
path = "/api/v1/crates/{name}/owners",
operation_id = "add_owners",
tag = "owners",
responses((status = 200, description = "Successful Response")),
)]
pub async fn add_owners(
app: AppState,
Path(crate_name): Path<String>,
Expand All @@ -84,7 +112,14 @@ pub async fn add_owners(
modify_owners(app, crate_name, parts, body, true).await
}

/// Handles the `DELETE /crates/:crate_id/owners` route.
/// Remove crate owners.
#[utoipa::path(
delete,
path = "/api/v1/crates/{name}/owners",
operation_id = "delete_owners",
tag = "owners",
responses((status = 200, description = "Successful Response")),
)]
pub async fn remove_owners(
app: AppState,
Path(crate_name): Path<String>,
Expand Down
Loading

0 comments on commit a093332

Please sign in to comment.