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
//! `PROTO` - data types used by the runtime to implement the humblespec service protocol.
//! The serde representation of the types in this module is part of the service protocol
//! specified in `humblespec/service_protocol.md`.

use hyper::Body;
use hyper::Response;

use serde::{Deserialize, Serialize};

// The "Error Response"
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
    pub code: u16,
    pub kind: ErrorResponseKind,
}

pub trait ToErrorResponse {
    fn to_error_response(self) -> ErrorResponse;
}

#[derive(Debug, Serialize, Deserialize)]
pub enum ErrorResponseKind {
    Service(ServiceError),
    Runtime(RuntimeError),
}

/// A service-level error.
///
/// This type is returned by implementors of a humblegen service trait function
/// as part of a `HandlerResponse`.
#[derive(Debug, Serialize, Deserialize)]
pub enum ServiceError {
    /// The request cannot be fulfilled due to an authentication problem.
    /// Maps to HTTP status code 401.
    Authentication,
    /// The request cannot be fulfilled due to an authorization problem.
    /// Maps to HTTP status code 403.
    Authorization,
    /// The request cannot be fulfilled due to an error internal to the service
    /// that is _not_ domain-specific. (Domain-specific errors should be represented
    /// as humblespec result types in the service definition.)
    /// Maps to HTTP status code 500.
    /// Examples: no database connection
    Internal(String),
}

/// Responses generated by humblegen-rt for conditions that are outside
/// of handler trait impl's realm of control.
#[derive(Debug, Serialize, Deserialize)]
pub enum RuntimeError {
    NoServiceMounted,
    ServiceMountsAmbiguous,
    NoRouteMountedInService {
        service: String,
    },
    RouteMountsAmbiguous {
        service: String,
    },
    RouteParamInvalid {
        param_name: String,
        parse_error: String,
    },
    QueryInvalid(String),
    PostBodyReadError(String),
    PostBodyInvalid(String),
    SerializeHandlerResponse(String),
    SerializeErrorResponse(String),
}

impl ErrorResponse {
    pub fn to_hyper_response(&self) -> Response<Body> {
        hyper::Response::builder()
            .status(self.code)
            .body(
                serde_json::to_string_pretty(self)
                    .expect("runtime responses must be JSON-serializable")
                    .into(),
            )
            .expect("runtime responses must always be buildable")
    }
}

impl ToErrorResponse for ServiceError {
    fn to_error_response(self) -> ErrorResponse {
        ErrorResponse {
            code: self.status_code(),
            kind: ErrorResponseKind::Service(self),
        }
    }
}

impl ToErrorResponse for RuntimeError {
    fn to_error_response(self) -> ErrorResponse {
        ErrorResponse {
            code: self.status_code(),
            kind: ErrorResponseKind::Runtime(self),
        }
    }
}

impl RuntimeError {
    fn status_code(&self) -> u16 {
        match self {
            RuntimeError::NoServiceMounted => 404,
            RuntimeError::NoRouteMountedInService { .. } => 404,
            RuntimeError::RouteMountsAmbiguous { .. } => 500,
            RuntimeError::ServiceMountsAmbiguous => 500,
            RuntimeError::RouteParamInvalid { .. } => 400,
            RuntimeError::QueryInvalid(_) => 400,
            RuntimeError::PostBodyReadError(_) => 400,
            RuntimeError::PostBodyInvalid(_) => 400,
            RuntimeError::SerializeHandlerResponse(_) => 500,
            RuntimeError::SerializeErrorResponse(_) => 500,
        }
    }
}

impl ServiceError {
    pub fn status_code(&self) -> u16 {
        match self {
            ServiceError::Authentication => 401,
            ServiceError::Authorization => 403,
            ServiceError::Internal(_) => 500,
        }
    }
}

impl From<super::handler::ServiceError> for ServiceError {
    fn from(e: super::handler::ServiceError) -> Self {
        match e {
            super::handler::ServiceError::Authentication => ServiceError::Authentication,
            super::handler::ServiceError::Authorization => ServiceError::Authorization,
            super::handler::ServiceError::Internal(e) => ServiceError::Internal(format!("{}", e)),
        }
    }
}