use std::time::Duration;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StreamError {
#[error("rpc error: {0}")]
Transport(#[source] Box<tonic::Status>),
#[error("connect error: {0}")]
Connect(#[source] tonic::transport::Error),
#[error("invalid endpoint: {0}")]
InvalidEndpoint(String),
#[error("invalid bearer token: not a valid header value")]
InvalidToken,
#[error(
"refusing to send a bearer token over an unencrypted endpoint: {0}. \
Encrypt the connection (an https:// endpoint plus tls()/tls_ca_pem(), \
both of which need this crate's `tls` feature), or call \
insecure_bearer_token() instead of bearer_token() to accept the risk."
)]
InsecureCredential(String),
#[error("unauthenticated: {0}")]
Unauthenticated(#[source] Box<tonic::Status>),
#[error("permission denied: {0}")]
PermissionDenied(#[source] Box<tonic::Status>),
#[error("resource exhausted: {0}")]
QuotaExhausted(#[source] Box<tonic::Status>),
#[error("rate limited")]
RateLimited {
retry_after: Option<Duration>,
},
#[error("cursor replay unavailable: server has no block source")]
ReplayUnavailable,
#[error("prefix bits {got} out of server range [{min}, {max}]")]
PrefixBitsOutOfRange {
got: u32,
min: u32,
max: u32,
},
#[error("invalid argument: {0}")]
InvalidArgument(String),
#[error("decode error: {0}")]
Decode(String),
#[error("control channel closed")]
ControlClosed,
#[error("watch-set loader failed: {0}")]
WatchSetLoader(#[source] Box<dyn std::error::Error + Send + Sync>),
}
impl StreamError {
pub(crate) fn from_status(status: tonic::Status) -> Self {
use tonic::Code;
match status.code() {
Code::Unauthenticated => StreamError::Unauthenticated(Box::new(status)),
Code::PermissionDenied => StreamError::PermissionDenied(Box::new(status)),
Code::ResourceExhausted => StreamError::QuotaExhausted(Box::new(status)),
_ => StreamError::Transport(Box::new(status)),
}
}
pub fn is_retryable(&self) -> bool {
use tonic::Code;
match self {
StreamError::Connect(_) => true,
StreamError::RateLimited { .. } => true,
StreamError::QuotaExhausted(_) => true,
StreamError::Transport(s) => matches!(
s.code(),
Code::Unavailable
| Code::DeadlineExceeded
| Code::Aborted
| Code::Cancelled
| Code::ResourceExhausted
),
_ => false,
}
}
}
impl From<tonic::Status> for StreamError {
fn from(s: tonic::Status) -> Self {
StreamError::from_status(s)
}
}
impl From<tonic::transport::Error> for StreamError {
fn from(e: tonic::transport::Error) -> Self {
StreamError::Connect(e)
}
}