From 7652fdf94f9654d7619d4ee7c1b8a57940e4e000 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 26 Feb 2025 15:27:58 +0100 Subject: [PATCH 1/2] Replace `ok_true()` fn with `OkResponse` struct This will allow us to use the struct in our OpenAPI description. --- src/controllers/helpers.rs | 28 +++++++++++++++++---- src/controllers/krate/follow.rs | 11 ++++---- src/controllers/user/email_notifications.rs | 7 +++--- src/controllers/user/email_verification.rs | 14 ++++++----- src/controllers/user/update.rs | 7 +++--- src/controllers/version/yank.rs | 11 ++++---- 6 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/controllers/helpers.rs b/src/controllers/helpers.rs index dff8702d5d6..9a664656ba9 100644 --- a/src/controllers/helpers.rs +++ b/src/controllers/helpers.rs @@ -1,13 +1,31 @@ -use crate::util::errors::AppResult; +use axum::Json; use axum::response::{IntoResponse, Response}; -use axum_extra::json; pub mod authorization; pub(crate) mod pagination; pub(crate) use self::pagination::Paginate; -pub fn ok_true() -> AppResult { - let json = json!({ "ok": true }); - Ok(json.into_response()) +#[derive(Debug, Serialize, utoipa::ToSchema)] +pub struct OkResponse { + #[schema(example = true)] + ok: bool, +} + +impl Default for OkResponse { + fn default() -> Self { + Self::new() + } +} + +impl OkResponse { + pub fn new() -> Self { + Self { ok: true } + } +} + +impl IntoResponse for OkResponse { + fn into_response(self) -> Response { + Json(self).into_response() + } } diff --git a/src/controllers/krate/follow.rs b/src/controllers/krate/follow.rs index fb2cb29a7dc..390639f54a8 100644 --- a/src/controllers/krate/follow.rs +++ b/src/controllers/krate/follow.rs @@ -2,12 +2,11 @@ use crate::app::AppState; use crate::auth::AuthCheck; -use crate::controllers::helpers::ok_true; +use crate::controllers::helpers::OkResponse; use crate::controllers::krate::CratePath; use crate::models::{Crate, Follow}; use crate::schema::*; use crate::util::errors::{AppResult, crate_not_found}; -use axum::response::Response; use axum_extra::json; use axum_extra::response::ErasedJson; use diesel::prelude::*; @@ -41,7 +40,7 @@ async fn follow_target( tag = "crates", responses((status = 200, description = "Successful Response")), )] -pub async fn follow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult { +pub async fn follow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult { let mut conn = app.db_write().await?; let user_id = AuthCheck::default().check(&req, &mut conn).await?.user_id(); let follow = follow_target(&path.name, &mut conn, user_id).await?; @@ -51,7 +50,7 @@ pub async fn follow_crate(app: AppState, path: CratePath, req: Parts) -> AppResu .execute(&mut conn) .await?; - ok_true() + Ok(OkResponse::new()) } /// Unfollow a crate. @@ -66,13 +65,13 @@ pub async fn follow_crate(app: AppState, path: CratePath, req: Parts) -> AppResu tag = "crates", responses((status = 200, description = "Successful Response")), )] -pub async fn unfollow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult { +pub async fn unfollow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult { let mut conn = app.db_write().await?; let user_id = AuthCheck::default().check(&req, &mut conn).await?.user_id(); let follow = follow_target(&path.name, &mut conn, user_id).await?; diesel::delete(&follow).execute(&mut conn).await?; - ok_true() + Ok(OkResponse::new()) } /// Check if a crate is followed. diff --git a/src/controllers/user/email_notifications.rs b/src/controllers/user/email_notifications.rs index c069d1cfd18..d9da2c2fccd 100644 --- a/src/controllers/user/email_notifications.rs +++ b/src/controllers/user/email_notifications.rs @@ -1,11 +1,10 @@ use crate::app::AppState; use crate::auth::AuthCheck; -use crate::controllers::helpers::ok_true; +use crate::controllers::helpers::OkResponse; use crate::models::{CrateOwner, OwnerKind}; use crate::schema::crate_owners; use crate::util::errors::AppResult; use axum::Json; -use axum::response::Response; use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::request::Parts; @@ -36,7 +35,7 @@ pub async fn update_email_notifications( app: AppState, parts: Parts, Json(updates): Json>, -) -> AppResult { +) -> AppResult { use diesel::pg::upsert::excluded; let updates: HashMap = updates @@ -89,5 +88,5 @@ pub async fn update_email_notifications( .execute(&mut conn) .await?; - ok_true() + Ok(OkResponse::new()) } diff --git a/src/controllers/user/email_verification.rs b/src/controllers/user/email_verification.rs index 4c035815f01..fd41e3c4d17 100644 --- a/src/controllers/user/email_verification.rs +++ b/src/controllers/user/email_verification.rs @@ -1,12 +1,11 @@ use super::update::UserConfirmEmail; use crate::app::AppState; use crate::auth::AuthCheck; -use crate::controllers::helpers::ok_true; +use crate::controllers::helpers::OkResponse; use crate::models::Email; use crate::util::errors::AppResult; use crate::util::errors::{BoxedAppError, bad_request}; use axum::extract::Path; -use axum::response::Response; use crates_io_database::schema::emails; use diesel::dsl::sql; use diesel::prelude::*; @@ -24,7 +23,10 @@ use http::request::Parts; tag = "users", responses((status = 200, description = "Successful Response")), )] -pub async fn confirm_user_email(state: AppState, Path(token): Path) -> AppResult { +pub async fn confirm_user_email( + state: AppState, + Path(token): Path, +) -> AppResult { let mut conn = state.db_write().await?; let updated_rows = diesel::update(emails::table.filter(emails::token.eq(&token))) @@ -36,7 +38,7 @@ pub async fn confirm_user_email(state: AppState, Path(token): Path) -> A return Err(bad_request("Email belonging to token not found.")); } - ok_true() + Ok(OkResponse::new()) } /// Regenerate and send an email verification token. @@ -57,7 +59,7 @@ pub async fn resend_email_verification( state: AppState, Path(param_user_id): Path, req: Parts, -) -> AppResult { +) -> AppResult { let mut conn = state.db_write().await?; let auth = AuthCheck::default().check(&req, &mut conn).await?; @@ -91,7 +93,7 @@ pub async fn resend_email_verification( }) .await?; - ok_true() + Ok(OkResponse::new()) } #[cfg(test)] diff --git a/src/controllers/user/update.rs b/src/controllers/user/update.rs index 5174692acca..64f5123fff0 100644 --- a/src/controllers/user/update.rs +++ b/src/controllers/user/update.rs @@ -1,12 +1,11 @@ use crate::app::AppState; use crate::auth::AuthCheck; -use crate::controllers::helpers::ok_true; +use crate::controllers::helpers::OkResponse; use crate::models::NewEmail; use crate::schema::users; use crate::util::errors::{AppResult, bad_request, server_error}; use axum::Json; use axum::extract::Path; -use axum::response::Response; use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::request::Parts; @@ -47,7 +46,7 @@ pub async fn update_user( Path(param_user_id): Path, req: Parts, Json(user_update): Json, -) -> AppResult { +) -> AppResult { let mut conn = state.db_write().await?; let auth = AuthCheck::default().check(&req, &mut conn).await?; @@ -116,7 +115,7 @@ pub async fn update_user( let _ = state.emails.send(user_email, email).await; } - ok_true() + Ok(OkResponse::new()) } pub struct UserConfirmEmail<'a> { diff --git a/src/controllers/version/yank.rs b/src/controllers/version/yank.rs index 7f7b3d367fc..bc020eb48f5 100644 --- a/src/controllers/version/yank.rs +++ b/src/controllers/version/yank.rs @@ -3,10 +3,9 @@ use super::CrateVersionPath; use super::update::{authenticate, perform_version_yank_update}; use crate::app::AppState; -use crate::controllers::helpers::ok_true; +use crate::controllers::helpers::OkResponse; use crate::rate_limiter::LimitedAction; use crate::util::errors::AppResult; -use axum::response::Response; use http::request::Parts; /// Yank a crate version. @@ -35,7 +34,7 @@ pub async fn yank_version( app: AppState, path: CrateVersionPath, req: Parts, -) -> AppResult { +) -> AppResult { modify_yank(path, app, req, true).await } @@ -55,7 +54,7 @@ pub async fn unyank_version( app: AppState, path: CrateVersionPath, req: Parts, -) -> AppResult { +) -> AppResult { modify_yank(path, app, req, false).await } @@ -65,7 +64,7 @@ async fn modify_yank( state: AppState, req: Parts, yanked: bool, -) -> AppResult { +) -> AppResult { // FIXME: Should reject bad requests before authentication, but can't due to // lifetime issues with `req`. @@ -89,5 +88,5 @@ async fn modify_yank( ) .await?; - ok_true() + Ok(OkResponse::new()) } From f98a88659a7a7e2ef9b1b65d0c7d848876facf19 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Wed, 26 Feb 2025 15:34:16 +0100 Subject: [PATCH 2/2] Add OpenAPI documentation for `OkResponse` responses --- src/controllers/krate/follow.rs | 4 +- src/controllers/user/email_notifications.rs | 2 +- src/controllers/user/email_verification.rs | 4 +- src/controllers/user/update.rs | 2 +- src/controllers/version/yank.rs | 4 +- ..._io__openapi__tests__openapi_snapshot.snap | 128 ++++++++++++++++++ 6 files changed, 136 insertions(+), 8 deletions(-) diff --git a/src/controllers/krate/follow.rs b/src/controllers/krate/follow.rs index 390639f54a8..7475dc1f4b3 100644 --- a/src/controllers/krate/follow.rs +++ b/src/controllers/krate/follow.rs @@ -38,7 +38,7 @@ async fn follow_target( ("cookie" = []), ), tag = "crates", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(OkResponse))), )] pub async fn follow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult { let mut conn = app.db_write().await?; @@ -63,7 +63,7 @@ pub async fn follow_crate(app: AppState, path: CratePath, req: Parts) -> AppResu ("cookie" = []), ), tag = "crates", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(OkResponse))), )] pub async fn unfollow_crate(app: AppState, path: CratePath, req: Parts) -> AppResult { let mut conn = app.db_write().await?; diff --git a/src/controllers/user/email_notifications.rs b/src/controllers/user/email_notifications.rs index d9da2c2fccd..baf6e760c6e 100644 --- a/src/controllers/user/email_notifications.rs +++ b/src/controllers/user/email_notifications.rs @@ -28,7 +28,7 @@ pub struct CrateEmailNotifications { ("cookie" = []), ), tag = "users", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(OkResponse))), )] #[deprecated] pub async fn update_email_notifications( diff --git a/src/controllers/user/email_verification.rs b/src/controllers/user/email_verification.rs index fd41e3c4d17..55cc7248c8e 100644 --- a/src/controllers/user/email_verification.rs +++ b/src/controllers/user/email_verification.rs @@ -21,7 +21,7 @@ use http::request::Parts; ("email_token" = String, Path, description = "Secret verification token sent to the user's email address"), ), tag = "users", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(OkResponse))), )] pub async fn confirm_user_email( state: AppState, @@ -53,7 +53,7 @@ pub async fn confirm_user_email( ("cookie" = []), ), tag = "users", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(OkResponse))), )] pub async fn resend_email_verification( state: AppState, diff --git a/src/controllers/user/update.rs b/src/controllers/user/update.rs index 64f5123fff0..eb956c6b556 100644 --- a/src/controllers/user/update.rs +++ b/src/controllers/user/update.rs @@ -39,7 +39,7 @@ pub struct User { ("cookie" = []), ), tag = "users", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(OkResponse))), )] pub async fn update_user( state: AppState, diff --git a/src/controllers/version/yank.rs b/src/controllers/version/yank.rs index bc020eb48f5..c18531606ee 100644 --- a/src/controllers/version/yank.rs +++ b/src/controllers/version/yank.rs @@ -28,7 +28,7 @@ use http::request::Parts; ("cookie" = []), ), tag = "versions", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(OkResponse))), )] pub async fn yank_version( app: AppState, @@ -48,7 +48,7 @@ pub async fn yank_version( ("cookie" = []), ), tag = "versions", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(OkResponse))), )] pub async fn unyank_version( app: AppState, diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 752c3e1c6fa..08a2e4adf30 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -646,6 +646,22 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ok": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "ok" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -982,6 +998,22 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ok": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "ok" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -1013,6 +1045,22 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ok": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "ok" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -1603,6 +1651,22 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ok": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "ok" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -1647,6 +1711,22 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ok": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "ok" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -1925,6 +2005,22 @@ expression: response.json() "operationId": "update_email_notifications", "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ok": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "ok" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -2156,6 +2252,22 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ok": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "ok" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -2275,6 +2387,22 @@ expression: response.json() ], "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ok": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "ok" + ], + "type": "object" + } + } + }, "description": "Successful Response" } },