#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ClientErrorKind {
Unauthorized,
Unavailable,
InvalidRequest,
Ambiguous,
NotFound,
Conflict,
Unsupported,
Protocol,
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("{message}")]
pub struct ClientError {
kind: ClientErrorKind,
message: String,
#[source]
source: tonic::Status,
}
impl ClientError {
pub fn kind(&self) -> ClientErrorKind {
self.kind
}
pub(crate) fn from_status(operation: &'static str, status: tonic::Status) -> Self {
let kind = match status.code() {
tonic::Code::Unauthenticated | tonic::Code::PermissionDenied => ClientErrorKind::Unauthorized,
tonic::Code::Unavailable | tonic::Code::DeadlineExceeded | tonic::Code::Cancelled => ClientErrorKind::Unavailable,
tonic::Code::InvalidArgument => ClientErrorKind::InvalidRequest,
tonic::Code::FailedPrecondition => ClientErrorKind::Ambiguous,
tonic::Code::NotFound => ClientErrorKind::NotFound,
tonic::Code::AlreadyExists | tonic::Code::Aborted => ClientErrorKind::Conflict,
tonic::Code::Unimplemented => ClientErrorKind::Unsupported,
_ => ClientErrorKind::Protocol,
};
Self {
kind,
message: format!("{operation} failed: {status}"),
source: status,
}
}
}
#[cfg(test)]
mod tests {
use std::error::Error as _;
use super::*;
#[test]
fn transport_status_remains_in_the_error_source_chain() {
let error = ClientError::from_status("ListDevices", tonic::Status::unavailable("offline"));
assert_eq!(error.kind(), ClientErrorKind::Unavailable);
assert!(error.source().is_some());
}
}