From 5ebb1ad951e30058e11e72c093d39cbdc08ff208 Mon Sep 17 00:00:00 2001 From: yuriynex Date: Sun, 4 May 2025 19:12:37 -0700 Subject: [PATCH] feat: improve error handling example - Add structured error responses with JSON format - Improve error messages and logging - Add better success/error probability constants - Use proper logging levels - Add serde for JSON serialization --- basics/error-handling/Cargo.toml | 2 + basics/error-handling/src/main.rs | 75 ++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/basics/error-handling/Cargo.toml b/basics/error-handling/Cargo.toml index c9c24878..514e5e60 100644 --- a/basics/error-handling/Cargo.toml +++ b/basics/error-handling/Cargo.toml @@ -10,3 +10,5 @@ derive_more = { workspace = true, features = ["display"] } env_logger.workspace = true log.workspace = true rand.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true diff --git a/basics/error-handling/src/main.rs b/basics/error-handling/src/main.rs index 3afb7751..f925c15f 100644 --- a/basics/error-handling/src/main.rs +++ b/basics/error-handling/src/main.rs @@ -18,19 +18,28 @@ use rand::{ Rng, distr::{Distribution, StandardUniform}, }; +use serde::Serialize; -#[derive(Debug, Display)] +#[derive(Debug, Display, Serialize)] +#[serde(rename_all = "snake_case")] pub enum CustomError { - #[display("Custom Error 1")] + #[display("Access forbidden: insufficient permissions")] CustomOne, - #[display("Custom Error 2")] + #[display("Authentication required")] CustomTwo, - #[display("Custom Error 3")] + #[display("Internal server error occurred")] CustomThree, - #[display("Custom Error 4")] + #[display("Invalid request parameters")] CustomFour, } +#[derive(Serialize)] +struct ErrorResponse { + code: u16, + message: String, + error_type: String, +} + impl Distribution for StandardUniform { fn sample(&self, rng: &mut R) -> CustomError { match rng.random_range(0..4) { @@ -45,46 +54,59 @@ impl Distribution for StandardUniform { /// Actix Web uses `ResponseError` for conversion of errors to a response impl ResponseError for CustomError { fn error_response(&self) -> HttpResponse { - match self { + let (status_code, error_msg) = match self { CustomError::CustomOne => { - println!("do some stuff related to CustomOne error"); - HttpResponse::Forbidden().finish() + log::error!("Forbidden error: {}", self); + (403, self.to_string()) } - CustomError::CustomTwo => { - println!("do some stuff related to CustomTwo error"); - HttpResponse::Unauthorized().finish() + log::error!("Unauthorized error: {}", self); + (401, self.to_string()) } - CustomError::CustomThree => { - println!("do some stuff related to CustomThree error"); - HttpResponse::InternalServerError().finish() + log::error!("Internal server error: {}", self); + (500, self.to_string()) } + CustomError::CustomFour => { + log::error!("Bad request error: {}", self); + (400, self.to_string()) + } + }; - _ => { - println!("do some stuff related to CustomFour error"); - HttpResponse::BadRequest().finish() - } - } + let error_response = ErrorResponse { + code: status_code, + message: error_msg, + error_type: format!("{:?}", self), + }; + + HttpResponse::build(actix_web::http::StatusCode::from_u16(status_code).unwrap()) + .json(error_response) } } /// randomly returns either () or one of the 4 CustomError variants async fn do_something_random() -> Result<(), CustomError> { let mut rng = rand::rng(); - - // 20% chance that () will be returned by this function - if rng.random_bool(2.0 / 10.0) { + + // 20% chance of success + const SUCCESS_PROBABILITY: f64 = 0.2; + + if rng.random_bool(SUCCESS_PROBABILITY) { + log::info!("Random operation succeeded"); Ok(()) } else { - Err(rand::random::()) + let error = rand::random::(); + log::warn!("Random operation failed with error: {}", error); + Err(error) } } async fn do_something() -> Result { do_something_random().await?; - - Ok(HttpResponse::Ok().body("Nothing interesting happened. Try again.")) + Ok(HttpResponse::Ok().json(serde_json::json!({ + "status": "success", + "message": "Nothing interesting happened. Try again." + }))) } #[actix_web::main] @@ -94,7 +116,8 @@ async fn main() -> std::io::Result<()> { log::info!("starting HTTP server at http://localhost:8080"); HttpServer::new(move || { - App::new().service(web::resource("/something").route(web::get().to(do_something))) + App::new() + .service(web::resource("/something").route(web::get().to(do_something))) }) .bind(("127.0.0.1", 8080))? .run()