Skip to main content

humblegen_rt/
service_protocol.rs

1//! `PROTO` - data types used by the runtime to implement the humblespec service protocol.
2//! The serde representation of the types in this module is part of the service protocol
3//! specified in `humblespec/service_protocol.md`.
4
5use hyper::Body;
6use hyper::Response;
7
8use serde::{Deserialize, Serialize};
9
10// The "Error Response"
11#[derive(Debug, Serialize, Deserialize)]
12pub struct ErrorResponse {
13    pub code: u16,
14    pub kind: ErrorResponseKind,
15}
16
17pub trait ToErrorResponse {
18    fn to_error_response(self) -> ErrorResponse;
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22pub enum ErrorResponseKind {
23    Service(ServiceError),
24    Runtime(RuntimeError),
25}
26
27/// A service-level error.
28///
29/// This type is returned by implementors of a humblegen service trait function
30/// as part of a `HandlerResponse`.
31#[derive(Debug, Serialize, Deserialize)]
32pub enum ServiceError {
33    /// The request cannot be fulfilled due to an authentication problem.
34    /// Maps to HTTP status code 401.
35    Authentication,
36    /// The request cannot be fulfilled due to an authorization problem.
37    /// Maps to HTTP status code 403.
38    Authorization,
39    /// The request cannot be fulfilled due to an error internal to the service
40    /// that is _not_ domain-specific. (Domain-specific errors should be represented
41    /// as humblespec result types in the service definition.)
42    /// Maps to HTTP status code 500.
43    /// Examples: no database connection
44    Internal(String),
45}
46
47/// Responses generated by humblegen-rt for conditions that are outside
48/// of handler trait impl's realm of control.
49#[derive(Debug, Serialize, Deserialize)]
50pub enum RuntimeError {
51    NoServiceMounted,
52    ServiceMountsAmbiguous,
53    NoRouteMountedInService {
54        service: String,
55    },
56    RouteMountsAmbiguous {
57        service: String,
58    },
59    RouteParamInvalid {
60        param_name: String,
61        parse_error: String,
62    },
63    QueryInvalid(String),
64    PostBodyReadError(String),
65    PostBodyInvalid(String),
66    SerializeHandlerResponse(String),
67    SerializeErrorResponse(String),
68}
69
70impl ErrorResponse {
71    pub fn to_hyper_response(&self) -> Response<Body> {
72        hyper::Response::builder()
73            .status(self.code)
74            .body(
75                serde_json::to_string_pretty(self)
76                    .expect("runtime responses must be JSON-serializable")
77                    .into(),
78            )
79            .expect("runtime responses must always be buildable")
80    }
81}
82
83impl ToErrorResponse for ServiceError {
84    fn to_error_response(self) -> ErrorResponse {
85        ErrorResponse {
86            code: self.status_code(),
87            kind: ErrorResponseKind::Service(self),
88        }
89    }
90}
91
92impl ToErrorResponse for RuntimeError {
93    fn to_error_response(self) -> ErrorResponse {
94        ErrorResponse {
95            code: self.status_code(),
96            kind: ErrorResponseKind::Runtime(self),
97        }
98    }
99}
100
101impl RuntimeError {
102    fn status_code(&self) -> u16 {
103        match self {
104            RuntimeError::NoServiceMounted => 404,
105            RuntimeError::NoRouteMountedInService { .. } => 404,
106            RuntimeError::RouteMountsAmbiguous { .. } => 500,
107            RuntimeError::ServiceMountsAmbiguous => 500,
108            RuntimeError::RouteParamInvalid { .. } => 400,
109            RuntimeError::QueryInvalid(_) => 400,
110            RuntimeError::PostBodyReadError(_) => 400,
111            RuntimeError::PostBodyInvalid(_) => 400,
112            RuntimeError::SerializeHandlerResponse(_) => 500,
113            RuntimeError::SerializeErrorResponse(_) => 500,
114        }
115    }
116}
117
118impl ServiceError {
119    pub fn status_code(&self) -> u16 {
120        match self {
121            ServiceError::Authentication => 401,
122            ServiceError::Authorization => 403,
123            ServiceError::Internal(_) => 500,
124        }
125    }
126}
127
128impl From<super::handler::ServiceError> for ServiceError {
129    fn from(e: super::handler::ServiceError) -> Self {
130        match e {
131            super::handler::ServiceError::Authentication => ServiceError::Authentication,
132            super::handler::ServiceError::Authorization => ServiceError::Authorization,
133            super::handler::ServiceError::Internal(e) => ServiceError::Internal(format!("{}", e)),
134        }
135    }
136}