1use bytes::Bytes;
2use h2;
3use http::header::HeaderValue;
4use http::{self, HeaderMap};
5use log::{debug, trace, warn};
6use percent_encoding::{percent_decode, percent_encode, EncodeSet, DEFAULT_ENCODE_SET};
7use std::{error::Error, fmt};
8
9const GRPC_STATUS_HEADER_CODE: &str = "grpc-status";
10const GRPC_STATUS_MESSAGE_HEADER: &str = "grpc-message";
11const GRPC_STATUS_DETAILS_HEADER: &str = "grpc-status-details-bin";
12
13#[derive(Clone)]
15pub struct Status {
16 code: Code,
18 message: String,
20 details: Bytes,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum Code {
27 Ok = 0,
28 Cancelled = 1,
29 Unknown = 2,
30 InvalidArgument = 3,
31 DeadlineExceeded = 4,
32 NotFound = 5,
33 AlreadyExists = 6,
34 PermissionDenied = 7,
35 ResourceExhausted = 8,
36 FailedPrecondition = 9,
37 Aborted = 10,
38 OutOfRange = 11,
39 Unimplemented = 12,
40 Internal = 13,
41 Unavailable = 14,
42 DataLoss = 15,
43 Unauthenticated = 16,
44
45 #[doc(hidden)]
47 __NonExhaustive,
48}
49
50impl Status {
53 pub fn new(code: Code, message: impl Into<String>) -> Status {
55 Status {
56 code,
57 message: message.into(),
58 details: Bytes::new(),
59 }
60 }
61
62 #[doc(hidden)]
65 #[deprecated(note = "use State::new")]
66 pub fn with_code(code: Code) -> Status {
67 Status::new(code, String::new())
68 }
69
70 #[doc(hidden)]
72 #[deprecated(note = "use State::new")]
73 pub fn with_code_and_message(code: Code, message: String) -> Status {
74 Status::new(code, message)
75 }
76
77 pub(crate) fn from_error(err: &(dyn Error + 'static)) -> Status {
80 Status::try_from_error(err).unwrap_or_else(|| Status::new(Code::Unknown, err.to_string()))
81 }
82
83 fn try_from_error(err: &(dyn Error + 'static)) -> Option<Status> {
84 let mut cause = Some(err);
85
86 while let Some(err) = cause {
87 if let Some(status) = err.downcast_ref::<Status>() {
88 return Some(Status {
89 code: status.code,
90 message: status.message.clone(),
91 details: status.details.clone(),
92 });
93 } else if let Some(h2) = err.downcast_ref::<h2::Error>() {
94 return Some(Status::from_h2_error(h2));
95 }
96
97 cause = err.source();
98 }
99
100 None
101 }
102
103 fn from_h2_error(err: &h2::Error) -> Status {
104 let code = match err.reason() {
106 Some(h2::Reason::NO_ERROR)
107 | Some(h2::Reason::PROTOCOL_ERROR)
108 | Some(h2::Reason::INTERNAL_ERROR)
109 | Some(h2::Reason::FLOW_CONTROL_ERROR)
110 | Some(h2::Reason::SETTINGS_TIMEOUT)
111 | Some(h2::Reason::COMPRESSION_ERROR)
112 | Some(h2::Reason::CONNECT_ERROR) => Code::Internal,
113 Some(h2::Reason::REFUSED_STREAM) => Code::Unavailable,
114 Some(h2::Reason::CANCEL) => Code::Cancelled,
115 Some(h2::Reason::ENHANCE_YOUR_CALM) => Code::ResourceExhausted,
116 Some(h2::Reason::INADEQUATE_SECURITY) => Code::PermissionDenied,
117
118 _ => Code::Unknown,
119 };
120
121 Status::new(code, format!("h2 protocol error: {}", err))
122 }
123
124 fn to_h2_error(&self) -> h2::Error {
125 let reason = match self.code {
127 Code::Cancelled => h2::Reason::CANCEL,
128 _ => h2::Reason::INTERNAL_ERROR,
129 };
130
131 reason.into()
132 }
133
134 pub(crate) fn map_error<E>(err: E) -> Status
135 where
136 E: Into<Box<dyn Error + Send + Sync>>,
137 {
138 Status::from_error(&*err.into())
139 }
140
141 pub(crate) fn from_header_map(header_map: &HeaderMap) -> Option<Status> {
142 header_map.get(GRPC_STATUS_HEADER_CODE).map(|code| {
143 let code = Code::from_bytes(code.as_ref());
144 let error_message = header_map
145 .get(GRPC_STATUS_MESSAGE_HEADER)
146 .map(|header| {
147 percent_decode(header.as_bytes())
148 .decode_utf8()
149 .map(|cow| cow.to_string())
150 })
151 .unwrap_or_else(|| Ok(String::new()));
152 let details = header_map
153 .get(GRPC_STATUS_DETAILS_HEADER)
154 .map(|h| Bytes::from(h.as_bytes()))
155 .unwrap_or_else(Bytes::new);
156 match error_message {
157 Ok(message) => Status {
158 code,
159 message,
160 details,
161 },
162 Err(err) => {
163 warn!("Error deserializing status message header: {}", err);
164 Status {
165 code: Code::Unknown,
166 message: format!("Error deserializing status message header: {}", err),
167 details,
168 }
169 }
170 }
171 })
172 }
173
174 pub fn code(&self) -> Code {
176 self.code
177 }
178
179 pub fn message(&self) -> &str {
181 &self.message
182 }
183
184 pub fn details(&self) -> &[u8] {
186 &self.details
187 }
188
189 #[doc(hidden)]
190 #[deprecated(note = "use Status::message")]
191 pub fn error_message(&self) -> &str {
192 &self.message
193 }
194
195 #[doc(hidden)]
196 #[deprecated(note = "use Status::details")]
197 pub fn binary_error_details(&self) -> &Bytes {
198 &self.details
199 }
200
201 pub(crate) fn to_header_map(&self) -> Result<HeaderMap, Self> {
202 let mut header_map = HeaderMap::with_capacity(3);
203 self.add_header(&mut header_map)?;
204 Ok(header_map)
205 }
206
207 pub(crate) fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
208 header_map.insert(GRPC_STATUS_HEADER_CODE, self.code.to_header_value());
209
210 if !self.message.is_empty() {
211 let is_need_encode = self
212 .message
213 .as_bytes()
214 .iter()
215 .any(|&x| DEFAULT_ENCODE_SET.contains(x));
216 let to_write = if is_need_encode {
217 percent_encode(&self.message().as_bytes(), DEFAULT_ENCODE_SET)
218 .to_string()
219 .into()
220 } else {
221 Bytes::from(self.message().as_bytes())
222 };
223
224 header_map.insert(
225 GRPC_STATUS_MESSAGE_HEADER,
226 HeaderValue::from_shared(to_write).map_err(invalid_header_value_byte)?,
227 );
228 }
229
230 if !self.details.is_empty() {
231 header_map.insert(
232 GRPC_STATUS_DETAILS_HEADER,
233 HeaderValue::from_shared(self.details.clone())
234 .map_err(invalid_header_value_byte)?,
235 );
236 }
237
238 Ok(())
239 }
240}
241
242impl fmt::Debug for Status {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 let mut builder = f.debug_struct("Status");
246
247 builder.field("code", &self.code);
248
249 if !self.message.is_empty() {
250 builder.field("message", &self.message);
251 }
252
253 if !self.details.is_empty() {
254 builder.field("details", &self.details);
255 }
256
257 builder.finish()
258 }
259}
260
261fn invalid_header_value_byte<Error: fmt::Display>(err: Error) -> Status {
262 debug!("Invalid header: {}", err);
263 Status::new(
264 Code::Internal,
265 "Couldn't serialize non-text grpc status header".to_string(),
266 )
267}
268
269impl From<h2::Error> for Status {
270 fn from(err: h2::Error) -> Self {
271 Status::from_h2_error(&err)
272 }
273}
274
275impl From<Status> for h2::Error {
276 fn from(status: Status) -> Self {
277 status.to_h2_error()
278 }
279}
280
281impl fmt::Display for Status {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 write!(
284 f,
285 "grpc-status: {:?}, grpc-message: {:?}",
286 self.code(),
287 self.message()
288 )
289 }
290}
291
292impl Error for Status {}
293
294pub(crate) fn infer_grpc_status(
298 trailers: Option<HeaderMap>,
299 status_code: http::StatusCode,
300) -> Result<(), Status> {
301 if let Some(trailers) = trailers {
302 if let Some(status) = Status::from_header_map(&trailers) {
303 if status.code() == Code::Ok {
304 return Ok(());
305 } else {
306 return Err(status);
307 }
308 }
309 }
310 trace!("trailers missing grpc-status");
311 let code = match status_code {
312 http::StatusCode::BAD_REQUEST => Code::Internal,
314 http::StatusCode::UNAUTHORIZED => Code::Unauthenticated,
315 http::StatusCode::FORBIDDEN => Code::PermissionDenied,
316 http::StatusCode::NOT_FOUND => Code::Unimplemented,
317 http::StatusCode::TOO_MANY_REQUESTS
318 | http::StatusCode::BAD_GATEWAY
319 | http::StatusCode::SERVICE_UNAVAILABLE
320 | http::StatusCode::GATEWAY_TIMEOUT => Code::Unavailable,
321 _ => Code::Unknown,
322 };
323
324 let msg = format!(
325 "grpc-status header missing, mapped from HTTP status code {}",
326 status_code.as_u16(),
327 );
328 let status = Status::new(code, msg);
329 Err(status)
330}
331
332impl Code {
335 pub fn from_i32(i: i32) -> Code {
339 Code::from(i)
340 }
341
342 pub(crate) fn from_bytes(bytes: &[u8]) -> Code {
343 match bytes.len() {
344 1 => match bytes[0] {
345 b'0' => Code::Ok,
346 b'1' => Code::Cancelled,
347 b'2' => Code::Unknown,
348 b'3' => Code::InvalidArgument,
349 b'4' => Code::DeadlineExceeded,
350 b'5' => Code::NotFound,
351 b'6' => Code::AlreadyExists,
352 b'7' => Code::PermissionDenied,
353 b'8' => Code::ResourceExhausted,
354 b'9' => Code::FailedPrecondition,
355 _ => Code::parse_err(),
356 },
357 2 => match (bytes[0], bytes[1]) {
358 (b'1', b'0') => Code::Aborted,
359 (b'1', b'1') => Code::OutOfRange,
360 (b'1', b'2') => Code::Unimplemented,
361 (b'1', b'3') => Code::Internal,
362 (b'1', b'4') => Code::Unavailable,
363 (b'1', b'5') => Code::DataLoss,
364 (b'1', b'6') => Code::Unauthenticated,
365 _ => Code::parse_err(),
366 },
367 _ => Code::parse_err(),
368 }
369 }
370
371 fn to_header_value(&self) -> HeaderValue {
372 match self {
373 Code::Ok => HeaderValue::from_static("0"),
374 Code::Cancelled => HeaderValue::from_static("1"),
375 Code::Unknown => HeaderValue::from_static("2"),
376 Code::InvalidArgument => HeaderValue::from_static("3"),
377 Code::DeadlineExceeded => HeaderValue::from_static("4"),
378 Code::NotFound => HeaderValue::from_static("5"),
379 Code::AlreadyExists => HeaderValue::from_static("6"),
380 Code::PermissionDenied => HeaderValue::from_static("7"),
381 Code::ResourceExhausted => HeaderValue::from_static("8"),
382 Code::FailedPrecondition => HeaderValue::from_static("9"),
383 Code::Aborted => HeaderValue::from_static("10"),
384 Code::OutOfRange => HeaderValue::from_static("11"),
385 Code::Unimplemented => HeaderValue::from_static("12"),
386 Code::Internal => HeaderValue::from_static("13"),
387 Code::Unavailable => HeaderValue::from_static("14"),
388 Code::DataLoss => HeaderValue::from_static("15"),
389 Code::Unauthenticated => HeaderValue::from_static("16"),
390
391 Code::__NonExhaustive => unreachable!("Code::__NonExhaustive"),
392 }
393 }
394
395 fn parse_err() -> Code {
396 trace!("error parsing grpc-status");
397 Code::Unknown
398 }
399}
400
401impl From<i32> for Code {
402 fn from(i: i32) -> Self {
403 match i {
404 0 => Code::Ok,
405 1 => Code::Cancelled,
406 2 => Code::Unknown,
407 3 => Code::InvalidArgument,
408 4 => Code::DeadlineExceeded,
409 5 => Code::NotFound,
410 6 => Code::AlreadyExists,
411 7 => Code::PermissionDenied,
412 8 => Code::ResourceExhausted,
413 9 => Code::FailedPrecondition,
414 10 => Code::Aborted,
415 11 => Code::OutOfRange,
416 12 => Code::Unimplemented,
417 13 => Code::Internal,
418 14 => Code::Unavailable,
419 15 => Code::DataLoss,
420 16 => Code::Unauthenticated,
421
422 _ => Code::Unknown,
423 }
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use crate::error::Error;
431
432 #[derive(Debug)]
433 struct Nested(Error);
434
435 impl fmt::Display for Nested {
436 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437 write!(f, "nested error: {}", self.0)
438 }
439 }
440
441 impl std::error::Error for Nested {
442 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
443 Some(&*self.0)
444 }
445 }
446
447 #[test]
448 fn from_error_status() {
449 let orig = Status::new(Code::OutOfRange, "weeaboo");
450 let found = Status::from_error(&orig);
451
452 assert_eq!(orig.code(), found.code());
453 assert_eq!(orig.message(), found.message());
454 }
455
456 #[test]
457 fn from_error_unknown() {
458 let orig: Error = "peek-a-boo".into();
459 let found = Status::from_error(&*orig);
460
461 assert_eq!(found.code(), Code::Unknown);
462 assert_eq!(found.message(), orig.to_string());
463 }
464
465 #[test]
466 fn from_error_nested() {
467 let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
468 let found = Status::from_error(&orig);
469
470 assert_eq!(found.code(), Code::OutOfRange);
471 assert_eq!(found.message(), "weeaboo");
472 }
473
474 #[test]
475 fn from_error_h2() {
476 let orig = h2::Error::from(h2::Reason::CANCEL);
477 let found = Status::from_error(&orig);
478
479 assert_eq!(found.code(), Code::Cancelled);
480 }
481
482 #[test]
483 fn to_h2_error() {
484 let orig = Status::new(Code::Cancelled, "stop eet!");
485 let err = orig.to_h2_error();
486
487 assert_eq!(err.reason(), Some(h2::Reason::CANCEL));
488 }
489
490 #[test]
491 fn code_from_i32() {
492 for i in 0..(Code::__NonExhaustive as i32) {
495 let code = Code::from(i);
496 assert_eq!(
497 i, code as i32,
498 "Code::from({}) returned {:?} which is {}",
499 i, code, code as i32,
500 );
501 }
502
503 assert_eq!(Code::from(-1), Code::Unknown);
504 assert_eq!(Code::from(Code::__NonExhaustive as i32), Code::Unknown);
505 }
506}