use std::fmt;
use a2a_protocol_types::{A2aError, TaskId};
#[derive(Debug)]
#[non_exhaustive]
pub enum ClientError {
Http(hyper::Error),
HttpClient(String),
Serialization(serde_json::Error),
Protocol(A2aError),
Transport(String),
InvalidEndpoint(String),
UnexpectedStatus {
status: u16,
body: String,
retry_after: Option<std::time::Duration>,
},
AuthRequired {
task_id: TaskId,
},
Timeout(String),
ProtocolBindingMismatch(String),
}
impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Http(e) => write!(f, "HTTP error: {e}"),
Self::HttpClient(msg) => write!(f, "HTTP client error: {msg}"),
Self::Serialization(e) => write!(f, "serialization error: {e}"),
Self::Protocol(e) => write!(f, "protocol error: {e}"),
Self::Transport(msg) => write!(f, "transport error: {msg}"),
Self::InvalidEndpoint(msg) => write!(f, "invalid endpoint: {msg}"),
Self::UnexpectedStatus { status, body, .. } => {
write!(f, "unexpected HTTP status {status}: {body}")
}
Self::AuthRequired { task_id } => {
write!(f, "authentication required for task: {task_id}")
}
Self::Timeout(msg) => write!(f, "timeout: {msg}"),
Self::ProtocolBindingMismatch(msg) => {
write!(
f,
"protocol binding mismatch: {msg}; check the agent card's supported_interfaces"
)
}
}
}
}
impl std::error::Error for ClientError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Http(e) => Some(e),
Self::Serialization(e) => Some(e),
Self::Protocol(e) => Some(e),
_ => None,
}
}
}
impl ClientError {
#[must_use]
pub const fn retry_after(&self) -> Option<std::time::Duration> {
match self {
Self::UnexpectedStatus { retry_after, .. } => *retry_after,
_ => None,
}
}
#[must_use]
pub fn is_stream_lagged(&self) -> bool {
matches!(self, Self::Protocol(e) if e.is_stream_lagged())
}
#[must_use]
pub fn dropped_event_count(&self) -> Option<u64> {
match self {
Self::Protocol(e) => e.dropped_event_count(),
_ => None,
}
}
}
#[must_use]
pub(crate) fn parse_retry_after(headers: &hyper::HeaderMap) -> Option<std::time::Duration> {
let raw = headers.get(hyper::header::RETRY_AFTER)?.to_str().ok()?;
let secs: u64 = raw.trim().parse().ok()?;
Some(std::time::Duration::from_secs(secs.min(3600)))
}
impl From<A2aError> for ClientError {
fn from(e: A2aError) -> Self {
Self::Protocol(e)
}
}
impl From<hyper::Error> for ClientError {
fn from(e: hyper::Error) -> Self {
Self::Http(e)
}
}
impl From<serde_json::Error> for ClientError {
fn from(e: serde_json::Error) -> Self {
Self::Serialization(e)
}
}
pub type ClientResult<T> = Result<T, ClientError>;
#[cfg(test)]
mod tests;