diff --git a/api-backend/Cargo.lock b/api-backend/Cargo.lock index d879336ec..86e876605 100644 --- a/api-backend/Cargo.lock +++ b/api-backend/Cargo.lock @@ -175,6 +175,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9" +dependencies = [ + "axum", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "serde", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "axum-macros" version = "0.4.2" @@ -723,10 +745,6 @@ dependencies = [ "utoipa-scalar", ] -[[package]] -name = "hartex_backend_extractors" -version = "0.13.0" - [[package]] name = "hartex_backend_models" version = "0.13.0" @@ -743,6 +761,7 @@ name = "hartex_backend_routes" version = "0.13.0" dependencies = [ "axum", + "axum-extra", "bb8-postgres", "hartex_backend_models", "hartex_database_queries", diff --git a/api-backend/Cargo.toml b/api-backend/Cargo.toml index b2ed39034..6e1072e79 100644 --- a/api-backend/Cargo.toml +++ b/api-backend/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "hartex-backend-driver", - "hartex-backend-extractors", "hartex-backend-models", "hartex-backend-routes", ] diff --git a/api-backend/hartex-backend-driver/src/main.rs b/api-backend/hartex-backend-driver/src/main.rs index 1fb9d176e..327210303 100644 --- a/api-backend/hartex-backend-driver/src/main.rs +++ b/api-backend/hartex-backend-driver/src/main.rs @@ -77,16 +77,15 @@ pub async fn main() -> miette::Result<()> { let api_pgsql_url = env::var("API_PGSQL_URL").into_diagnostic()?; log::debug!("building database connection pool"); - let manager = PostgresConnectionManager::new_from_stringlike(api_pgsql_url, NoTls).into_diagnostic()?; + let manager = + PostgresConnectionManager::new_from_stringlike(api_pgsql_url, NoTls).into_diagnostic()?; let pool = Pool::builder().build(manager).await.into_diagnostic()?; log::debug!("starting axum server"); let (app, mut openapi) = OpenApiRouter::new() .layer(TraceLayer::new_for_http()) .layer(TimeoutLayer::new(Duration::from_secs(30))) - .routes(routes!( - hartex_backend_routes::uptime::get_uptime - )) + .routes(routes!(hartex_backend_routes::uptime::get_uptime)) .with_state(pool) .split_for_parts(); @@ -123,7 +122,7 @@ async fn shutdown() { .recv() .await; }; - + #[cfg(not(unix))] let terminate = future::pending::<()>(); diff --git a/api-backend/hartex-backend-extractors/Cargo.toml b/api-backend/hartex-backend-extractors/Cargo.toml deleted file mode 100644 index 19c979c5f..000000000 --- a/api-backend/hartex-backend-extractors/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "hartex_backend_extractors" -version = "0.13.0" -edition = "2021" -description = """ -Backend extractors -""" -license = "AGPL-3.0-or-later" -rust-version = "1.83.0" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[features] diff --git a/api-backend/hartex-backend-extractors/src/lib.rs b/api-backend/hartex-backend-extractors/src/lib.rs deleted file mode 100644 index 0d9dfa73a..000000000 --- a/api-backend/hartex-backend-extractors/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * - * This file is part of HarTex. - * - * HarTex - * Copyright (c) 2021-2024 HarTex Project Developers - * - * HarTex is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * HarTex is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License along - * with HarTex. If not, see . - */ - -//! # Backend Extractors -//! -//! This crate defines certain extractors for use with the Axum HTTP server. - -#![deny(clippy::pedantic)] -#![deny(unsafe_code)] -#![deny(warnings)] diff --git a/api-backend/hartex-backend-models/src/lib.rs b/api-backend/hartex-backend-models/src/lib.rs index 7f3c899df..0de02c95b 100644 --- a/api-backend/hartex-backend-models/src/lib.rs +++ b/api-backend/hartex-backend-models/src/lib.rs @@ -28,11 +28,11 @@ #![deny(unsafe_code)] #![deny(warnings)] +use axum::http::StatusCode; use axum::Json; use serde::Deserialize; use serde::Serialize; -pub use hartex_discord_configuration_models as config; pub mod uptime; /// An API response object. @@ -40,7 +40,7 @@ pub mod uptime; /// This is the object returned by a certain API endpoint. #[derive(Deserialize, Serialize)] pub struct Response { - code: u16, + pub code: u16, message: String, data: Option, } @@ -49,22 +49,43 @@ impl<'a, T> Response where T: Clone + Deserialize<'a>, { + #[allow(clippy::missing_panics_doc)] + pub fn from_code_with_data(code: StatusCode, data: T) -> (StatusCode, Json>) { + let code_display = code.to_string(); + let part = code_display.split_once(' ').unwrap().1; + + ( + code, + Json(Self { + code: code.as_u16(), + message: part.to_lowercase(), + data: Some(data), + }), + ) + } + /// Constructs a response object with a status code of 500 and its corresponding message. - pub fn internal_server_error() -> Json> { - Json(Self { - code: 500, - message: String::from("internal server error"), - data: None, - }) + pub fn internal_server_error() -> (StatusCode, Json>) { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(Self { + code: 500, + message: String::from("internal server error"), + data: None, + }), + ) } /// Constructs a response object with a status code of 200 and its corresponding message. - pub fn ok(value: T) -> Json> { - Json(Self { - code: 200, - message: String::from("ok"), - data: Some(value), - }) + pub fn ok(value: T) -> (StatusCode, Json>) { + ( + StatusCode::OK, + Json(Self { + code: 200, + message: String::from("ok"), + data: Some(value), + }), + ) } } diff --git a/api-backend/hartex-backend-models/src/uptime.rs b/api-backend/hartex-backend-models/src/uptime.rs index ec5d39ddb..48510fd03 100644 --- a/api-backend/hartex-backend-models/src/uptime.rs +++ b/api-backend/hartex-backend-models/src/uptime.rs @@ -24,6 +24,10 @@ //! //! Models for the uptime API specification V2 of the backend. +use axum::extract::rejection::QueryRejection; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::response::Response; use serde::Deserialize; use serde::Serialize; use utoipa::IntoParams; @@ -52,7 +56,27 @@ impl UptimeQuery { } } -/// A response to an uptime query. +pub struct UptimeQueryRejection { + status_code: StatusCode, + data_message: String, +} + +impl From for UptimeQueryRejection { + fn from(value: QueryRejection) -> Self { + Self { + status_code: value.status(), + data_message: value.body_text().to_lowercase(), + } + } +} + +impl IntoResponse for UptimeQueryRejection { + fn into_response(self) -> Response { + crate::Response::from_code_with_data(self.status_code, self.data_message).into_response() + } +} + +/// The uptime of the specified component. #[allow(clippy::module_name_repetitions)] #[derive(Clone, Deserialize, Serialize, ToSchema)] pub struct UptimeResponse { @@ -66,7 +90,7 @@ impl UptimeResponse { Self { start_timestamp } } - /// The start timestamp of the uptime entry. + /// The start timestamp of the uptime data. #[must_use] pub fn start_timestamp(&self) -> u128 { self.start_timestamp diff --git a/api-backend/hartex-backend-routes/Cargo.toml b/api-backend/hartex-backend-routes/Cargo.toml index 92301a57f..6c0171d21 100644 --- a/api-backend/hartex-backend-routes/Cargo.toml +++ b/api-backend/hartex-backend-routes/Cargo.toml @@ -18,6 +18,7 @@ hartex_database_queries = { path = "../../database/hartex-database-queries" } hartex_log = { path = "../../rust-utilities/hartex-log" } axum = { version = "0.7.7", features = ["json", "macros"] } +axum-extra = "0.9.4" bb8-postgres = "0.8.1" serde_json = "1.0.128" time = "0.3.36" diff --git a/api-backend/hartex-backend-routes/src/uptime.rs b/api-backend/hartex-backend-routes/src/uptime.rs index aa76b63b2..946669bea 100644 --- a/api-backend/hartex-backend-routes/src/uptime.rs +++ b/api-backend/hartex-backend-routes/src/uptime.rs @@ -23,16 +23,17 @@ /// # Uptime Routes /// /// Routes interacting with the uptime API. - use axum::extract::Query; use axum::extract::State; use axum::http::StatusCode; use axum::Json; +use axum_extra::extract::WithRejection; use bb8_postgres::bb8::Pool; use bb8_postgres::tokio_postgres::GenericClient; use bb8_postgres::tokio_postgres::NoTls; use bb8_postgres::PostgresConnectionManager; use hartex_backend_models::uptime::UptimeQuery; +use hartex_backend_models::uptime::UptimeQueryRejection; use hartex_backend_models::uptime::UptimeResponse; use hartex_backend_models::uptime::UptimeUpdate; use hartex_backend_models::Response; @@ -53,15 +54,12 @@ use time::OffsetDateTime; )] pub async fn get_uptime( State(pool): State>>, - Query(query): Query, + WithRejection(Query(query), _): WithRejection, UptimeQueryRejection>, ) -> (StatusCode, Json>) { log::trace!("retrieving connection from database pool"); let result = pool.get().await; if result.is_err() { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Response::internal_server_error(), - ); + return Response::internal_server_error(); } let connection = result.unwrap(); @@ -77,19 +75,13 @@ pub async fn get_uptime( if result.is_err() { log::error!("{:?}", result.unwrap_err()); - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Response::internal_server_error(), - ); + return Response::internal_server_error(); } let data = result.unwrap(); - ( - StatusCode::OK, - Response::ok(UptimeResponse::with_start_timestamp( - data.timestamp.unix_timestamp() as u128, - )), - ) + Response::ok(UptimeResponse::with_start_timestamp( + data.timestamp.unix_timestamp() as u128, + )) } /// # `PATCH /stats/uptime` @@ -106,10 +98,7 @@ pub async fn patch_uptime( log::trace!("retrieving connection from database pool"); let result = pool.get().await; if result.is_err() { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Response::internal_server_error(), - ); + return Response::internal_server_error(); } let connection = result.unwrap(); @@ -119,24 +108,15 @@ pub async fn patch_uptime( let Ok(timestamp) = OffsetDateTime::from_unix_timestamp(query.start_timestamp() as i64) else { // FIXME: return a better status code as the timestamp is out of range if this branch is reached // just 500 for now - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Response::internal_server_error(), - ); + return Response::internal_server_error(); }; let result = start_timestamp_upsert() .bind(client, &query.component_name(), ×tamp) .await; if result.is_err() { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Response::internal_server_error(), - ); + return Response::internal_server_error(); } - ( - StatusCode::OK, - Response::ok(()), - ) + Response::ok(()) }