connect_rpc/response/
error.rs1use http::{header, HeaderMap, HeaderValue};
2
3use crate::{common::base64_decode, metadata::Metadata, Error};
4
5const ERROR_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/json");
6
7#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
9pub struct ConnectError {
10 #[serde(default, deserialize_with = "deserialize_error_code")]
11 code: Option<ConnectCode>,
12 #[serde(default, skip_serializing_if = "String::is_empty")]
13 pub message: String,
14 #[serde(default, skip_serializing_if = "Vec::is_empty")]
15 pub details: Vec<ConnectErrorDetail>,
16 #[serde(skip)]
17 headers: HeaderMap,
18}
19
20impl ConnectError {
21 pub fn new(code: ConnectCode, message: impl std::fmt::Display) -> Self {
22 Self {
23 code: Some(code),
24 message: message.to_string(),
25 details: Default::default(),
26 headers: Default::default(),
27 }
28 }
29
30 pub fn code(&self) -> ConnectCode {
31 self.code.unwrap_or(ConnectCode::Unknown)
32 }
33
34 pub fn metadata(&self) -> &impl Metadata {
35 &self.headers
36 }
37}
38
39impl std::fmt::Display for ConnectError {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.write_str(serde_json::to_value(self.code()).unwrap().as_str().unwrap())?;
42 if !self.message.is_empty() {
43 write!(f, ": {}", self.message)?;
44 }
45 Ok(())
46 }
47}
48
49impl<T: AsRef<[u8]>> From<http::Response<T>> for ConnectError {
50 fn from(resp: http::Response<T>) -> Self {
51 let (parts, body) = resp.into_parts();
52 let error = if parts.headers.get(header::CONTENT_TYPE) == Some(&ERROR_CONTENT_TYPE) {
53 match serde_json::from_slice::<ConnectError>(body.as_ref()) {
54 Ok(mut error) => {
55 error.code.get_or_insert_with(|| parts.status.into());
56 Some(error)
57 }
58 Err(err) => {
59 tracing::debug!(?err, "Failed to decode error JSON");
60 None
61 }
62 }
63 } else {
64 None
65 };
66 let mut error = error.unwrap_or_else(|| Self::new(parts.status.into(), "request invalid"));
67 error.headers = parts.headers;
68 error
69 }
70}
71
72impl From<Error> for ConnectError {
73 fn from(err: Error) -> Self {
74 let code = match err {
75 Error::ConnectError(connect_error) => return connect_error,
76 Error::InvalidResponse(_)
77 | Error::UnacceptableEncoding(_)
78 | Error::UnexpectedMessageCodec(_) => ConnectCode::Internal,
79 _ => ConnectCode::Unknown,
80 };
81 let message = match &err {
82 Error::UnacceptableEncoding(_) | Error::UnexpectedMessageCodec(_) => err.to_string(),
83 _ => "".into(),
84 };
85 Self::new(code, message)
86 }
87}
88
89fn deserialize_error_code<'de, D: serde::Deserializer<'de>>(
90 deserializer: D,
91) -> Result<Option<ConnectCode>, D::Error> {
92 use serde::Deserialize;
93 Option::<ConnectCode>::deserialize(deserializer).or(Ok(None))
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
98#[serde(rename_all = "snake_case")]
99pub enum ConnectCode {
100 Ok,
102 Canceled,
104 Unknown,
106 InvalidArgument,
108 DeadlineExceeded,
110 NotFound,
112 AlreadyExists,
114 PermissionDenied,
116 ResourceExhausted,
118 FailedPrecondition,
120 Aborted,
122 OutOfRange,
124 Unimplemented,
126 Internal,
128 Unavailable,
130 DataLoss,
132 Unauthenticated,
134}
135
136impl From<http::StatusCode> for ConnectCode {
138 fn from(code: http::StatusCode) -> Self {
139 use http::StatusCode;
140 match code {
141 StatusCode::BAD_REQUEST => Self::Internal,
142 StatusCode::UNAUTHORIZED => Self::Unauthenticated,
143 StatusCode::FORBIDDEN => Self::PermissionDenied,
144 StatusCode::NOT_FOUND => Self::Unimplemented,
145 StatusCode::NOT_IMPLEMENTED => Self::Unimplemented,
146 StatusCode::TOO_MANY_REQUESTS
147 | StatusCode::BAD_GATEWAY
148 | StatusCode::SERVICE_UNAVAILABLE
149 | StatusCode::GATEWAY_TIMEOUT => Self::Unavailable,
150 _ => Self::Unknown,
151 }
152 }
153}
154
155#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
157pub struct ConnectErrorDetail {
158 #[serde(rename = "type")]
159 pub proto_type: String,
160 #[serde(rename = "value")]
161 pub value_base64: String,
162}
163
164impl ConnectErrorDetail {
165 pub fn type_url(&self) -> String {
166 format!("type.googleapis.com/{}", self.proto_type)
167 }
168
169 pub fn value(&self) -> Result<Vec<u8>, Error> {
170 base64_decode(&self.value_base64)
171 }
172}