How to send a custom error message in rust-warp?

I use warp to handle a couple of routes (POST /topup and POST /print):

// main.rs
let print_route = warp::path("print")
    .and(warp::path::param::<String>())
    .and(warp::post())
    .and_then(handle_upload);

let topup_route = warp::path("topup")
    .and(warp::post())
    .and(warp::body::json())
    .and_then(handle_topup);

let routes = root
    .or(print_route)
    .or(topup_route)
    .recover(handle_rejection)
    .with(warp::cors().allow_any_origin());

println!("Client is up and running at localhost:{}", PORT);
warp::serve(routes).run(([0, 0, 0, 0], PORT)).await;

on both handle_upload and handle_topup functions, I use a custom error type AppErr

use warp::{reject::Reject};

#[derive(Debug)]
pub struct AppErr {
    pub reason: String,
}

impl Reject for AppErr {}

If I need to throw an error on any part of the handle_topup or handle_upload, I just call the warp::reject::custom() function like this:

let reason = format!("Error writing file to destination: {}", &path);
warp::reject::custom(AppErr { reason })

Every reason that I give should be forwarded to client, so I just make a handle_rejection to convert any AppErr from both routes into a response type that will be sent by warp

// Custom rejection handler that maps rejections into responses.
async fn handle_rejection(err: Rejection) -> Result<impl Reply, std::convert::Infallible> {
    if err.is_not_found() {
        Ok(reply::with_status("NOT_FOUND", StatusCode::NOT_FOUND))
    } else if let Some(e) = err.find::<AppErr>() {
        Ok(reply::with_status(e.reason.as_str(), StatusCode::BAD_REQUEST))
    } else {
        eprintln!("unhandled rejection: {:?}", err);
        Ok(reply::with_status(
            "INTERNAL_SERVER_ERROR",
            StatusCode::INTERNAL_SERVER_ERROR,
        ))
    }
}

It should be easy enough, but I have a problem with this line

error[E0597]: `err` does not live long enough
   --> src\main.rs:225:29
    |
225 |     } else if let Some(e) = err.find::<AppErr>() {
    |                             ^^^^^^^^^^^^^^^^^^^^
    |                             |
    |                             borrowed value does not live long enough
    |                             argument requires that `err` is borrowed for `'static`
...
234 | }
    | - `err` dropped here while still borrowed

If it try to clone the e.reason it throws temporary value dropped while borrowed error.
My question is why I can't make anything from e available to outside scope of if let Some(e) = err.find::<AppErr>()

Because the scope of e is limited to the if branch.

why can't I clone, copy or do anything that will make e.reason escape the if scope ?
I tried to put e.reason into a new variable.

else if let Some(e) = err.find::<AppErr>() {
     let msg = e.reason.as_str().to_string();
     Ok(reply::with_status(msg, StatusCode::BAD_REQUEST))
}

which resulted in an error:

error[E0308]: mismatched types
   --> src\main.rs:227:31
    |
227 |         Ok(reply::with_status(msg, StatusCode::BAD_REQUEST))
    |                               ^^^
    |                               |
    |                               expected `&str`, found struct `std::string::String`
    |                               help: consider borrowing here: `&msg`

also this doesn't work

else if let Some(e) = err.find::<AppErr>() {
      let msg = e.reason.as_str().to_string();
      Ok(reply::with_status(msg.as_str(), StatusCode::BAD_REQUEST))
}
error[E0597]: `msg` does not live long enough
   --> src\main.rs:227:31
    |
227 |         Ok(reply::with_status(msg.as_str(), StatusCode::BAD_REQUEST))
    |                               ^^^^^^^^^^^^
    |                               |
    |                               borrowed value does not live long enough
    |                               argument requires that `msg` is borrowed for `'static`
228 |     } else {
    |     - `msg` dropped here while still borrowed

I don't think you got the point about scopes. The scope of msg is still limited to the else block. You are not improving anything by defining msg within the block.
Instead you need to pass a String, via a to_string.

I try that but Rust gives me some error about lifetimes.
It turns out I need to make every arm in the if statements returns "String" via to_string() just like what you said. It's not that obvious since I mixed &str and String together and rust give me error about msg lifetime which I found to be confusing.
Thank you

async fn handle_rejection(err: Rejection) -> Result<impl Reply, std::convert::Infallible> {
    if err.is_not_found() {
        Ok(reply::with_status(
            "NOT_FOUND".to_string(),
            StatusCode::NOT_FOUND,
        ))
    } else if let Some(e) = err.find::<AppErr>() {
        let msg = e.reason.as_str().to_string();
        Ok(reply::with_status(msg, StatusCode::BAD_REQUEST))
    } else {
        eprintln!("unhandled rejection: {:?}", err);
        Ok(reply::with_status(
            "INTERNAL_SERVER_ERROR".to_string(),
            StatusCode::INTERNAL_SERVER_ERROR,
        ))
    }
}
```

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.