use tonic::Code;
#[non_exhaustive]
#[derive(Debug, Clone, thiserror::Error)]
pub enum PcsFailure {
#[error("rejected: {code:?} {message}")]
Rejected { code: Code, message: String },
#[error("server error: {code:?} {message}")]
ServerError { code: Code, message: String },
#[error("transport: {detail}")]
Transport { detail: String },
}
#[must_use]
pub fn classify_status(status: &tonic::Status) -> PcsFailure {
let code = status.code();
let message = status.message().to_string();
match code {
Code::InvalidArgument
| Code::NotFound
| Code::AlreadyExists
| Code::PermissionDenied
| Code::Unauthenticated
| Code::ResourceExhausted
| Code::FailedPrecondition
| Code::OutOfRange
| Code::Aborted
| Code::Cancelled => PcsFailure::Rejected { code, message },
Code::Internal | Code::Unknown | Code::DataLoss | Code::Unimplemented => {
PcsFailure::ServerError { code, message }
}
Code::DeadlineExceeded => PcsFailure::ServerError { code, message },
Code::Unavailable => PcsFailure::Transport { detail: message },
Code::Ok => PcsFailure::Transport {
detail: format!("classify_status called on Code::Ok: {message}"),
},
}
}
#[must_use]
pub fn classify_transport(detail: impl Into<String>) -> PcsFailure {
PcsFailure::Transport { detail: detail.into() }
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn st(code: Code) -> tonic::Status {
tonic::Status::new(code, "test")
}
#[test]
fn rejected_arms() {
for code in [
Code::InvalidArgument,
Code::NotFound,
Code::AlreadyExists,
Code::PermissionDenied,
Code::Unauthenticated,
Code::ResourceExhausted,
Code::FailedPrecondition,
Code::OutOfRange,
Code::Aborted,
Code::Cancelled,
] {
assert!(
matches!(classify_status(&st(code)), PcsFailure::Rejected { .. }),
"expected Rejected for {code:?}"
);
}
}
#[test]
fn server_error_arms() {
for code in [
Code::Internal,
Code::Unknown,
Code::DataLoss,
Code::Unimplemented,
Code::DeadlineExceeded,
] {
assert!(
matches!(classify_status(&st(code)), PcsFailure::ServerError { .. }),
"expected ServerError for {code:?}"
);
}
}
#[test]
fn transport_arms() {
assert!(matches!(
classify_status(&st(Code::Unavailable)),
PcsFailure::Transport { .. }
));
assert!(matches!(
classify_status(&st(Code::Ok)),
PcsFailure::Transport { .. }
));
}
#[test]
fn classify_transport_helper() {
let f = classify_transport("connect refused");
assert!(matches!(f, PcsFailure::Transport { detail } if detail == "connect refused"));
}
}