1

I am trying to use anyhow::Result instead of std::io::Result in an actix-web service. This is because I want to be able to use the "?" operator instead of .expect/.map_err. However, I am getting an error message:

error[E0277]: `?` couldn't convert the error to `std::io::Error`
  --> backend/src/services/get_movies_count.rs:10:48
   |
10 |     let mut database = app_state.database.get()?;
   |                                                ^ the trait `std::convert::From<r2d2::Error>` is not implemented for `std::io::Error`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = help: the following other types implement trait `std::convert::From<T>`:
             <std::io::Error as std::convert::From<ErrorKind>>
             <std::io::Error as std::convert::From<IntoInnerError<W>>>
             <std::io::Error as std::convert::From<JoinError>>
             <std::io::Error as std::convert::From<NulError>>
             <std::io::Error as std::convert::From<flate2::mem::CompressError>>
             <std::io::Error as std::convert::From<flate2::mem::DecompressError>>
             <std::io::Error as std::convert::From<getrandom::error::Error>>
             <std::io::Error as std::convert::From<httpdate::Error>>
           and 4 others
   = note: required for `Result<String, std::io::Error>` to implement `FromResidual<Result<Infallible, r2d2::Error>>`

For more information about this error, try `rustc --explain E0277`.

Here is the actix-web service code:

use crate::AppState;
use anyhow::Result;
use actix_web::{get, web::Data};

#[get("movies/count")]
async fn get_movies_count(app_state: Data<AppState>) -> Result<String> {
    let database = app_state.database.get()?;
    Ok("Hi".into())
}

main.rs

use std::env::var;
use anyhow::Result;
use actix_web::{HttpServer, App, web::Data};
use diesel::{r2d2::{ConnectionManager, Pool}, SqliteConnection};
use services::{
    get_movies_count::get_movies_count,
};

mod schema;
mod models;
mod services;

#[derive(Clone)]
struct AppState {
    database: Pool<ConnectionManager<SqliteConnection>>
}

#[actix_web::main]
async fn main() -> Result<()> {
    dotenv::dotenv()?;
    let manager = ConnectionManager::<SqliteConnection>::new(var("DATABASE_URL")?);
    let pool = Pool::builder().build(manager)?;
    let app_state = AppState {
        database: pool
    };
    HttpServer::new(
        move || {
            App::new()
            .app_data(Data::new(app_state.clone()))
            .service(get_movies_count)
        }
    )
    .bind(
        (
            var("HOST")?,
            var("API_PORT")?.parse()?
        )
    )?
    .run()
    .await?;
    Ok(())
}
VenoX
  • 11
  • 2

1 Answers1

0

The documentation explains how to implement your own Error type, that is understood by actix: Your error type needs to implement the trait actix_web::error::ResponseError.

To solve your problem, the easiest way would to be implement a "wrapper" struct, as seen here. The code from the previous link can be simplified using the thiserror crate, which I wholeheartedly recommend you to use.

DISCLAIMER: I didn't test this code. It might have some syntax error hidden. This example is to demonstrate the principle.

The simplified version would look like this:


#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("an unspecified internal error occurred: {0}")]
    InternalError(#[from] anyhow::Error),
}

impl ResponseError for Error {

    fn status_code(&self) -> StatusCode {
        match &self {
            Self::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR
        }
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponse::build(self.status_code()).body(self.to_string())
    }

}

// Short hand alias, which allows you to use just Result<T>
pub type Result<T> = std::result::Result<T, Error>;

Your handlers would now look something like this:


#[get("/")]
async fn my_handler() -> Result<ReturnType> {
    anything_that_returns_anyhow_error()?;

    // And whatever else your handler should be doing
    todo!()
}


The solution proposed above has one issue: It disregards the different http status codes and always returns HTTP 500 - Internal Server Error, whenever it catches an error.

I don't have enough experience with anyhow to know, whether you can get the original error to perhaps return a different HTTP status and/or response.

However, the solution I am using now, which also suggest you to use, is to have several cases for your errors. In my projects, it looks roughly like this:


#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("this resource requires higher privileges")]
    Forbidden,
    #[error("this resource was not found")]
    NotFound,
    #[error("an unhandled database error occurred")]
    DatabaseError(#[from] sqlx::Error),
    // and so on
}

You would extend your implementation of ResponseError for Error to return the proper response (and status code) for each of those cases.

Julian Kirsch
  • 539
  • 4
  • 17