Skip to main content

axum_connect/
error.rs

1use axum::http::StatusCode;
2use prost::Message;
3use serde::Serialize;
4
5use crate::{prelude::RpcResult, response::RpcIntoResponse};
6
7#[derive(Clone, Serialize)]
8pub struct RpcError {
9    pub code: RpcErrorCode,
10    pub message: String,
11    pub details: Vec<RpcErrorDetail>,
12}
13
14pub trait RpcIntoError {
15    fn rpc_into_error(self) -> RpcError;
16}
17
18impl RpcIntoError for RpcError {
19    fn rpc_into_error(self) -> RpcError {
20        self
21    }
22}
23
24impl RpcError {
25    pub fn new(code: RpcErrorCode, message: String) -> Self {
26        Self {
27            code,
28            message,
29            details: vec![],
30        }
31    }
32}
33
34impl<C, M> RpcIntoError for (C, M)
35where
36    C: Into<RpcErrorCode>,
37    M: Into<String>,
38{
39    fn rpc_into_error(self) -> RpcError {
40        RpcError {
41            code: self.0.into(),
42            message: self.1.into(),
43            details: vec![],
44        }
45    }
46}
47
48#[derive(Clone, Serialize)]
49pub struct RpcErrorDetail {
50    #[serde(rename = "type")]
51    pub proto_type: String,
52    #[serde(rename = "value")]
53    pub proto_b62_value: String,
54}
55
56#[derive(Clone, Serialize)]
57#[serde(rename_all = "snake_case")]
58pub enum RpcErrorCode {
59    Canceled,
60    Unknown,
61    InvalidArgument,
62    DeadlineExceeded,
63    NotFound,
64    AlreadyExists,
65    PermissionDenied,
66    ResourceExhausted,
67    FailedPrecondition,
68    Aborted,
69    OutOfRange,
70    Unimplemented,
71    Internal,
72    Unavailable,
73    DataLoss,
74    Unauthenticated,
75}
76
77impl From<RpcErrorCode> for StatusCode {
78    fn from(val: RpcErrorCode) -> Self {
79        match val {
80            // Spec: https://connect.build/docs/protocol/#error-codes
81            RpcErrorCode::Canceled => StatusCode::REQUEST_TIMEOUT,
82            RpcErrorCode::Unknown => StatusCode::INTERNAL_SERVER_ERROR,
83            RpcErrorCode::InvalidArgument => StatusCode::BAD_REQUEST,
84            RpcErrorCode::DeadlineExceeded => StatusCode::REQUEST_TIMEOUT,
85            RpcErrorCode::NotFound => StatusCode::NOT_FOUND,
86            RpcErrorCode::AlreadyExists => StatusCode::CONFLICT,
87            RpcErrorCode::PermissionDenied => StatusCode::FORBIDDEN,
88            RpcErrorCode::ResourceExhausted => StatusCode::TOO_MANY_REQUESTS,
89            RpcErrorCode::FailedPrecondition => StatusCode::PRECONDITION_FAILED,
90            RpcErrorCode::Aborted => StatusCode::CONFLICT,
91            RpcErrorCode::OutOfRange => StatusCode::BAD_REQUEST,
92            RpcErrorCode::Unimplemented => StatusCode::NOT_FOUND,
93            RpcErrorCode::Internal => StatusCode::INTERNAL_SERVER_ERROR,
94            RpcErrorCode::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
95            RpcErrorCode::DataLoss => StatusCode::INTERNAL_SERVER_ERROR,
96            RpcErrorCode::Unauthenticated => StatusCode::UNAUTHORIZED,
97        }
98    }
99}
100
101impl<T> RpcIntoResponse<T> for RpcErrorCode
102where
103    T: Message,
104{
105    fn rpc_into_response(self) -> RpcResult<T> {
106        Err(RpcError::new(self, "".to_string()))
107    }
108}
109
110impl<T> RpcIntoResponse<T> for RpcError
111where
112    T: Message,
113{
114    fn rpc_into_response(self) -> RpcResult<T> {
115        Err(self)
116    }
117}
118
119// TODO: This needs to be done in the handler to support streaming errors.
120// impl IntoResponse for RpcError {
121//     fn into_response(self) -> Response {
122//         let status_code = StatusCode::from(self.code.clone());
123//         let json = serde_json::to_string(&self).expect("serialize error type");
124//         (status_code, json).into_response()
125//     }
126// }