1
0
mirror of https://github.com/actix/examples synced 2025-05-12 00:12:57 +02:00

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

This commit is contained in:
yuriynex 2025-05-04 19:12:37 -07:00
parent 00aedf05ea
commit 5ebb1ad951
2 changed files with 51 additions and 26 deletions

View File

@ -10,3 +10,5 @@ derive_more = { workspace = true, features = ["display"] }
env_logger.workspace = true env_logger.workspace = true
log.workspace = true log.workspace = true
rand.workspace = true rand.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true

View File

@ -18,19 +18,28 @@ use rand::{
Rng, Rng,
distr::{Distribution, StandardUniform}, distr::{Distribution, StandardUniform},
}; };
use serde::Serialize;
#[derive(Debug, Display)] #[derive(Debug, Display, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CustomError { pub enum CustomError {
#[display("Custom Error 1")] #[display("Access forbidden: insufficient permissions")]
CustomOne, CustomOne,
#[display("Custom Error 2")] #[display("Authentication required")]
CustomTwo, CustomTwo,
#[display("Custom Error 3")] #[display("Internal server error occurred")]
CustomThree, CustomThree,
#[display("Custom Error 4")] #[display("Invalid request parameters")]
CustomFour, CustomFour,
} }
#[derive(Serialize)]
struct ErrorResponse {
code: u16,
message: String,
error_type: String,
}
impl Distribution<CustomError> for StandardUniform { impl Distribution<CustomError> for StandardUniform {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> CustomError { fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> CustomError {
match rng.random_range(0..4) { match rng.random_range(0..4) {
@ -45,46 +54,59 @@ impl Distribution<CustomError> for StandardUniform {
/// Actix Web uses `ResponseError` for conversion of errors to a response /// Actix Web uses `ResponseError` for conversion of errors to a response
impl ResponseError for CustomError { impl ResponseError for CustomError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match self { let (status_code, error_msg) = match self {
CustomError::CustomOne => { CustomError::CustomOne => {
println!("do some stuff related to CustomOne error"); log::error!("Forbidden error: {}", self);
HttpResponse::Forbidden().finish() (403, self.to_string())
} }
CustomError::CustomTwo => { CustomError::CustomTwo => {
println!("do some stuff related to CustomTwo error"); log::error!("Unauthorized error: {}", self);
HttpResponse::Unauthorized().finish() (401, self.to_string())
} }
CustomError::CustomThree => { CustomError::CustomThree => {
println!("do some stuff related to CustomThree error"); log::error!("Internal server error: {}", self);
HttpResponse::InternalServerError().finish() (500, self.to_string())
} }
CustomError::CustomFour => {
log::error!("Bad request error: {}", self);
(400, self.to_string())
}
};
_ => { let error_response = ErrorResponse {
println!("do some stuff related to CustomFour error"); code: status_code,
HttpResponse::BadRequest().finish() 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 /// randomly returns either () or one of the 4 CustomError variants
async fn do_something_random() -> Result<(), CustomError> { async fn do_something_random() -> Result<(), CustomError> {
let mut rng = rand::rng(); let mut rng = rand::rng();
// 20% chance that () will be returned by this function // 20% chance of success
if rng.random_bool(2.0 / 10.0) { const SUCCESS_PROBABILITY: f64 = 0.2;
if rng.random_bool(SUCCESS_PROBABILITY) {
log::info!("Random operation succeeded");
Ok(()) Ok(())
} else { } else {
Err(rand::random::<CustomError>()) let error = rand::random::<CustomError>();
log::warn!("Random operation failed with error: {}", error);
Err(error)
} }
} }
async fn do_something() -> Result<HttpResponse, Error> { async fn do_something() -> Result<HttpResponse, Error> {
do_something_random().await?; do_something_random().await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
Ok(HttpResponse::Ok().body("Nothing interesting happened. Try again.")) "status": "success",
"message": "Nothing interesting happened. Try again."
})))
} }
#[actix_web::main] #[actix_web::main]
@ -94,7 +116,8 @@ async fn main() -> std::io::Result<()> {
log::info!("starting HTTP server at http://localhost:8080"); log::info!("starting HTTP server at http://localhost:8080");
HttpServer::new(move || { 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))? .bind(("127.0.0.1", 8080))?
.run() .run()