saluki_io/net/util/
tonic.rs

1use tonic::{Code, Status};
2
3/// A human-friendly wrapper around `tonic::Status`.
4///
5/// This wrapper type provides a more human-friendly error message over `tonic::Status` to avoid adding noisy/superfluous
6/// information when the error is displayed.
7pub struct StatusError(Status);
8
9impl From<Status> for StatusError {
10    fn from(status: Status) -> Self {
11        Self(status)
12    }
13}
14
15impl std::fmt::Debug for StatusError {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        // Forward directly to `Status`.
18        self.0.fmt(f)
19    }
20}
21
22impl std::fmt::Display for StatusError {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        let status_code = code_to_identifier(self.0.code());
25        let status_message = self.0.message();
26
27        let message = if status_message.is_empty() {
28            // When we don't have a message, just use the human-friendly description of the code itself.
29            self.0.code().description()
30        } else {
31            status_message
32        };
33
34        write!(f, "{}({})", status_code, message)
35    }
36}
37
38impl std::error::Error for StatusError {
39    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
40        self.0.source()
41    }
42}
43
44fn code_to_identifier(code: Code) -> &'static str {
45    match code {
46        Code::Ok => "Ok",
47        Code::Cancelled => "Cancelled",
48        Code::Unknown => "Unknown",
49        Code::InvalidArgument => "InvalidArgument",
50        Code::DeadlineExceeded => "DeadlineExceeded",
51        Code::NotFound => "NotFound",
52        Code::AlreadyExists => "AlreadyExists",
53        Code::PermissionDenied => "PermissionDenied",
54        Code::ResourceExhausted => "ResourceExhausted",
55        Code::FailedPrecondition => "FailedPrecondition",
56        Code::Aborted => "Aborted",
57        Code::OutOfRange => "OutOfRange",
58        Code::Unimplemented => "Unimplemented",
59        Code::Internal => "Internal",
60        Code::Unavailable => "Unavailable",
61        Code::DataLoss => "DataLoss",
62        Code::Unauthenticated => "Unauthenticated",
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn status_empty_message() {
72        let status = Status::new(Code::Ok, "");
73        let error = StatusError::from(status);
74        assert_eq!(error.to_string(), "Ok(The operation completed successfully)");
75    }
76
77    #[test]
78    fn status_non_empty_message() {
79        let status = Status::new(Code::Unavailable, "tcp connect error");
80        let error = StatusError::from(status);
81        assert_eq!(error.to_string(), "Unavailable(tcp connect error)");
82    }
83}