1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use error::Error;
use serde_json;
use response::Serializer;
use util::BufStream;

use futures::{future, Future, IntoFuture};
use http;

use std::sync::Arc;

/// Handles an error generated by a resource method.
///
/// In the event that an error occurs while processing an HTTP request,
/// implementations of `Catch` have the opportunity of gracefully handling the
/// error. This usually means responding with a friendly error response.
///
/// For example, if the error is generated due to no routes matching the HTTP
/// request, then a 404 response should be returned. If a route was matched, but
/// the arguments were not satisfied, then a 400 response should be returned.
pub trait Catch: Clone {

    /// The HTTP response body type
    type Body: BufStream;

    /// Future yielding the HTTP response.
    type Future: Future<Item = http::Response<Self::Body>, Error = Error>;

    /// Handles an error.
    ///
    /// Implementations of `Catch` generate a friendly HTTP error response based
    /// on the provideed `request` and `error`.
    ///
    /// See module level documentation for more details.
    fn catch(&mut self, request: &http::Request<()>, error: Error) -> Self::Future;
}

/// Convert `Self` into a value that implements `Catch`.
///
/// By implementing `IntoCatch` for a type, you define how it will be converted
/// into a `Catch` instance.
///
/// The `Serializer` configured for the service is provided. This allows the
/// `Catch` instance to be able to serialize responses.
pub trait IntoCatch<S> {
    /// The `Catch` value being converted to
    type Catch: Catch;

    /// Convert `self` into a `Catch` value.
    fn into_catch(self) -> Self::Catch;
}

/// The default `Catch` instance used when none is specified.
///
/// This implementation provides basic error handling by generating simple
/// HTTP responses based on the error type.
#[derive(Debug, Clone)]
pub struct DefaultCatch {
    _p: (),
}

/// Provides a `Catch` implementation using the provided `Fn` instance.
#[derive(Debug)]
pub struct FnCatch<F>(Arc<F>);

// ===== impl DefaultCatch =====

impl DefaultCatch {
    /// Create a new `DefaultCatch` instance.
    pub fn new() -> DefaultCatch {
        DefaultCatch {
            _p: (),
        }
    }
}

impl<S> IntoCatch<S> for DefaultCatch
where S: Serializer,
{
    type Catch = DefaultCatch;

    fn into_catch(self) -> Self::Catch {
        self
    }
}

/// Implements `Catch` by returning an HTTP response with the appropriate status
/// code.
///
/// The HTTP response will include a basic error message in the response body.
impl Catch for DefaultCatch {
    type Body = String;
    type Future = future::FutureResult<http::Response<Self::Body>, Error>;

    fn catch(&mut self, _request: &http::Request<()>, error: Error) -> Self::Future {
        let status = error.status_code().as_u16();
        let problem = serde_json::to_string(&error)
            .unwrap_or_else(|_| {
                serde_json::to_string(&Error::from(http::status::StatusCode::INTERNAL_SERVER_ERROR))
                    .expect("Error serializing a blank error to JSON")
            });

        // TODO: Improve the default errors
        let response = http::response::Builder::new()
            .status(status)
            .header("content-type", "application/problem+json")
            .body(problem)
            .unwrap();

        future::ok(response)
    }
}

// ===== impl FnCatch =====

impl<F, R, Body, S> IntoCatch<S> for F
where F: Fn(&http::Request<()>, Error) -> R,
      R: IntoFuture<Item = http::Response<Body>, Error = Error>,
      Body: BufStream,
{
    type Catch = FnCatch<F>;

    fn into_catch(self) -> Self::Catch {
        FnCatch(Arc::new(self))
    }
}

impl<F, R, Body> Catch for FnCatch<F>
where F: Fn(&http::Request<()>, Error) -> R,
      R: IntoFuture<Item = http::Response<Body>, Error = Error>,
      Body: BufStream,
{
    type Body = Body;
    type Future = R::Future;

    fn catch(&mut self, request: &http::Request<()>, error: Error) -> Self::Future {
        self.0(request, error).into_future()
    }
}

impl<F> Clone for FnCatch<F> {
    fn clone(&self) -> FnCatch<F> {
        FnCatch(self.0.clone())
    }
}