#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum A2aClientError {
#[error("HTTP {status}: {message}")]
Http { status: u16, message: String },
#[error("A2A error {status}: {message}")]
A2aError {
status: u16,
message: String,
reason: Option<String>,
},
#[error("Request error: {0}")]
Request(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Type conversion error: {0}")]
Conversion(String),
#[error("SSE error: {0}")]
Sse(String),
#[error("SSE stream closed")]
StreamClosed,
#[cfg(feature = "grpc")]
#[error("gRPC error {0:?}: {1}")]
Grpc(tonic::Code, String, Option<String>),
#[cfg(feature = "grpc")]
#[error("gRPC transport error: {0}")]
GrpcTransport(String),
}
impl A2aClientError {
pub fn reason(&self) -> Option<&str> {
match self {
Self::A2aError { reason, .. } => reason.as_deref(),
#[cfg(feature = "grpc")]
Self::Grpc(_, _, reason) => reason.as_deref(),
_ => None,
}
}
pub fn status(&self) -> Option<u16> {
match self {
Self::Http { status, .. } | Self::A2aError { status, .. } => Some(*status),
_ => None,
}
}
#[cfg(feature = "grpc")]
pub fn grpc_code(&self) -> Option<tonic::Code> {
match self {
Self::Grpc(code, _, _) => Some(*code),
_ => None,
}
}
}
#[cfg(feature = "grpc")]
impl From<tonic::Status> for A2aClientError {
fn from(status: tonic::Status) -> Self {
let reason = {
use tonic_types::StatusExt;
status.get_details_error_info().map(|info| info.reason)
};
A2aClientError::Grpc(status.code(), status.message().to_string(), reason)
}
}
#[cfg(feature = "grpc")]
impl From<tonic::transport::Error> for A2aClientError {
fn from(err: tonic::transport::Error) -> Self {
A2aClientError::GrpcTransport(err.to_string())
}
}