diff --git a/core/src/http/server/collect_to_json.rs b/core/src/http/server/collect_to_json.rs index f1569996..24fdc35e 100644 --- a/core/src/http/server/collect_to_json.rs +++ b/core/src/http/server/collect_to_json.rs @@ -1,8 +1,7 @@ -use hyper::body::Incoming; -use hyper::StatusCode; -use http_body_util::BodyExt; -use serde::de::DeserializeOwned; use crate::http::server::error::AppError; +use http_body_util::BodyExt; +use hyper::body::Incoming; +use serde::de::DeserializeOwned; pub(crate) trait CollectToJson { async fn collect_to_json(self) -> Result; @@ -13,7 +12,7 @@ impl CollectToJson for Incoming { let bytes = self.collect().await?.to_bytes(); serde_json::from_slice::(&bytes).map_err(|err| { tracing::warn!("Failed to parse JSON body: {err:#}"); - AppError::status(StatusCode::BAD_REQUEST, Some("Invalid JSON body".to_string())) + AppError::BadRequest("Invalid JSON body".to_string()) }) } } diff --git a/core/src/http/server/controller/v2.rs b/core/src/http/server/controller/v2.rs index 92c197da..fa1a76ef 100644 --- a/core/src/http/server/controller/v2.rs +++ b/core/src/http/server/controller/v2.rs @@ -1,9 +1,10 @@ -use hyper::body::Incoming; -use hyper::StatusCode; use crate::http::dto::{RegisterDto, RegisterResponseDto}; -use crate::http::server::{AppState, JsonResponse, RequestClientInfo}; use crate::http::server::collect_to_json::CollectToJson; use crate::http::server::error::AppError; +use crate::http::server::response::JsonResponse; +use crate::http::server::{AppState, RequestClientInfo}; +use hyper::body::Incoming; +use hyper::StatusCode; pub(crate) async fn register( body: Incoming, diff --git a/core/src/http/server/controller/v3.rs b/core/src/http/server/controller/v3.rs index 3be98f03..a9e24dd6 100644 --- a/core/src/http/server/controller/v3.rs +++ b/core/src/http/server/controller/v3.rs @@ -1,10 +1,11 @@ +use crate::http::dto::{NonceRequest, NonceResponse, RegisterDto, RegisterResponseDto}; +use crate::http::server::collect_to_json::CollectToJson; +use crate::http::server::error::AppError; +use crate::http::server::response::JsonResponse; +use crate::http::server::{AppState, RequestClientInfo}; +use crate::{crypto, util}; use hyper::body::Incoming; use hyper::StatusCode; -use crate::http::dto::{NonceRequest, NonceResponse, RegisterDto, RegisterResponseDto}; -use crate::http::server::{AppState, RequestClientInfo, JsonResponse}; -use crate::http::server::error::AppError; -use crate::{crypto, util}; -use crate::http::server::collect_to_json::CollectToJson; pub(crate) async fn nonce_exchange( body: Incoming, @@ -15,18 +16,12 @@ pub(crate) async fn nonce_exchange( let nonce = util::base64::decode(&payload.nonce).map_err(|_| { tracing::warn!("Failed to decode nonce from base64"); - AppError::status( - StatusCode::BAD_REQUEST, - Some("Invalid nonce format".to_string()), - ) + AppError::BadRequest("Invalid nonce format".to_string()) })?; if !crypto::nonce::validate_nonce(&nonce) { tracing::warn!("Invalid nonce received"); - return Err(AppError::status( - StatusCode::BAD_REQUEST, - Some("Invalid nonce".to_string()), - )); + return Err(AppError::BadRequest("Invalid nonce".to_string())); } // Save the nonce diff --git a/core/src/http/server/error.rs b/core/src/http/server/error.rs index fb2e20d6..c18e5518 100644 --- a/core/src/http/server/error.rs +++ b/core/src/http/server/error.rs @@ -1,31 +1,42 @@ -use hyper::StatusCode; +use crate::http::dto::ErrorResponse; +use crate::http::server::response::JsonResponse; +use bytes::Bytes; +use http_body_util::Full; +use hyper::{Response, StatusCode}; -#[derive(Debug)] -pub struct AppError { - pub status: StatusCode, - pub message: Option, - pub error: Option, +#[derive(Debug, thiserror::Error)] +pub enum AppError { + #[error("Hyper error: {0}")] + Hyper(#[from] hyper::Error), + + #[error("Status Error: {0}")] + Status(StatusCode), + + #[error("Invalid request: {0}")] + BadRequest(String), } impl AppError { - pub fn status(status: StatusCode, message: Option) -> Self { - Self { - status, - message, - error: None, - } - } -} + pub(crate) fn to_response(self) -> Response> { + let json = match self { + AppError::Hyper(_) => JsonResponse { + status: StatusCode::INTERNAL_SERVER_ERROR, + body: ErrorResponse { + message: "Internal server error".to_string(), + }, + }, + AppError::Status(code) => JsonResponse { + status: code, + body: ErrorResponse { + message: format!("Status code: {code}"), + }, + }, + AppError::BadRequest(message) => JsonResponse { + status: StatusCode::BAD_REQUEST, + body: ErrorResponse { message }, + }, + }; -impl From for AppError -where - E: Into, -{ - fn from(err: E) -> Self { - Self { - status: StatusCode::INTERNAL_SERVER_ERROR, - message: None, - error: Some(err.into()), - } + json.into_response() } } diff --git a/core/src/http/server/mod.rs b/core/src/http/server/mod.rs index bc55ee56..b2ef00d4 100644 --- a/core/src/http/server/mod.rs +++ b/core/src/http/server/mod.rs @@ -2,9 +2,9 @@ mod client_cert_verifier; mod collect_to_json; mod controller; mod error; +mod response; use crate::crypto::cert::public_key_from_cert_der; -use crate::http::dto::ErrorResponse; use crate::http::server::client_cert_verifier::CustomClientCertVerifier; use crate::http::server::controller::web::WebPageState; use crate::http::server::error::AppError; @@ -12,13 +12,12 @@ use crate::http::state::ClientInfo; use bytes::Bytes; use http_body_util::Full; use hyper::body::Incoming; -use hyper::{http, Method, Request, Response, StatusCode}; +use hyper::{Method, Request, Response, StatusCode}; use hyper_util::rt::{TokioExecutor, TokioIo}; use hyper_util::server::conn::auto::Builder; use lru::LruCache; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use serde::Serialize; use std::fmt::Debug; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::num::NonZeroUsize; @@ -238,57 +237,26 @@ async fn handle_request( .await .unwrap_or_else(|err| { tracing::error!("Error handling request: {err:?}"); - JsonResponse { - status: err.status, - body: ErrorResponse { - message: err - .message - .unwrap_or_else(|| "Internal Server Error".to_string()), - }, - } - .into_response() + err.to_response() })) } -struct JsonResponse { - status: StatusCode, - body: T, -} - -impl JsonResponse { - fn into_response(self) -> Response> { - let mut response = Response::new(Full::default()); - *response.status_mut() = self.status; - - response.headers_mut().insert( - http::header::CONTENT_TYPE, - http::HeaderValue::from_static("application/json"), - ); - - *response.body_mut() = Full::from(Bytes::from( - serde_json::to_string(&self.body).unwrap_or_else(|_| "{}".to_string()), - )); - - response - } -} - async fn handle_request_inner( mut req: Request, legacy_enabled: bool, ) -> Result>, AppError> { let Some(state) = req.extensions_mut().remove::() else { - return Err(AppError::status(StatusCode::INTERNAL_SERVER_ERROR, None)); + return Err(AppError::Status(StatusCode::INTERNAL_SERVER_ERROR)); }; let Some(client_info) = req.extensions_mut().remove::() else { - return Err(AppError::status(StatusCode::INTERNAL_SERVER_ERROR, None)); + return Err(AppError::Status(StatusCode::INTERNAL_SERVER_ERROR)); }; match (req.method(), req.uri().path()) { (&Method::POST, "/api/localsend/v2/register") => { if !legacy_enabled { - return Err(AppError::status(StatusCode::NOT_FOUND, None)); + return Err(AppError::Status(StatusCode::NOT_FOUND)); } Ok( @@ -299,7 +267,7 @@ async fn handle_request_inner( } (&Method::POST, "/api/localsend/v2/prepare-upload") => { if !legacy_enabled { - return Err(AppError::status(StatusCode::NOT_FOUND, None)); + return Err(AppError::Status(StatusCode::NOT_FOUND)); } Ok( @@ -310,7 +278,7 @@ async fn handle_request_inner( } (&Method::POST, "/api/localsend/v2/upload") => { if !legacy_enabled { - return Err(AppError::status(StatusCode::NOT_FOUND, None)); + return Err(AppError::Status(StatusCode::NOT_FOUND)); } Ok( @@ -321,7 +289,7 @@ async fn handle_request_inner( } (&Method::POST, "/api/localsend/v2/cancel") => { if !legacy_enabled { - return Err(AppError::status(StatusCode::NOT_FOUND, None)); + return Err(AppError::Status(StatusCode::NOT_FOUND)); } Ok( diff --git a/core/src/http/server/response.rs b/core/src/http/server/response.rs new file mode 100644 index 00000000..0f8a968d --- /dev/null +++ b/core/src/http/server/response.rs @@ -0,0 +1,27 @@ +use bytes::Bytes; +use http_body_util::Full; +use hyper::{http, Response, StatusCode}; +use serde::Serialize; + +pub(crate) struct JsonResponse { + pub(crate) status: StatusCode, + pub(crate) body: T, +} + +impl JsonResponse { + pub(crate) fn into_response(self) -> Response> { + let mut response = Response::new(Full::default()); + *response.status_mut() = self.status; + + response.headers_mut().insert( + http::header::CONTENT_TYPE, + http::HeaderValue::from_static("application/json"), + ); + + *response.body_mut() = Full::from(Bytes::from( + serde_json::to_string(&self.body).unwrap_or_else(|_| "{}".to_string()), + )); + + response + } +}