1use std::{borrow::Cow, error::Error, fmt, sync::Arc};
4
5use base64::Engine;
6use bytes::Bytes;
7use http::header::{HeaderMap, HeaderValue};
8use percent_encoding::{AsciiSet, CONTROLS, percent_decode, percent_encode};
9use tower::BoxError;
10use tracing::{debug, trace, warn};
11use volo::loadbalance::error::{LoadBalanceError, Retryable};
12
13use crate::{BASE64_ENGINE, body::BoxBody, metadata::MetadataMap};
14
15const ENCODING_SET: &AsciiSet = &CONTROLS
16 .add(b' ')
17 .add(b'"')
18 .add(b'#')
19 .add(b'<')
20 .add(b'>')
21 .add(b'`')
22 .add(b'?')
23 .add(b'{')
24 .add(b'}');
25
26const GRPC_STATUS_HEADER_CODE: &str = "grpc-status";
27const GRPC_STATUS_MESSAGE_HEADER: &str = "grpc-message";
28const GRPC_STATUS_DETAILS_HEADER: &str = "grpc-status-details-bin";
29
30#[derive(Clone)]
43pub struct Status {
44 code: Code,
46 message: String,
48 details: Bytes,
50 metadata: MetadataMap,
54 source: Option<Arc<dyn Error + Send + Sync + 'static>>,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
64pub enum Code {
65 Ok = 0,
67
68 Cancelled = 1,
70
71 Unknown = 2,
73
74 InvalidArgument = 3,
76
77 DeadlineExceeded = 4,
79
80 NotFound = 5,
82
83 AlreadyExists = 6,
85
86 PermissionDenied = 7,
88
89 ResourceExhausted = 8,
91
92 FailedPrecondition = 9,
94
95 Aborted = 10,
97
98 OutOfRange = 11,
100
101 Unimplemented = 12,
103
104 Internal = 13,
106
107 Unavailable = 14,
109
110 DataLoss = 15,
112
113 Unauthenticated = 16,
115}
116
117impl Code {
118 pub fn description(&self) -> &'static str {
134 match self {
135 Self::Ok => "The operation completed successfully",
136 Self::Cancelled => "The operation was cancelled",
137 Self::Unknown => "Unknown error",
138 Self::InvalidArgument => "Client specified an invalid argument",
139 Self::DeadlineExceeded => "Deadline expired before operation could complete",
140 Self::NotFound => "Some requested entity was not found",
141 Self::AlreadyExists => "Some entity that we attempted to create already exists",
142 Self::PermissionDenied => {
143 "The caller does not have permission to execute the specified operation"
144 }
145 Self::ResourceExhausted => "Some resource has been exhausted",
146 Self::FailedPrecondition => {
147 "The system is not in a state required for the operation's execution"
148 }
149 Self::Aborted => "The operation was aborted",
150 Self::OutOfRange => "Operation was attempted past the valid range",
151 Self::Unimplemented => "Operation is not implemented or not supported",
152 Self::Internal => "Internal error",
153 Self::Unavailable => "The service is currently unavailable",
154 Self::DataLoss => "Unrecoverable data loss or corruption",
155 Self::Unauthenticated => "The request does not have valid authentication credentials",
156 }
157 }
158}
159
160impl fmt::Display for Code {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 std::fmt::Display::fmt(self.description(), f)
163 }
164}
165
166impl Status {
167 pub fn boxed(self) -> BoxError {
168 Box::new(self)
169 }
170
171 pub fn new(code: Code, message: impl Into<String>) -> Self {
173 Self {
174 code,
175 message: message.into(),
176 details: Bytes::new(),
177 metadata: MetadataMap::new(),
178 source: None,
179 }
180 }
181
182 pub fn ok(message: impl Into<String>) -> Self {
184 Self::new(Code::Ok, message)
185 }
186
187 pub fn cancelled(message: impl Into<String>) -> Self {
189 Self::new(Code::Cancelled, message)
190 }
191
192 pub fn unknown(message: impl Into<String>) -> Self {
197 Self::new(Code::Unknown, message)
198 }
199
200 pub fn invalid_argument(message: impl Into<String>) -> Self {
205 Self::new(Code::InvalidArgument, message)
206 }
207
208 pub fn deadline_exceeded(message: impl Into<String>) -> Self {
214 Self::new(Code::DeadlineExceeded, message)
215 }
216
217 pub fn not_found(message: impl Into<String>) -> Self {
219 Self::new(Code::NotFound, message)
220 }
221
222 pub fn already_exists(message: impl Into<String>) -> Self {
225 Self::new(Code::AlreadyExists, message)
226 }
227
228 pub fn permission_denied(message: impl Into<String>) -> Self {
236 Self::new(Code::PermissionDenied, message)
237 }
238
239 pub fn resource_exhausted(message: impl Into<String>) -> Self {
243 Self::new(Code::ResourceExhausted, message)
244 }
245
246 pub fn failed_precondition(message: impl Into<String>) -> Self {
261 Self::new(Code::FailedPrecondition, message)
262 }
263
264 pub fn aborted(message: impl Into<String>) -> Self {
270 Self::new(Code::Aborted, message)
271 }
272
273 pub fn out_of_range(message: impl Into<String>) -> Self {
287 Self::new(Code::OutOfRange, message)
288 }
289
290 pub fn unimplemented(message: impl Into<String>) -> Self {
292 Self::new(Code::Unimplemented, message)
293 }
294
295 pub fn internal(message: impl Into<String>) -> Self {
298 Self::new(Code::Internal, message)
299 }
300
301 pub fn unavailable(message: impl Into<String>) -> Self {
307 Self::new(Code::Unavailable, message)
308 }
309
310 pub fn data_loss(message: impl Into<String>) -> Self {
312 Self::new(Code::DataLoss, message)
313 }
314
315 pub fn unauthenticated(message: impl Into<String>) -> Self {
318 Self::new(Code::Unauthenticated, message)
319 }
320
321 pub fn from_error(err: BoxError) -> Self {
324 Self::try_from_error(err).unwrap_or_else(|err| {
325 let mut status = Self::new(Code::Unknown, err.to_string());
326 status.source = Some(err.into());
327 status
328 })
329 }
330
331 pub fn try_from_error(err: BoxError) -> Result<Self, BoxError> {
332 let err = match err.downcast::<Self>() {
333 Ok(status) => {
334 return Ok(*status);
335 }
336 Err(err) => err,
337 };
338
339 let err = match err.downcast::<h2::Error>() {
340 Ok(h2) => {
341 return Ok(Self::from_h2_error(h2));
342 }
343 Err(err) => err,
344 };
345
346 if let Some(status) = find_status_in_source_chain(&*err) {
347 return Ok(status);
348 }
349
350 Err(err)
351 }
352
353 pub fn from_h2_error(err: Box<h2::Error>) -> Self {
356 let code = Self::code_from_h2(&err);
357
358 let mut status = Self::new(code, format!("h2 protocol error: {err}"));
359 status.source = Some(Arc::new(*err));
360 status
361 }
362
363 fn code_from_h2(err: &h2::Error) -> Code {
364 match err.reason() {
366 Some(h2::Reason::NO_ERROR)
367 | Some(h2::Reason::PROTOCOL_ERROR)
368 | Some(h2::Reason::INTERNAL_ERROR)
369 | Some(h2::Reason::FLOW_CONTROL_ERROR)
370 | Some(h2::Reason::SETTINGS_TIMEOUT)
371 | Some(h2::Reason::COMPRESSION_ERROR)
372 | Some(h2::Reason::CONNECT_ERROR) => Code::Internal,
373 Some(h2::Reason::REFUSED_STREAM) => Code::Unavailable,
374 Some(h2::Reason::CANCEL) => Code::Cancelled,
375 Some(h2::Reason::ENHANCE_YOUR_CALM) => Code::ResourceExhausted,
376 Some(h2::Reason::INADEQUATE_SECURITY) => Code::PermissionDenied,
377
378 _ => Code::Unknown,
379 }
380 }
381
382 pub fn to_h2_error(&self) -> h2::Error {
383 let reason = match self.code {
384 Code::Cancelled => h2::Reason::CANCEL,
385 _ => h2::Reason::INTERNAL_ERROR,
386 };
387
388 reason.into()
389 }
390
391 pub fn from_hyper_error(err: &hyper::Error) -> Option<Self> {
397 if err.is_timeout() {
410 return Some(Self::unavailable(err.to_string()));
411 }
412 if let Some(h2_err) = err.source().and_then(|e| e.downcast_ref::<h2::Error>()) {
413 let code = Self::code_from_h2(h2_err);
414 let status = Self::new(code, format!("h2 protocol error: {err}"));
415
416 return Some(status);
417 }
418 None
419 }
420
421 pub fn map_error<E>(err: E) -> Self
422 where
423 E: Into<Box<dyn Error + Send + Sync>>,
424 {
425 let err: Box<dyn Error + Send + Sync> = err.into();
426 Self::from_error(err)
427 }
428
429 pub fn from_header_map(header_map: &HeaderMap) -> Option<Self> {
431 header_map.get(GRPC_STATUS_HEADER_CODE).map(|code| {
432 let code = Code::from_bytes(code.as_ref());
434 let error_message = header_map
436 .get(GRPC_STATUS_MESSAGE_HEADER)
437 .map(|header| {
438 percent_decode(header.as_bytes())
439 .decode_utf8()
440 .map(|cow| cow.to_string())
441 })
442 .unwrap_or_else(|| Ok(String::new()));
443
444 let details = header_map
446 .get(GRPC_STATUS_DETAILS_HEADER)
447 .map(|h| {
448 BASE64_ENGINE
449 .decode(h.as_bytes())
450 .expect("Invalid status header, expected base64 encoded value")
451 })
452 .map(Bytes::from)
453 .unwrap_or_default();
454
455 let mut other_headers = header_map.clone();
457 other_headers.remove(GRPC_STATUS_HEADER_CODE);
458 other_headers.remove(GRPC_STATUS_MESSAGE_HEADER);
459 other_headers.remove(GRPC_STATUS_DETAILS_HEADER);
460
461 match error_message {
463 Ok(message) => Self {
464 code,
465 message,
466 details,
467 metadata: MetadataMap::from_headers(other_headers),
468 source: None,
469 },
470 Err(err) => {
471 warn!("[VOLO] Error deserializing status message header: {}", err);
472 Self {
473 code: Code::Unknown,
474 message: format!("Error deserializing status message header: {err}"),
475 details,
476 metadata: MetadataMap::from_headers(other_headers),
477 source: None,
478 }
479 }
480 }
481 })
482 }
483
484 #[allow(clippy::result_large_err)]
486 pub fn infer_grpc_status(
487 trailers: Option<&HeaderMap>,
488 status_code: http::StatusCode,
489 ) -> Result<(), Option<Self>> {
490 if let Some(trailers) = trailers {
491 if let Some(status) = Self::from_header_map(trailers) {
492 return if status.code() == Code::Ok {
493 Ok(())
494 } else {
495 Err(status.into())
496 };
497 }
498 }
499 trace!("[VOLO] trailers missing grpc-status");
500 let code = match status_code {
501 http::StatusCode::BAD_REQUEST => Code::Internal,
502 http::StatusCode::UNAUTHORIZED => Code::Unauthenticated,
503 http::StatusCode::FORBIDDEN => Code::PermissionDenied,
504 http::StatusCode::NOT_FOUND => Code::Unimplemented,
505 http::StatusCode::TOO_MANY_REQUESTS
506 | http::StatusCode::BAD_GATEWAY
507 | http::StatusCode::SERVICE_UNAVAILABLE
508 | http::StatusCode::GATEWAY_TIMEOUT => Code::Unavailable,
509 http::StatusCode::OK => return Err(None),
510 _ => Code::Unknown,
511 };
512
513 let msg = format!(
514 "grpc-status header missing, mapped from HTTP status code {}",
515 status_code.as_u16(),
516 );
517 let status = Self::new(code, msg);
518 Err(status.into())
519 }
520
521 pub fn code(&self) -> Code {
523 self.code
524 }
525
526 pub fn is_ok(&self) -> bool {
528 self.code == Code::Ok
529 }
530
531 pub fn message(&self) -> &str {
533 &self.message
534 }
535
536 pub fn details(&self) -> &[u8] {
538 &self.details
539 }
540
541 pub fn metadata(&self) -> &MetadataMap {
543 &self.metadata
544 }
545
546 pub fn metadata_mut(&mut self) -> &mut MetadataMap {
548 &mut self.metadata
549 }
550
551 #[allow(clippy::result_large_err)]
553 pub fn to_header_map(&self) -> Result<HeaderMap, Self> {
554 let mut header_map = HeaderMap::with_capacity(3 + self.metadata.len());
555 self.add_header(&mut header_map)?;
556 Ok(header_map)
557 }
558
559 #[allow(clippy::result_large_err)]
561 pub(crate) fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
562 header_map.extend(self.metadata.clone().into_sanitized_headers());
563
564 header_map.insert(GRPC_STATUS_HEADER_CODE, self.code.to_header_value());
566
567 if !self.message.is_empty() {
569 let to_write = Bytes::copy_from_slice(
570 Cow::from(percent_encode(self.message().as_bytes(), ENCODING_SET)).as_bytes(),
571 );
572
573 header_map.insert(
574 GRPC_STATUS_MESSAGE_HEADER,
575 HeaderValue::from_maybe_shared(to_write).map_err(invalid_header_value_byte)?,
576 );
577 }
578
579 if !self.details.is_empty() {
581 let details = BASE64_ENGINE.encode(&self.details[..]);
582
583 header_map.insert(
584 GRPC_STATUS_DETAILS_HEADER,
585 HeaderValue::from_maybe_shared(details).map_err(invalid_header_value_byte)?,
586 );
587 }
588
589 Ok(())
590 }
591
592 pub fn with_details(code: Code, message: impl Into<String>, details: Bytes) -> Self {
594 Self::with_details_and_metadata(code, message, details, MetadataMap::new())
595 }
596
597 pub fn with_metadata(code: Code, message: impl Into<String>, metadata: MetadataMap) -> Self {
599 Self::with_details_and_metadata(code, message, Bytes::new(), metadata)
600 }
601
602 pub fn with_details_and_metadata(
605 code: Code,
606 message: impl Into<String>,
607 details: Bytes,
608 metadata: MetadataMap,
609 ) -> Self {
610 Self {
611 code,
612 message: message.into(),
613 details,
614 metadata,
615 source: None,
616 }
617 }
618
619 #[allow(clippy::wrong_self_convention)]
621 pub fn to_http(self) -> http::Response<BoxBody> {
622 let mut response = http::Response::new(crate::body::empty_body());
623 response.headers_mut().insert(
624 http::header::CONTENT_TYPE,
625 HeaderValue::from_static("application/grpc"),
626 );
627 self.add_header(response.headers_mut()).unwrap();
628 response
629 }
630}
631
632fn find_status_in_source_chain(err: &(dyn Error + 'static)) -> Option<Status> {
633 let mut source = Some(err);
634
635 while let Some(err) = source {
636 if let Some(status) = err.downcast_ref::<Status>() {
637 return Some(Status {
638 code: status.code,
639 message: status.message.clone(),
640 details: status.details.clone(),
641 metadata: status.metadata.clone(),
642 source: None,
643 });
644 }
645
646 if let Some(hyper) = err
647 .downcast_ref::<hyper::Error>()
648 .and_then(Status::from_hyper_error)
649 {
650 return Some(hyper);
651 }
652
653 source = err.source();
654 }
655
656 None
657}
658
659impl fmt::Debug for Status {
660 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
661 let mut builder = f.debug_struct("Status");
663
664 builder.field("code", &self.code);
665
666 if !self.message.is_empty() {
667 builder.field("message", &self.message);
668 }
669
670 if !self.details.is_empty() {
671 builder.field("details", &self.details);
672 }
673
674 if !self.metadata.is_empty() {
675 builder.field("metadata", &self.metadata);
676 }
677
678 builder.field("source", &self.source);
679
680 builder.finish()
681 }
682}
683
684fn invalid_header_value_byte<Error: fmt::Display>(err: Error) -> Status {
685 debug!("[VOLO] Invalid header: {}", err);
686 Status::new(
687 Code::Internal,
688 "Couldn't serialize non-text grpc status header".to_string(),
689 )
690}
691
692impl From<h2::Error> for Status {
693 fn from(err: h2::Error) -> Self {
694 Self::from_h2_error(Box::new(err))
695 }
696}
697
698impl From<Status> for h2::Error {
699 fn from(status: Status) -> Self {
700 status.to_h2_error()
701 }
702}
703
704impl From<std::io::Error> for Status {
705 fn from(err: std::io::Error) -> Self {
706 use std::io::ErrorKind;
707 let code = match err.kind() {
708 ErrorKind::BrokenPipe
709 | ErrorKind::WouldBlock
710 | ErrorKind::WriteZero
711 | ErrorKind::Interrupted => Code::Internal,
712 ErrorKind::ConnectionRefused
713 | ErrorKind::ConnectionReset
714 | ErrorKind::NotConnected
715 | ErrorKind::AddrInUse
716 | ErrorKind::AddrNotAvailable => Code::Unavailable,
717 ErrorKind::AlreadyExists => Code::AlreadyExists,
718 ErrorKind::ConnectionAborted => Code::Aborted,
719 ErrorKind::InvalidData => Code::DataLoss,
720 ErrorKind::InvalidInput => Code::InvalidArgument,
721 ErrorKind::NotFound => Code::NotFound,
722 ErrorKind::PermissionDenied => Code::PermissionDenied,
723 ErrorKind::TimedOut => Code::DeadlineExceeded,
724 ErrorKind::UnexpectedEof => Code::OutOfRange,
725 _ => Code::Unknown,
726 };
727 Self::new(code, err.to_string())
728 }
729}
730
731impl From<http::header::ToStrError> for Status {
732 fn from(err: http::header::ToStrError) -> Self {
733 Self::invalid_argument(err.to_string())
734 }
735}
736
737impl From<crate::metadata::errors::InvalidMetadataKey> for Status {
738 fn from(err: crate::metadata::errors::InvalidMetadataKey) -> Self {
739 Self::invalid_argument(err.to_string())
740 }
741}
742
743impl From<crate::metadata::errors::InvalidMetadataValue> for Status {
744 fn from(err: crate::metadata::errors::InvalidMetadataValue) -> Self {
745 Self::invalid_argument(err.to_string())
746 }
747}
748
749impl From<crate::metadata::errors::ToStrError> for Status {
750 fn from(err: crate::metadata::errors::ToStrError) -> Self {
751 Self::invalid_argument(err.to_string())
752 }
753}
754
755impl From<BoxError> for Status {
756 fn from(err: BoxError) -> Self {
757 Self::from_error(err)
758 }
759}
760
761impl From<LoadBalanceError> for Status {
762 fn from(err: LoadBalanceError) -> Self {
763 Self::unknown(err.to_string())
764 }
765}
766
767impl From<anyhow::Error> for Status {
768 fn from(err: anyhow::Error) -> Self {
769 Self::from_error(err.into())
770 }
771}
772
773impl Retryable for Status {
774 fn retryable(&self) -> bool {
775 matches!(
776 self.code,
777 Code::Internal | Code::Unavailable | Code::Cancelled | Code::ResourceExhausted
778 )
779 }
780}
781
782impl fmt::Display for Status {
783 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
784 write!(
785 f,
786 "status: {:?}, message: {:?}, details: {:?}, metadata: {:?}",
787 self.code(),
788 self.message(),
789 self.details(),
790 self.metadata(),
791 )
792 }
793}
794
795impl Error for Status {
796 fn source(&self) -> Option<&(dyn Error + 'static)> {
797 self.source.as_ref().map(|err| (&**err) as _)
798 }
799}
800
801impl Code {
802 pub fn from_i32(i: i32) -> Self {
806 Self::from(i)
807 }
808
809 pub fn from_bytes(bytes: &[u8]) -> Self {
813 match bytes.len() {
814 1 => match bytes[0] {
815 b'0' => Self::Ok,
816 b'1' => Self::Cancelled,
817 b'2' => Self::Unknown,
818 b'3' => Self::InvalidArgument,
819 b'4' => Self::DeadlineExceeded,
820 b'5' => Self::NotFound,
821 b'6' => Self::AlreadyExists,
822 b'7' => Self::PermissionDenied,
823 b'8' => Self::ResourceExhausted,
824 b'9' => Self::FailedPrecondition,
825 _ => Self::parse_err(),
826 },
827 2 => match (bytes[0], bytes[1]) {
828 (b'1', b'0') => Self::Aborted,
829 (b'1', b'1') => Self::OutOfRange,
830 (b'1', b'2') => Self::Unimplemented,
831 (b'1', b'3') => Self::Internal,
832 (b'1', b'4') => Self::Unavailable,
833 (b'1', b'5') => Self::DataLoss,
834 (b'1', b'6') => Self::Unauthenticated,
835 _ => Self::parse_err(),
836 },
837 _ => Self::parse_err(),
838 }
839 }
840
841 fn to_header_value(self) -> HeaderValue {
842 match self {
843 Self::Ok => HeaderValue::from_static("0"),
844 Self::Cancelled => HeaderValue::from_static("1"),
845 Self::Unknown => HeaderValue::from_static("2"),
846 Self::InvalidArgument => HeaderValue::from_static("3"),
847 Self::DeadlineExceeded => HeaderValue::from_static("4"),
848 Self::NotFound => HeaderValue::from_static("5"),
849 Self::AlreadyExists => HeaderValue::from_static("6"),
850 Self::PermissionDenied => HeaderValue::from_static("7"),
851 Self::ResourceExhausted => HeaderValue::from_static("8"),
852 Self::FailedPrecondition => HeaderValue::from_static("9"),
853 Self::Aborted => HeaderValue::from_static("10"),
854 Self::OutOfRange => HeaderValue::from_static("11"),
855 Self::Unimplemented => HeaderValue::from_static("12"),
856 Self::Internal => HeaderValue::from_static("13"),
857 Self::Unavailable => HeaderValue::from_static("14"),
858 Self::DataLoss => HeaderValue::from_static("15"),
859 Self::Unauthenticated => HeaderValue::from_static("16"),
860 }
861 }
862
863 fn parse_err() -> Self {
864 trace!("[VOLO] error parsing grpc-status");
865 Self::Unknown
866 }
867}
868
869impl From<i32> for Code {
870 fn from(i: i32) -> Self {
871 match i {
872 0 => Self::Ok,
873 1 => Self::Cancelled,
874 2 => Self::Unknown,
875 3 => Self::InvalidArgument,
876 4 => Self::DeadlineExceeded,
877 5 => Self::NotFound,
878 6 => Self::AlreadyExists,
879 7 => Self::PermissionDenied,
880 8 => Self::ResourceExhausted,
881 9 => Self::FailedPrecondition,
882 10 => Self::Aborted,
883 11 => Self::OutOfRange,
884 12 => Self::Unimplemented,
885 13 => Self::Internal,
886 14 => Self::Unavailable,
887 15 => Self::DataLoss,
888 16 => Self::Unauthenticated,
889
890 _ => Self::Unknown,
891 }
892 }
893}
894
895impl From<Code> for i32 {
896 #[inline]
897 fn from(code: Code) -> i32 {
898 code as i32
899 }
900}
901
902#[cfg(test)]
903mod tests {
904 use super::*;
905 use crate::BoxError as Error;
906
907 #[derive(Debug)]
908 struct Nested(Error);
909
910 impl fmt::Display for Nested {
911 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
912 write!(f, "nested error: {}", self.0)
913 }
914 }
915
916 impl std::error::Error for Nested {
917 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
918 Some(&*self.0)
919 }
920 }
921
922 #[test]
923 fn from_error_status() {
924 let orig = Status::new(Code::OutOfRange, "weeaboo");
925 let found = Status::from_error(Box::new(orig));
926
927 assert_eq!(found.code(), Code::OutOfRange);
928 assert_eq!(found.message(), "weeaboo");
929 }
930
931 #[test]
932 fn from_error_unknown() {
933 let orig: Error = "peek-a-boo".into();
934 let found = Status::from_error(orig);
935
936 assert_eq!(found.code(), Code::Unknown);
937 assert_eq!(found.message(), "peek-a-boo".to_string());
938 }
939
940 #[test]
941 fn from_error_nested() {
942 let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
943 let found = Status::from_error(Box::new(orig));
944
945 assert_eq!(found.code(), Code::OutOfRange);
946 assert_eq!(found.message(), "weeaboo");
947 }
948
949 #[test]
950 fn from_error_h2() {
951 use std::error::Error as _;
952
953 let orig = h2::Error::from(h2::Reason::CANCEL);
954 let found = Status::from_error(Box::new(orig));
955
956 assert_eq!(found.code(), Code::Cancelled);
957
958 let source = found
959 .source()
960 .and_then(|err| err.downcast_ref::<h2::Error>())
961 .unwrap();
962 assert_eq!(source.reason(), Some(h2::Reason::CANCEL));
963 }
964
965 #[test]
966 fn to_h2_error() {
967 let orig = Status::new(Code::Cancelled, "stop eet!");
968 let err = orig.to_h2_error();
969
970 assert_eq!(err.reason(), Some(h2::Reason::CANCEL));
971 }
972
973 #[test]
974 fn code_from_i32() {
975 for i in 0..(Code::Unauthenticated as i32) {
978 let code = Code::from(i);
979 assert_eq!(
980 i, code as i32,
981 "Code::from({}) returned {:?} which is {}",
982 i, code, code as i32,
983 );
984 }
985
986 assert_eq!(Code::from(-1), Code::Unknown);
987 }
988
989 #[test]
990 fn constructors() {
991 assert_eq!(Status::ok("").code(), Code::Ok);
992 assert_eq!(Status::cancelled("").code(), Code::Cancelled);
993 assert_eq!(Status::unknown("").code(), Code::Unknown);
994 assert_eq!(Status::invalid_argument("").code(), Code::InvalidArgument);
995 assert_eq!(Status::deadline_exceeded("").code(), Code::DeadlineExceeded);
996 assert_eq!(Status::not_found("").code(), Code::NotFound);
997 assert_eq!(Status::already_exists("").code(), Code::AlreadyExists);
998 assert_eq!(Status::permission_denied("").code(), Code::PermissionDenied);
999 assert_eq!(
1000 Status::resource_exhausted("").code(),
1001 Code::ResourceExhausted
1002 );
1003 assert_eq!(
1004 Status::failed_precondition("").code(),
1005 Code::FailedPrecondition
1006 );
1007 assert_eq!(Status::aborted("").code(), Code::Aborted);
1008 assert_eq!(Status::out_of_range("").code(), Code::OutOfRange);
1009 assert_eq!(Status::unimplemented("").code(), Code::Unimplemented);
1010 assert_eq!(Status::internal("").code(), Code::Internal);
1011 assert_eq!(Status::unavailable("").code(), Code::Unavailable);
1012 assert_eq!(Status::data_loss("").code(), Code::DataLoss);
1013 assert_eq!(Status::unauthenticated("").code(), Code::Unauthenticated);
1014 }
1015
1016 #[test]
1017 fn details() {
1018 const DETAILS: &[u8] = &[0, 2, 3];
1019
1020 let status = Status::with_details(Code::Unavailable, "some message", DETAILS.into());
1021
1022 assert_eq!(status.details(), DETAILS);
1023
1024 let header_map = status.to_header_map().unwrap();
1025
1026 let b64_details = BASE64_ENGINE.encode(DETAILS);
1027
1028 assert_eq!(header_map[GRPC_STATUS_DETAILS_HEADER], b64_details);
1029
1030 let status = Status::from_header_map(&header_map).unwrap();
1031
1032 assert_eq!(status.details(), DETAILS);
1033 }
1034}