Skip to main content

a2a_protocol_server/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Tom F.
3
4//! Server-specific error types.
5//!
6//! [`ServerError`] wraps lower-level errors and A2A protocol errors into a
7//! unified enum for the server framework. Use [`ServerError::to_a2a_error`]
8//! to convert back to a protocol-level [`A2aError`] for wire responses.
9
10use std::fmt;
11
12use a2a_protocol_types::error::{A2aError, ErrorCode};
13use a2a_protocol_types::task::TaskId;
14
15// ── ServerError ──────────────────────────────────────────────────────────────
16
17/// Server framework error type.
18///
19/// Each variant maps to a specific A2A [`ErrorCode`] via [`to_a2a_error`](Self::to_a2a_error).
20#[derive(Debug)]
21#[non_exhaustive]
22pub enum ServerError {
23    /// The requested task was not found.
24    TaskNotFound(TaskId),
25    /// The task is in a terminal state and cannot be canceled.
26    TaskNotCancelable(TaskId),
27    /// Invalid method parameters.
28    InvalidParams(String),
29    /// JSON serialization/deserialization failure.
30    Serialization(serde_json::Error),
31    /// Hyper HTTP error.
32    Http(hyper::Error),
33    /// HTTP client-side error (e.g. push notification delivery).
34    HttpClient(String),
35    /// Transport-layer error.
36    Transport(String),
37    /// The agent does not support push notifications.
38    PushNotSupported,
39    /// An internal server error.
40    Internal(String),
41    /// The requested JSON-RPC method was not found.
42    MethodNotFound(String),
43    /// An A2A protocol error propagated from the executor.
44    Protocol(A2aError),
45    /// The request body exceeds the configured size limit.
46    PayloadTooLarge(String),
47    /// An invalid task state transition was attempted.
48    InvalidStateTransition {
49        /// The task ID.
50        task_id: TaskId,
51        /// The current state.
52        from: a2a_protocol_types::task::TaskState,
53        /// The attempted target state.
54        to: a2a_protocol_types::task::TaskState,
55    },
56}
57
58impl fmt::Display for ServerError {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            Self::TaskNotFound(id) => write!(f, "task not found: {id}"),
62            Self::TaskNotCancelable(id) => write!(f, "task not cancelable: {id}"),
63            Self::InvalidParams(msg) => write!(f, "invalid params: {msg}"),
64            Self::Serialization(e) => write!(f, "serialization error: {e}"),
65            Self::Http(e) => write!(f, "HTTP error: {e}"),
66            Self::HttpClient(msg) => write!(f, "HTTP client error: {msg}"),
67            Self::Transport(msg) => write!(f, "transport error: {msg}"),
68            Self::PushNotSupported => f.write_str("push notifications not supported"),
69            Self::Internal(msg) => write!(f, "internal error: {msg}"),
70            Self::MethodNotFound(m) => write!(f, "method not found: {m}"),
71            Self::Protocol(e) => write!(f, "protocol error: {e}"),
72            Self::PayloadTooLarge(msg) => write!(f, "payload too large: {msg}"),
73            Self::InvalidStateTransition { task_id, from, to } => {
74                write!(
75                    f,
76                    "invalid state transition for task {task_id}: {from} → {to}"
77                )
78            }
79        }
80    }
81}
82
83impl std::error::Error for ServerError {
84    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
85        match self {
86            Self::Serialization(e) => Some(e),
87            Self::Http(e) => Some(e),
88            Self::Protocol(e) => Some(e),
89            _ => None,
90        }
91    }
92}
93
94impl ServerError {
95    /// Converts this server error into an [`A2aError`] suitable for wire responses.
96    ///
97    /// # Mapping
98    ///
99    /// | Variant | [`ErrorCode`] |
100    /// |---|---|
101    /// | `TaskNotFound` | `TaskNotFound` |
102    /// | `TaskNotCancelable` | `TaskNotCancelable` |
103    /// | `InvalidParams` | `InvalidParams` |
104    /// | `Serialization` | `ParseError` |
105    /// | `MethodNotFound` | `MethodNotFound` |
106    /// | `PushNotSupported` | `PushNotificationNotSupported` |
107    /// | everything else | `InternalError` |
108    #[must_use]
109    pub fn to_a2a_error(&self) -> A2aError {
110        match self {
111            Self::TaskNotFound(id) => A2aError::task_not_found(id),
112            Self::TaskNotCancelable(id) => A2aError::task_not_cancelable(id),
113            Self::InvalidParams(msg) => A2aError::invalid_params(msg.clone()),
114            Self::Serialization(e) => A2aError::parse_error(e.to_string()),
115            Self::MethodNotFound(m) => {
116                A2aError::new(ErrorCode::MethodNotFound, format!("Method not found: {m}"))
117            }
118            Self::PushNotSupported => A2aError::new(
119                ErrorCode::PushNotificationNotSupported,
120                "Push notifications not supported",
121            ),
122            Self::Protocol(e) => e.clone(),
123            Self::Http(e) => A2aError::internal(e.to_string()),
124            Self::HttpClient(msg)
125            | Self::Transport(msg)
126            | Self::Internal(msg)
127            | Self::PayloadTooLarge(msg) => A2aError::internal(msg.clone()),
128            Self::InvalidStateTransition { task_id, from, to } => A2aError::invalid_params(
129                format!("invalid state transition for task {task_id}: {from} → {to}"),
130            ),
131        }
132    }
133}
134
135// ── From impls ───────────────────────────────────────────────────────────────
136
137impl From<A2aError> for ServerError {
138    fn from(e: A2aError) -> Self {
139        Self::Protocol(e)
140    }
141}
142
143impl From<serde_json::Error> for ServerError {
144    fn from(e: serde_json::Error) -> Self {
145        Self::Serialization(e)
146    }
147}
148
149impl From<hyper::Error> for ServerError {
150    fn from(e: hyper::Error) -> Self {
151        Self::Http(e)
152    }
153}
154
155// ── ServerResult ─────────────────────────────────────────────────────────────
156
157/// Convenience type alias: `Result<T, ServerError>`.
158pub type ServerResult<T> = Result<T, ServerError>;