1use crate::body::BoxBody;
2use crate::metadata::MetadataMap;
3use bytes::Bytes;
4use http::header::{HeaderMap, HeaderValue};
5use percent_encoding::{percent_decode, percent_encode, AsciiSet, CONTROLS};
6use std::{borrow::Cow, error::Error, fmt};
7use tracing::{debug, trace, warn};
8
9const ENCODING_SET: &AsciiSet = &CONTROLS
10 .add(b' ')
11 .add(b'"')
12 .add(b'#')
13 .add(b'<')
14 .add(b'>')
15 .add(b'`')
16 .add(b'?')
17 .add(b'{')
18 .add(b'}');
19
20const GRPC_STATUS_HEADER_CODE: &str = "grpc-status";
21const GRPC_STATUS_MESSAGE_HEADER: &str = "grpc-message";
22const GRPC_STATUS_DETAILS_HEADER: &str = "grpc-status-details-bin";
23
24pub struct Status {
37 code: Code,
39 message: String,
41 details: Bytes,
43 metadata: MetadataMap,
47 source: Option<Box<dyn Error + Send + Sync + 'static>>,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub enum Code {
58 Ok = 0,
60
61 Cancelled = 1,
63
64 Unknown = 2,
66
67 InvalidArgument = 3,
69
70 DeadlineExceeded = 4,
72
73 NotFound = 5,
75
76 AlreadyExists = 6,
78
79 PermissionDenied = 7,
81
82 ResourceExhausted = 8,
84
85 FailedPrecondition = 9,
87
88 Aborted = 10,
90
91 OutOfRange = 11,
93
94 Unimplemented = 12,
96
97 Internal = 13,
99
100 Unavailable = 14,
102
103 DataLoss = 15,
105
106 Unauthenticated = 16,
108}
109
110impl Code {
111 pub fn description(&self) -> &'static str {
124 match self {
125 Code::Ok => "The operation completed successfully",
126 Code::Cancelled => "The operation was cancelled",
127 Code::Unknown => "Unknown error",
128 Code::InvalidArgument => "Client specified an invalid argument",
129 Code::DeadlineExceeded => "Deadline expired before operation could complete",
130 Code::NotFound => "Some requested entity was not found",
131 Code::AlreadyExists => "Some entity that we attempted to create already exists",
132 Code::PermissionDenied => {
133 "The caller does not have permission to execute the specified operation"
134 }
135 Code::ResourceExhausted => "Some resource has been exhausted",
136 Code::FailedPrecondition => {
137 "The system is not in a state required for the operation's execution"
138 }
139 Code::Aborted => "The operation was aborted",
140 Code::OutOfRange => "Operation was attempted past the valid range",
141 Code::Unimplemented => "Operation is not implemented or not supported",
142 Code::Internal => "Internal error",
143 Code::Unavailable => "The service is currently unavailable",
144 Code::DataLoss => "Unrecoverable data loss or corruption",
145 Code::Unauthenticated => "The request does not have valid authentication credentials",
146 }
147 }
148}
149
150impl std::fmt::Display for Code {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 std::fmt::Display::fmt(self.description(), f)
153 }
154}
155
156impl Status {
159 pub fn new(code: Code, message: impl Into<String>) -> Status {
161 Status {
162 code,
163 message: message.into(),
164 details: Bytes::new(),
165 metadata: MetadataMap::new(),
166 source: None,
167 }
168 }
169
170 pub fn ok(message: impl Into<String>) -> Status {
172 Status::new(Code::Ok, message)
173 }
174
175 pub fn cancelled(message: impl Into<String>) -> Status {
177 Status::new(Code::Cancelled, message)
178 }
179
180 pub fn unknown(message: impl Into<String>) -> Status {
185 Status::new(Code::Unknown, message)
186 }
187
188 pub fn invalid_argument(message: impl Into<String>) -> Status {
193 Status::new(Code::InvalidArgument, message)
194 }
195
196 pub fn deadline_exceeded(message: impl Into<String>) -> Status {
202 Status::new(Code::DeadlineExceeded, message)
203 }
204
205 pub fn not_found(message: impl Into<String>) -> Status {
207 Status::new(Code::NotFound, message)
208 }
209
210 pub fn already_exists(message: impl Into<String>) -> Status {
213 Status::new(Code::AlreadyExists, message)
214 }
215
216 pub fn permission_denied(message: impl Into<String>) -> Status {
222 Status::new(Code::PermissionDenied, message)
223 }
224
225 pub fn resource_exhausted(message: impl Into<String>) -> Status {
228 Status::new(Code::ResourceExhausted, message)
229 }
230
231 pub fn failed_precondition(message: impl Into<String>) -> Status {
246 Status::new(Code::FailedPrecondition, message)
247 }
248
249 pub fn aborted(message: impl Into<String>) -> Status {
255 Status::new(Code::Aborted, message)
256 }
257
258 pub fn out_of_range(message: impl Into<String>) -> Status {
272 Status::new(Code::OutOfRange, message)
273 }
274
275 pub fn unimplemented(message: impl Into<String>) -> Status {
277 Status::new(Code::Unimplemented, message)
278 }
279
280 pub fn internal(message: impl Into<String>) -> Status {
283 Status::new(Code::Internal, message)
284 }
285
286 pub fn unavailable(message: impl Into<String>) -> Status {
292 Status::new(Code::Unavailable, message)
293 }
294
295 pub fn data_loss(message: impl Into<String>) -> Status {
297 Status::new(Code::DataLoss, message)
298 }
299
300 pub fn unauthenticated(message: impl Into<String>) -> Status {
303 Status::new(Code::Unauthenticated, message)
304 }
305
306 #[cfg_attr(not(feature = "transport"), allow(dead_code))]
307 pub(crate) fn from_error_generic(
308 err: impl Into<Box<dyn Error + Send + Sync + 'static>>,
309 ) -> Status {
310 Self::from_error(err.into())
311 }
312
313 #[cfg_attr(not(feature = "transport"), allow(dead_code))]
318 pub fn from_error(err: Box<dyn Error + Send + Sync + 'static>) -> Status {
319 Status::try_from_error(err).unwrap_or_else(|err| {
320 let mut status = Status::new(Code::Unknown, err.to_string());
321 status.source = Some(err);
322 status
323 })
324 }
325
326 pub(crate) fn try_from_error(
327 err: Box<dyn Error + Send + Sync + 'static>,
328 ) -> Result<Status, Box<dyn Error + Send + Sync + 'static>> {
329 let err = match err.downcast::<Status>() {
330 Ok(status) => {
331 return Ok(*status);
332 }
333 Err(err) => err,
334 };
335
336 #[cfg(feature = "transport")]
337 let err = match err.downcast::<h2::Error>() {
338 Ok(h2) => {
339 return Ok(Status::from_h2_error(h2));
340 }
341 Err(err) => err,
342 };
343
344 if let Some(mut status) = find_status_in_source_chain(&*err) {
345 status.source = Some(err);
346 return Ok(status);
347 }
348
349 Err(err)
350 }
351
352 #[cfg(feature = "transport")]
354 fn from_h2_error(err: Box<h2::Error>) -> Status {
355 let code = match err.reason() {
357 Some(h2::Reason::NO_ERROR)
358 | Some(h2::Reason::PROTOCOL_ERROR)
359 | Some(h2::Reason::INTERNAL_ERROR)
360 | Some(h2::Reason::FLOW_CONTROL_ERROR)
361 | Some(h2::Reason::SETTINGS_TIMEOUT)
362 | Some(h2::Reason::COMPRESSION_ERROR)
363 | Some(h2::Reason::CONNECT_ERROR) => Code::Internal,
364 Some(h2::Reason::REFUSED_STREAM) => Code::Unavailable,
365 Some(h2::Reason::CANCEL) => Code::Cancelled,
366 Some(h2::Reason::ENHANCE_YOUR_CALM) => Code::ResourceExhausted,
367 Some(h2::Reason::INADEQUATE_SECURITY) => Code::PermissionDenied,
368
369 _ => Code::Unknown,
370 };
371
372 let mut status = Self::new(code, format!("h2 protocol error: {}", err));
373 status.source = Some(err);
374 status
375 }
376
377 #[cfg(feature = "transport")]
378 fn to_h2_error(&self) -> h2::Error {
379 let reason = match self.code {
381 Code::Cancelled => h2::Reason::CANCEL,
382 _ => h2::Reason::INTERNAL_ERROR,
383 };
384
385 reason.into()
386 }
387
388 #[cfg(feature = "transport")]
394 fn from_hyper_error(err: &hyper::Error) -> Option<Status> {
395 if err.is_timeout() || err.is_connect() {
408 return Some(Status::unavailable(err.to_string()));
409 }
410 None
411 }
412
413 pub(crate) fn map_error<E>(err: E) -> Status
414 where
415 E: Into<Box<dyn Error + Send + Sync>>,
416 {
417 let err: Box<dyn Error + Send + Sync> = err.into();
418 Status::from_error(err)
419 }
420
421 pub fn from_header_map(header_map: &HeaderMap) -> Option<Status> {
423 header_map.get(GRPC_STATUS_HEADER_CODE).map(|code| {
424 let code = Code::from_bytes(code.as_ref());
425 let error_message = header_map
426 .get(GRPC_STATUS_MESSAGE_HEADER)
427 .map(|header| {
428 percent_decode(header.as_bytes())
429 .decode_utf8()
430 .map(|cow| cow.to_string())
431 })
432 .unwrap_or_else(|| Ok(String::new()));
433
434 let details = header_map
435 .get(GRPC_STATUS_DETAILS_HEADER)
436 .map(|h| {
437 base64::decode(h.as_bytes())
438 .expect("Invalid status header, expected base64 encoded value")
439 })
440 .map(Bytes::from)
441 .unwrap_or_else(Bytes::new);
442
443 let mut other_headers = header_map.clone();
444 other_headers.remove(GRPC_STATUS_HEADER_CODE);
445 other_headers.remove(GRPC_STATUS_MESSAGE_HEADER);
446 other_headers.remove(GRPC_STATUS_DETAILS_HEADER);
447
448 match error_message {
449 Ok(message) => Status {
450 code,
451 message,
452 details,
453 metadata: MetadataMap::from_headers(other_headers),
454 source: None,
455 },
456 Err(err) => {
457 warn!("Error deserializing status message header: {}", err);
458 Status {
459 code: Code::Unknown,
460 message: format!("Error deserializing status message header: {}", err),
461 details,
462 metadata: MetadataMap::from_headers(other_headers),
463 source: None,
464 }
465 }
466 }
467 })
468 }
469
470 pub fn code(&self) -> Code {
472 self.code
473 }
474
475 pub fn message(&self) -> &str {
477 &self.message
478 }
479
480 pub fn details(&self) -> &[u8] {
482 &self.details
483 }
484
485 pub fn metadata(&self) -> &MetadataMap {
487 &self.metadata
488 }
489
490 pub fn metadata_mut(&mut self) -> &mut MetadataMap {
492 &mut self.metadata
493 }
494
495 pub(crate) fn to_header_map(&self) -> Result<HeaderMap, Self> {
496 let mut header_map = HeaderMap::with_capacity(3 + self.metadata.len());
497 self.add_header(&mut header_map)?;
498 Ok(header_map)
499 }
500
501 pub(crate) fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
502 header_map.extend(self.metadata.clone().into_sanitized_headers());
503
504 header_map.insert(GRPC_STATUS_HEADER_CODE, self.code.to_header_value());
505
506 if !self.message.is_empty() {
507 let to_write = Bytes::copy_from_slice(
508 Cow::from(percent_encode(self.message().as_bytes(), ENCODING_SET)).as_bytes(),
509 );
510
511 header_map.insert(
512 GRPC_STATUS_MESSAGE_HEADER,
513 HeaderValue::from_maybe_shared(to_write).map_err(invalid_header_value_byte)?,
514 );
515 }
516
517 if !self.details.is_empty() {
518 let details = base64::encode_config(&self.details[..], base64::STANDARD_NO_PAD);
519
520 header_map.insert(
521 GRPC_STATUS_DETAILS_HEADER,
522 HeaderValue::from_maybe_shared(details).map_err(invalid_header_value_byte)?,
523 );
524 }
525
526 Ok(())
527 }
528
529 pub fn with_details(code: Code, message: impl Into<String>, details: Bytes) -> Status {
531 Self::with_details_and_metadata(code, message, details, MetadataMap::new())
532 }
533
534 pub fn with_metadata(code: Code, message: impl Into<String>, metadata: MetadataMap) -> Status {
536 Self::with_details_and_metadata(code, message, Bytes::new(), metadata)
537 }
538
539 pub fn with_details_and_metadata(
541 code: Code,
542 message: impl Into<String>,
543 details: Bytes,
544 metadata: MetadataMap,
545 ) -> Status {
546 Status {
547 code,
548 message: message.into(),
549 details,
550 metadata,
551 source: None,
552 }
553 }
554
555 #[allow(clippy::wrong_self_convention)]
556 pub fn to_http(self) -> http::Response<BoxBody> {
558 let (mut parts, _body) = http::Response::new(()).into_parts();
559
560 parts.headers.insert(
561 http::header::CONTENT_TYPE,
562 http::header::HeaderValue::from_static("application/grpc"),
563 );
564
565 self.add_header(&mut parts.headers).unwrap();
566
567 http::Response::from_parts(parts, crate::body::empty_body())
568 }
569}
570
571fn find_status_in_source_chain(err: &(dyn Error + 'static)) -> Option<Status> {
572 let mut source = Some(err);
573
574 while let Some(err) = source {
575 if let Some(status) = err.downcast_ref::<Status>() {
576 return Some(Status {
577 code: status.code,
578 message: status.message.clone(),
579 details: status.details.clone(),
580 metadata: status.metadata.clone(),
581 source: None,
584 });
585 }
586
587 #[cfg(feature = "transport")]
588 if let Some(timeout) = err.downcast_ref::<crate::transport::TimeoutExpired>() {
589 return Some(Status::cancelled(timeout.to_string()));
590 }
591
592 #[cfg(feature = "transport")]
593 if let Some(hyper) = err
594 .downcast_ref::<hyper::Error>()
595 .and_then(Status::from_hyper_error)
596 {
597 return Some(hyper);
598 }
599
600 source = err.source();
601 }
602
603 None
604}
605
606impl fmt::Debug for Status {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 let mut builder = f.debug_struct("Status");
610
611 builder.field("code", &self.code);
612
613 if !self.message.is_empty() {
614 builder.field("message", &self.message);
615 }
616
617 if !self.details.is_empty() {
618 builder.field("details", &self.details);
619 }
620
621 if !self.metadata.is_empty() {
622 builder.field("metadata", &self.metadata);
623 }
624
625 builder.field("source", &self.source);
626
627 builder.finish()
628 }
629}
630
631fn invalid_header_value_byte<Error: fmt::Display>(err: Error) -> Status {
632 debug!("Invalid header: {}", err);
633 Status::new(
634 Code::Internal,
635 "Couldn't serialize non-text grpc status header".to_string(),
636 )
637}
638
639#[cfg(feature = "transport")]
640impl From<h2::Error> for Status {
641 fn from(err: h2::Error) -> Self {
642 Status::from_h2_error(Box::new(err))
643 }
644}
645
646#[cfg(feature = "transport")]
647impl From<Status> for h2::Error {
648 fn from(status: Status) -> Self {
649 status.to_h2_error()
650 }
651}
652
653impl From<std::io::Error> for Status {
654 fn from(err: std::io::Error) -> Self {
655 use std::io::ErrorKind;
656 let code = match err.kind() {
657 ErrorKind::BrokenPipe
658 | ErrorKind::WouldBlock
659 | ErrorKind::WriteZero
660 | ErrorKind::Interrupted => Code::Internal,
661 ErrorKind::ConnectionRefused
662 | ErrorKind::ConnectionReset
663 | ErrorKind::NotConnected
664 | ErrorKind::AddrInUse
665 | ErrorKind::AddrNotAvailable => Code::Unavailable,
666 ErrorKind::AlreadyExists => Code::AlreadyExists,
667 ErrorKind::ConnectionAborted => Code::Aborted,
668 ErrorKind::InvalidData => Code::DataLoss,
669 ErrorKind::InvalidInput => Code::InvalidArgument,
670 ErrorKind::NotFound => Code::NotFound,
671 ErrorKind::PermissionDenied => Code::PermissionDenied,
672 ErrorKind::TimedOut => Code::DeadlineExceeded,
673 ErrorKind::UnexpectedEof => Code::OutOfRange,
674 _ => Code::Unknown,
675 };
676 Status::new(code, err.to_string())
677 }
678}
679
680impl fmt::Display for Status {
681 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
682 write!(
683 f,
684 "status: {:?}, message: {:?}, details: {:?}, metadata: {:?}",
685 self.code(),
686 self.message(),
687 self.details(),
688 self.metadata(),
689 )
690 }
691}
692
693impl Error for Status {
694 fn source(&self) -> Option<&(dyn Error + 'static)> {
695 self.source.as_ref().map(|err| (&**err) as _)
696 }
697}
698
699pub(crate) fn infer_grpc_status(
703 trailers: Option<&HeaderMap>,
704 status_code: http::StatusCode,
705) -> Result<(), Option<Status>> {
706 if let Some(trailers) = trailers {
707 if let Some(status) = Status::from_header_map(trailers) {
708 if status.code() == Code::Ok {
709 return Ok(());
710 } else {
711 return Err(status.into());
712 }
713 }
714 }
715 trace!("trailers missing grpc-status");
716 let code = match status_code {
717 http::StatusCode::BAD_REQUEST => Code::Internal,
719 http::StatusCode::UNAUTHORIZED => Code::Unauthenticated,
720 http::StatusCode::FORBIDDEN => Code::PermissionDenied,
721 http::StatusCode::NOT_FOUND => Code::Unimplemented,
722 http::StatusCode::TOO_MANY_REQUESTS
723 | http::StatusCode::BAD_GATEWAY
724 | http::StatusCode::SERVICE_UNAVAILABLE
725 | http::StatusCode::GATEWAY_TIMEOUT => Code::Unavailable,
726 http::StatusCode::OK => return Err(None),
733 _ => Code::Unknown,
734 };
735
736 let msg = format!(
737 "grpc-status header missing, mapped from HTTP status code {}",
738 status_code.as_u16(),
739 );
740 let status = Status::new(code, msg);
741 Err(status.into())
742}
743
744impl Code {
747 pub fn from_i32(i: i32) -> Code {
751 Code::from(i)
752 }
753
754 pub fn from_bytes(bytes: &[u8]) -> Code {
758 match bytes.len() {
759 1 => match bytes[0] {
760 b'0' => Code::Ok,
761 b'1' => Code::Cancelled,
762 b'2' => Code::Unknown,
763 b'3' => Code::InvalidArgument,
764 b'4' => Code::DeadlineExceeded,
765 b'5' => Code::NotFound,
766 b'6' => Code::AlreadyExists,
767 b'7' => Code::PermissionDenied,
768 b'8' => Code::ResourceExhausted,
769 b'9' => Code::FailedPrecondition,
770 _ => Code::parse_err(),
771 },
772 2 => match (bytes[0], bytes[1]) {
773 (b'1', b'0') => Code::Aborted,
774 (b'1', b'1') => Code::OutOfRange,
775 (b'1', b'2') => Code::Unimplemented,
776 (b'1', b'3') => Code::Internal,
777 (b'1', b'4') => Code::Unavailable,
778 (b'1', b'5') => Code::DataLoss,
779 (b'1', b'6') => Code::Unauthenticated,
780 _ => Code::parse_err(),
781 },
782 _ => Code::parse_err(),
783 }
784 }
785
786 fn to_header_value(self) -> HeaderValue {
787 match self {
788 Code::Ok => HeaderValue::from_static("0"),
789 Code::Cancelled => HeaderValue::from_static("1"),
790 Code::Unknown => HeaderValue::from_static("2"),
791 Code::InvalidArgument => HeaderValue::from_static("3"),
792 Code::DeadlineExceeded => HeaderValue::from_static("4"),
793 Code::NotFound => HeaderValue::from_static("5"),
794 Code::AlreadyExists => HeaderValue::from_static("6"),
795 Code::PermissionDenied => HeaderValue::from_static("7"),
796 Code::ResourceExhausted => HeaderValue::from_static("8"),
797 Code::FailedPrecondition => HeaderValue::from_static("9"),
798 Code::Aborted => HeaderValue::from_static("10"),
799 Code::OutOfRange => HeaderValue::from_static("11"),
800 Code::Unimplemented => HeaderValue::from_static("12"),
801 Code::Internal => HeaderValue::from_static("13"),
802 Code::Unavailable => HeaderValue::from_static("14"),
803 Code::DataLoss => HeaderValue::from_static("15"),
804 Code::Unauthenticated => HeaderValue::from_static("16"),
805 }
806 }
807
808 fn parse_err() -> Code {
809 trace!("error parsing grpc-status");
810 Code::Unknown
811 }
812}
813
814impl From<i32> for Code {
815 fn from(i: i32) -> Self {
816 match i {
817 0 => Code::Ok,
818 1 => Code::Cancelled,
819 2 => Code::Unknown,
820 3 => Code::InvalidArgument,
821 4 => Code::DeadlineExceeded,
822 5 => Code::NotFound,
823 6 => Code::AlreadyExists,
824 7 => Code::PermissionDenied,
825 8 => Code::ResourceExhausted,
826 9 => Code::FailedPrecondition,
827 10 => Code::Aborted,
828 11 => Code::OutOfRange,
829 12 => Code::Unimplemented,
830 13 => Code::Internal,
831 14 => Code::Unavailable,
832 15 => Code::DataLoss,
833 16 => Code::Unauthenticated,
834
835 _ => Code::Unknown,
836 }
837 }
838}
839
840impl From<Code> for i32 {
841 #[inline]
842 fn from(code: Code) -> i32 {
843 code as i32
844 }
845}
846
847#[cfg(test)]
848mod tests {
849 use super::*;
850 use crate::Error;
851
852 #[derive(Debug)]
853 struct Nested(Error);
854
855 impl fmt::Display for Nested {
856 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
857 write!(f, "nested error: {}", self.0)
858 }
859 }
860
861 impl std::error::Error for Nested {
862 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
863 Some(&*self.0)
864 }
865 }
866
867 #[test]
868 fn from_error_status() {
869 let orig = Status::new(Code::OutOfRange, "weeaboo");
870 let found = Status::from_error(Box::new(orig));
871
872 assert_eq!(found.code(), Code::OutOfRange);
873 assert_eq!(found.message(), "weeaboo");
874 }
875
876 #[test]
877 fn from_error_unknown() {
878 let orig: Error = "peek-a-boo".into();
879 let found = Status::from_error(orig);
880
881 assert_eq!(found.code(), Code::Unknown);
882 assert_eq!(found.message(), "peek-a-boo".to_string());
883 }
884
885 #[test]
886 fn from_error_nested() {
887 let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
888 let found = Status::from_error(Box::new(orig));
889
890 assert_eq!(found.code(), Code::OutOfRange);
891 assert_eq!(found.message(), "weeaboo");
892 }
893
894 #[test]
895 #[cfg(feature = "transport")]
896 fn from_error_h2() {
897 use std::error::Error as _;
898
899 let orig = h2::Error::from(h2::Reason::CANCEL);
900 let found = Status::from_error(Box::new(orig));
901
902 assert_eq!(found.code(), Code::Cancelled);
903
904 let source = found
905 .source()
906 .and_then(|err| err.downcast_ref::<h2::Error>())
907 .unwrap();
908 assert_eq!(source.reason(), Some(h2::Reason::CANCEL));
909 }
910
911 #[test]
912 #[cfg(feature = "transport")]
913 fn to_h2_error() {
914 let orig = Status::new(Code::Cancelled, "stop eet!");
915 let err = orig.to_h2_error();
916
917 assert_eq!(err.reason(), Some(h2::Reason::CANCEL));
918 }
919
920 #[test]
921 fn code_from_i32() {
922 for i in 0..(Code::Unauthenticated as i32) {
925 let code = Code::from(i);
926 assert_eq!(
927 i, code as i32,
928 "Code::from({}) returned {:?} which is {}",
929 i, code, code as i32,
930 );
931 }
932
933 assert_eq!(Code::from(-1), Code::Unknown);
934 }
935
936 #[test]
937 fn constructors() {
938 assert_eq!(Status::ok("").code(), Code::Ok);
939 assert_eq!(Status::cancelled("").code(), Code::Cancelled);
940 assert_eq!(Status::unknown("").code(), Code::Unknown);
941 assert_eq!(Status::invalid_argument("").code(), Code::InvalidArgument);
942 assert_eq!(Status::deadline_exceeded("").code(), Code::DeadlineExceeded);
943 assert_eq!(Status::not_found("").code(), Code::NotFound);
944 assert_eq!(Status::already_exists("").code(), Code::AlreadyExists);
945 assert_eq!(Status::permission_denied("").code(), Code::PermissionDenied);
946 assert_eq!(
947 Status::resource_exhausted("").code(),
948 Code::ResourceExhausted
949 );
950 assert_eq!(
951 Status::failed_precondition("").code(),
952 Code::FailedPrecondition
953 );
954 assert_eq!(Status::aborted("").code(), Code::Aborted);
955 assert_eq!(Status::out_of_range("").code(), Code::OutOfRange);
956 assert_eq!(Status::unimplemented("").code(), Code::Unimplemented);
957 assert_eq!(Status::internal("").code(), Code::Internal);
958 assert_eq!(Status::unavailable("").code(), Code::Unavailable);
959 assert_eq!(Status::data_loss("").code(), Code::DataLoss);
960 assert_eq!(Status::unauthenticated("").code(), Code::Unauthenticated);
961 }
962
963 #[test]
964 fn details() {
965 const DETAILS: &[u8] = &[0, 2, 3];
966
967 let status = Status::with_details(Code::Unavailable, "some message", DETAILS.into());
968
969 assert_eq!(status.details(), DETAILS);
970
971 let header_map = status.to_header_map().unwrap();
972
973 let b64_details = base64::encode_config(DETAILS, base64::STANDARD_NO_PAD);
974
975 assert_eq!(header_map[super::GRPC_STATUS_DETAILS_HEADER], b64_details);
976
977 let status = Status::from_header_map(&header_map).unwrap();
978
979 assert_eq!(status.details(), DETAILS);
980 }
981}