Skip to main content

vtcode_a2a/
errors.rs

1//! A2A Protocol error types and error codes
2//!
3//! Implements both standard JSON-RPC 2.0 error codes and A2A-specific error codes
4//! as defined in the A2A specification.
5
6use std::fmt;
7use thiserror::Error;
8
9// ============================================================================
10// Standard JSON-RPC 2.0 Error Codes
11// ============================================================================
12
13/// Parse error - Invalid JSON was received
14const JSON_PARSE_ERROR: i32 = -32700;
15
16/// Invalid Request - The JSON sent is not a valid Request object
17const INVALID_REQUEST_ERROR: i32 = -32600;
18
19/// Method not found - The method does not exist / is not available
20const METHOD_NOT_FOUND_ERROR: i32 = -32601;
21
22/// Invalid params - Invalid method parameters
23const INVALID_PARAMS_ERROR: i32 = -32602;
24
25/// Internal error - Internal JSON-RPC error
26const INTERNAL_ERROR: i32 = -32603;
27
28// ============================================================================
29// A2A-Specific Error Codes
30// ============================================================================
31
32/// Task not found - The specified task does not exist
33const TASK_NOT_FOUND_ERROR: i32 = -32001;
34
35/// Task not cancelable - The task cannot be canceled in its current state
36const TASK_NOT_CANCELABLE_ERROR: i32 = -32002;
37
38/// Push notifications not supported - The agent does not support push notifications
39const PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR: i32 = -32003;
40
41/// Unsupported operation - The requested operation is not supported
42const UNSUPPORTED_OPERATION_ERROR: i32 = -32004;
43
44/// Content type not supported - The content type is not supported
45const CONTENT_TYPE_NOT_SUPPORTED_ERROR: i32 = -32005;
46
47/// Invalid agent response - The agent returned an invalid response
48const INVALID_AGENT_RESPONSE_ERROR: i32 = -32006;
49
50/// Authenticated extended card not configured
51const AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED_ERROR: i32 = -32007;
52
53// ============================================================================
54// Error Types
55// ============================================================================
56
57/// A2A error code enum for type-safe error handling
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum A2aErrorCode {
60    // Standard JSON-RPC errors
61    JsonParseError,
62    InvalidRequest,
63    MethodNotFound,
64    InvalidParams,
65    InternalError,
66    // A2A-specific errors
67    TaskNotFound,
68    TaskNotCancelable,
69    PushNotificationNotSupported,
70    UnsupportedOperation,
71    ContentTypeNotSupported,
72    InvalidAgentResponse,
73    AuthenticatedExtendedCardNotConfigured,
74    /// Custom error code
75    Custom(i32),
76}
77
78impl From<A2aErrorCode> for i32 {
79    fn from(code: A2aErrorCode) -> Self {
80        match code {
81            A2aErrorCode::JsonParseError => JSON_PARSE_ERROR,
82            A2aErrorCode::InvalidRequest => INVALID_REQUEST_ERROR,
83            A2aErrorCode::MethodNotFound => METHOD_NOT_FOUND_ERROR,
84            A2aErrorCode::InvalidParams => INVALID_PARAMS_ERROR,
85            A2aErrorCode::InternalError => INTERNAL_ERROR,
86            A2aErrorCode::TaskNotFound => TASK_NOT_FOUND_ERROR,
87            A2aErrorCode::TaskNotCancelable => TASK_NOT_CANCELABLE_ERROR,
88            A2aErrorCode::PushNotificationNotSupported => PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR,
89            A2aErrorCode::UnsupportedOperation => UNSUPPORTED_OPERATION_ERROR,
90            A2aErrorCode::ContentTypeNotSupported => CONTENT_TYPE_NOT_SUPPORTED_ERROR,
91            A2aErrorCode::InvalidAgentResponse => INVALID_AGENT_RESPONSE_ERROR,
92            A2aErrorCode::AuthenticatedExtendedCardNotConfigured => AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED_ERROR,
93            A2aErrorCode::Custom(code) => code,
94        }
95    }
96}
97
98impl From<i32> for A2aErrorCode {
99    fn from(code: i32) -> Self {
100        match code {
101            JSON_PARSE_ERROR => A2aErrorCode::JsonParseError,
102            INVALID_REQUEST_ERROR => A2aErrorCode::InvalidRequest,
103            METHOD_NOT_FOUND_ERROR => A2aErrorCode::MethodNotFound,
104            INVALID_PARAMS_ERROR => A2aErrorCode::InvalidParams,
105            INTERNAL_ERROR => A2aErrorCode::InternalError,
106            TASK_NOT_FOUND_ERROR => A2aErrorCode::TaskNotFound,
107            TASK_NOT_CANCELABLE_ERROR => A2aErrorCode::TaskNotCancelable,
108            PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR => A2aErrorCode::PushNotificationNotSupported,
109            UNSUPPORTED_OPERATION_ERROR => A2aErrorCode::UnsupportedOperation,
110            CONTENT_TYPE_NOT_SUPPORTED_ERROR => A2aErrorCode::ContentTypeNotSupported,
111            INVALID_AGENT_RESPONSE_ERROR => A2aErrorCode::InvalidAgentResponse,
112            AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED_ERROR => A2aErrorCode::AuthenticatedExtendedCardNotConfigured,
113            other => A2aErrorCode::Custom(other),
114        }
115    }
116}
117
118impl fmt::Display for A2aErrorCode {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            A2aErrorCode::JsonParseError => write!(f, "JSON parse error"),
122            A2aErrorCode::InvalidRequest => write!(f, "Invalid request"),
123            A2aErrorCode::MethodNotFound => write!(f, "Method not found"),
124            A2aErrorCode::InvalidParams => write!(f, "Invalid params"),
125            A2aErrorCode::InternalError => write!(f, "Internal error"),
126            A2aErrorCode::TaskNotFound => write!(f, "Task not found"),
127            A2aErrorCode::TaskNotCancelable => write!(f, "Task not cancelable"),
128            A2aErrorCode::PushNotificationNotSupported => {
129                write!(f, "Push notifications not supported")
130            }
131            A2aErrorCode::UnsupportedOperation => write!(f, "Unsupported operation"),
132            A2aErrorCode::ContentTypeNotSupported => write!(f, "Content type not supported"),
133            A2aErrorCode::InvalidAgentResponse => write!(f, "Invalid agent response"),
134            A2aErrorCode::AuthenticatedExtendedCardNotConfigured => {
135                write!(f, "Authenticated extended card not configured")
136            }
137            A2aErrorCode::Custom(code) => write!(f, "Custom error ({code})"),
138        }
139    }
140}
141
142/// A2A protocol error
143#[derive(Debug, Error)]
144pub enum A2aError {
145    #[error("JSON-RPC error ({code}): {message}")]
146    RpcError {
147        code: A2aErrorCode,
148        message: String,
149        #[source]
150        data: Option<Box<dyn std::error::Error + Send + Sync>>,
151    },
152
153    #[error("Task not found: {0}")]
154    TaskNotFound(String),
155
156    #[error("Task not cancelable: {0}")]
157    TaskNotCancelable(String),
158
159    #[error("Invalid task state transition: {from:?} -> {to:?}")]
160    InvalidStateTransition {
161        from: super::types::TaskState,
162        to: super::types::TaskState,
163    },
164
165    #[error("Unsupported operation: {0}")]
166    UnsupportedOperation(String),
167
168    #[error("Content type not supported: {0}")]
169    ContentTypeNotSupported(String),
170
171    #[error("Serialization error: {0}")]
172    Serialization(#[from] serde_json::Error),
173
174    #[error("Internal error: {0}")]
175    Internal(String),
176}
177
178impl A2aError {
179    /// Get the error code for this error
180    pub(crate) fn code(&self) -> A2aErrorCode {
181        match self {
182            A2aError::RpcError { code, .. } => *code,
183            A2aError::TaskNotFound(_) => A2aErrorCode::TaskNotFound,
184            A2aError::TaskNotCancelable(_) => A2aErrorCode::TaskNotCancelable,
185            A2aError::InvalidStateTransition { .. } => A2aErrorCode::InvalidParams,
186            A2aError::UnsupportedOperation(_) => A2aErrorCode::UnsupportedOperation,
187            A2aError::ContentTypeNotSupported(_) => A2aErrorCode::ContentTypeNotSupported,
188            A2aError::Serialization(_) => A2aErrorCode::JsonParseError,
189            A2aError::Internal(_) => A2aErrorCode::InternalError,
190        }
191    }
192
193    /// Create a new RPC error
194    pub(crate) fn rpc(code: A2aErrorCode, message: impl Into<String>) -> Self {
195        A2aError::RpcError { code, message: message.into(), data: None }
196    }
197
198    /// Create a new internal error
199    pub fn internal(message: impl Into<String>) -> Self {
200        A2aError::Internal(message.into())
201    }
202}
203
204/// A2A Result type alias
205pub type A2aResult<T> = Result<T, A2aError>;
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_error_code_conversion() {
213        assert_eq!(i32::from(A2aErrorCode::TaskNotFound), TASK_NOT_FOUND_ERROR);
214        assert_eq!(A2aErrorCode::from(TASK_NOT_FOUND_ERROR), A2aErrorCode::TaskNotFound);
215    }
216
217    #[test]
218    fn test_error_code_display() {
219        assert_eq!(A2aErrorCode::TaskNotFound.to_string(), "Task not found");
220        assert_eq!(A2aErrorCode::MethodNotFound.to_string(), "Method not found");
221    }
222
223    #[test]
224    fn test_a2a_error_code() {
225        let err = A2aError::TaskNotFound("task-123".to_string());
226        assert_eq!(err.code(), A2aErrorCode::TaskNotFound);
227    }
228
229    #[test]
230    fn test_custom_error_code() {
231        let custom = A2aErrorCode::Custom(-32099);
232        assert_eq!(i32::from(custom), -32099);
233        assert_eq!(custom.to_string(), "Custom error (-32099)");
234    }
235}