2
votes

I am trying to create a REST server using hyper. For robust error handling, I would prefer to have the service return a future with a custom error type that wraps hyper, Diesel, and other errors. Unfortunately, hyper::Response seems to hard-code a stream with error type hyper::error::Error, which conflicts with the error type I've defined for my service. I see a couple possible solutions:

  1. Make my service return my custom error type by modifying hyper::Response, which seems hard.

  2. Wrap non-hyper errors in a hyper::error::Error. This seems hacky.

  3. Something else. It seems like I'm missing the "right" way to do this.

The following code shows what I think I want to do:

extern crate diesel;
extern crate futures;
extern crate hyper;

use futures::future::{ok, Future};
use hyper::StatusCode;
use hyper::server::{Request, Response, Service};

fn main() {
    let address = "127.0.0.1:8080".parse().unwrap();
    let server = hyper::server::Http::new()
        .bind(&address, move || Ok(ApiService {}))
        .unwrap();
    server.run().unwrap();
}

pub struct ApiService;

impl Service for ApiService {
    type Request = Request;
    type Response = Response;
    type Error = Error;
    type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

    fn call(&self, request: Request) -> Self::Future {
        Box::new(ok(Response::new().with_status(StatusCode::Ok)))
    }
}

#[derive(Debug)]
pub enum Error {
    Request(hyper::Error),
    DatabaseResult(diesel::result::Error),
    DatabaseConnection(diesel::ConnectionError),
    Other(String),
}

// omitted impl of Display, std::error::Error for brevity

This code results in a compiler error which I believe is because the bind function requires that the response type have a body that is a stream with error type hyper::error::Error:

error[E0271]: type mismatch resolving `<ApiService as hyper::client::Service>::Error == hyper::Error`
  --> src/main.rs:14:10
   |
14 |         .bind(&address, move || Ok(ApiService {}))
   |          ^^^^ expected enum `Error`, found enum `hyper::Error`
   |
   = note: expected type `Error`
              found type `hyper::Error`
2

2 Answers

5
votes

Because the ultimate goal of the server is to return a response to the user, I found an acceptable solution to be to create a finalize function that converts errors encountered while processing a request into correctly formed responses and treats those errors as non-errors from hyper's perspective. I will need to flesh this idea out some (e.g. By passing along hyper errors as errors), but I believe the basic idea is sound.

The following code modifies the code in the question to do this:

extern crate diesel;
extern crate futures;
extern crate hyper;
#[macro_use]
extern crate serde_derive;

use futures::future::{ok, Future};
use hyper::StatusCode;
use hyper::server::{Request, Response, Service};

fn main() {
    let address = "127.0.0.1:8080".parse().unwrap();
    let server = hyper::server::Http::new()
        .bind(&address, move || Ok(ApiService {}))
        .unwrap();
    server.run().unwrap();
}

fn finalize(result: Result<Response, Error>) -> FutureResult<Response, hyper::Error> {
    match result {
        Ok(response) => ok(response),
        Err(error) => {
            let response_body =
                json!({"status": 500, "description": error.description()}).to_string();
            ok(Response::new()
                .with_status(StatusCode::InternalServerError)
                .with_header(ContentLength(response_body.len() as u64))
                .with_body(response_body))
        }
    }
}

pub struct ApiService;

impl Service for ApiService {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

    fn call(&self, request: Request) -> Self::Future {
        let response = Ok(Response::new().with_status(StatusCode::Ok));
        Box::new(finalize(response))
    }
}

#[derive(Debug)]
pub enum Error {
    Request(hyper::Error),
    DatabaseResult(diesel::result::Error),
    DatabaseConnection(diesel::ConnectionError),
    Other(String),
}

// omitted impl of Display, std::error::Error for brevity
-3
votes

You can implement the std::convert::From trait for your Error type. E.g. for the hyper::Error case:

impl From<hyper::Error> for Error {
    fn from(error: hyper::Error) -> Self {
        Error::Request(error)
    }
}