refactor: use thiserror

This commit is contained in:
Tien Do Nam
2026-01-12 18:11:53 +01:00
parent 8fa3ade352
commit ef459592a1
6 changed files with 87 additions and 86 deletions
+4 -5
View File
@@ -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<T: DeserializeOwned>(self) -> Result<T, AppError>;
@@ -13,7 +12,7 @@ impl CollectToJson for Incoming {
let bytes = self.collect().await?.to_bytes();
serde_json::from_slice::<T>(&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())
})
}
}
+4 -3
View File
@@ -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,
+8 -13
View File
@@ -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
+35 -24
View File
@@ -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<String>,
pub error: Option<anyhow::Error>,
#[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<String>) -> Self {
Self {
status,
message,
error: None,
}
}
}
pub(crate) fn to_response(self) -> Response<Full<Bytes>> {
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<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self {
status: StatusCode::INTERNAL_SERVER_ERROR,
message: None,
error: Some(err.into()),
}
json.into_response()
}
}
+9 -41
View File
@@ -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<T: Serialize> {
status: StatusCode,
body: T,
}
impl<T: Serialize> JsonResponse<T> {
fn into_response(self) -> Response<Full<Bytes>> {
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<Incoming>,
legacy_enabled: bool,
) -> Result<Response<Full<Bytes>>, AppError> {
let Some(state) = req.extensions_mut().remove::<AppState>() 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::<RequestClientInfo>() 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(
+27
View File
@@ -0,0 +1,27 @@
use bytes::Bytes;
use http_body_util::Full;
use hyper::{http, Response, StatusCode};
use serde::Serialize;
pub(crate) struct JsonResponse<T: Serialize> {
pub(crate) status: StatusCode,
pub(crate) body: T,
}
impl<T: Serialize> JsonResponse<T> {
pub(crate) fn into_response(self) -> Response<Full<Bytes>> {
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
}
}