#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum PolicyFetchError {
#[error("cex-policy gRPC target not configured: {0}")]
NotConfigured(String),
#[error("cex-policy {rpc} for {subject} failed transiently ({code:?}): {message}")]
Transient {
rpc: &'static str,
subject: String,
code: tonic::Code,
message: String,
},
#[error("cex-policy {rpc} for {subject} was refused ({code:?}): {message}")]
Refused {
rpc: &'static str,
subject: String,
code: tonic::Code,
message: String,
},
}
impl PolicyFetchError {
pub(crate) fn from_status(rpc: &'static str, subject: &str, status: &tonic::Status) -> Self {
let code = status.code();
let subject = subject.to_string();
let message = status.message().to_string();
if is_transient_code(code) {
Self::Transient {
rpc,
subject,
code,
message,
}
} else {
Self::Refused {
rpc,
subject,
code,
message,
}
}
}
pub fn is_retryable(&self) -> bool {
matches!(self, Self::Transient { .. })
}
pub fn code(&self) -> Option<tonic::Code> {
match self {
Self::NotConfigured(_) => None,
Self::Transient { code, .. } | Self::Refused { code, .. } => Some(*code),
}
}
}
fn is_transient_code(code: tonic::Code) -> bool {
matches!(
code,
tonic::Code::Unavailable
| tonic::Code::DeadlineExceeded
| tonic::Code::Internal
| tonic::Code::Unknown
| tonic::Code::ResourceExhausted
| tonic::Code::Aborted
| tonic::Code::Cancelled
)
}