Skip to main content

vtcode_core/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
14pub const JSON_PARSE_ERROR: i32 = -32700;
15
16/// Invalid Request - The JSON sent is not a valid Request object
17pub const INVALID_REQUEST_ERROR: i32 = -32600;
18
19/// Method not found - The method does not exist / is not available
20pub const METHOD_NOT_FOUND_ERROR: i32 = -32601;
21
22/// Invalid params - Invalid method parameters
23pub const INVALID_PARAMS_ERROR: i32 = -32602;
24
25/// Internal error - Internal JSON-RPC error
26pub const INTERNAL_ERROR: i32 = -32603;
27
28// ============================================================================
29// A2A-Specific Error Codes
30// ============================================================================
31
32/// Task not found - The specified task does not exist
33pub const TASK_NOT_FOUND_ERROR: i32 = -32001;
34
35/// Task not cancelable - The task cannot be canceled in its current state
36pub const TASK_NOT_CANCELABLE_ERROR: i32 = -32002;
37
38/// Push notifications not supported - The agent does not support push notifications
39pub const PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR: i32 = -32003;
40
41/// Unsupported operation - The requested operation is not supported
42pub const UNSUPPORTED_OPERATION_ERROR: i32 = -32004;
43
44/// Content type not supported - The content type is not supported
45pub const CONTENT_TYPE_NOT_SUPPORTED_ERROR: i32 = -32005;
46
47/// Invalid agent response - The agent returned an invalid response
48pub const INVALID_AGENT_RESPONSE_ERROR: i32 = -32006;
49
50/// Authenticated extended card not configured
51pub const 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 => {
93                AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED_ERROR
94            }
95            A2aErrorCode::Custom(code) => code,
96        }
97    }
98}
99
100impl From<i32> for A2aErrorCode {
101    fn from(code: i32) -> Self {
102        match code {
103            JSON_PARSE_ERROR => A2aErrorCode::JsonParseError,
104            INVALID_REQUEST_ERROR => A2aErrorCode::InvalidRequest,
105            METHOD_NOT_FOUND_ERROR => A2aErrorCode::MethodNotFound,
106            INVALID_PARAMS_ERROR => A2aErrorCode::InvalidParams,
107            INTERNAL_ERROR => A2aErrorCode::InternalError,
108            TASK_NOT_FOUND_ERROR => A2aErrorCode::TaskNotFound,
109            TASK_NOT_CANCELABLE_ERROR => A2aErrorCode::TaskNotCancelable,
110            PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR => A2aErrorCode::PushNotificationNotSupported,
111            UNSUPPORTED_OPERATION_ERROR => A2aErrorCode::UnsupportedOperation,
112            CONTENT_TYPE_NOT_SUPPORTED_ERROR => A2aErrorCode::ContentTypeNotSupported,
113            INVALID_AGENT_RESPONSE_ERROR => A2aErrorCode::InvalidAgentResponse,
114            AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED_ERROR => {
115                A2aErrorCode::AuthenticatedExtendedCardNotConfigured
116            }
117            other => A2aErrorCode::Custom(other),
118        }
119    }
120}
121
122impl fmt::Display for A2aErrorCode {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        match self {
125            A2aErrorCode::JsonParseError => write!(f, "JSON parse error"),
126            A2aErrorCode::InvalidRequest => write!(f, "Invalid request"),
127            A2aErrorCode::MethodNotFound => write!(f, "Method not found"),
128            A2aErrorCode::InvalidParams => write!(f, "Invalid params"),
129            A2aErrorCode::InternalError => write!(f, "Internal error"),
130            A2aErrorCode::TaskNotFound => write!(f, "Task not found"),
131            A2aErrorCode::TaskNotCancelable => write!(f, "Task not cancelable"),
132            A2aErrorCode::PushNotificationNotSupported => {
133                write!(f, "Push notifications not supported")
134            }
135            A2aErrorCode::UnsupportedOperation => write!(f, "Unsupported operation"),
136            A2aErrorCode::ContentTypeNotSupported => write!(f, "Content type not supported"),
137            A2aErrorCode::InvalidAgentResponse => write!(f, "Invalid agent response"),
138            A2aErrorCode::AuthenticatedExtendedCardNotConfigured => {
139                write!(f, "Authenticated extended card not configured")
140            }
141            A2aErrorCode::Custom(code) => write!(f, "Custom error ({code})"),
142        }
143    }
144}
145
146/// A2A protocol error
147#[derive(Debug, Error)]
148pub enum A2aError {
149    #[error("JSON-RPC error ({code}): {message}")]
150    RpcError {
151        code: A2aErrorCode,
152        message: String,
153        #[source]
154        data: Option<Box<dyn std::error::Error + Send + Sync>>,
155    },
156
157    #[error("Task not found: {0}")]
158    TaskNotFound(String),
159
160    #[error("Task not cancelable: {0}")]
161    TaskNotCancelable(String),
162
163    #[error("Invalid task state transition: {from:?} -> {to:?}")]
164    InvalidStateTransition {
165        from: super::types::TaskState,
166        to: super::types::TaskState,
167    },
168
169    #[error("Unsupported operation: {0}")]
170    UnsupportedOperation(String),
171
172    #[error("Content type not supported: {0}")]
173    ContentTypeNotSupported(String),
174
175    #[error("Serialization error: {0}")]
176    Serialization(#[from] serde_json::Error),
177
178    #[error("Internal error: {0}")]
179    Internal(String),
180}
181
182impl A2aError {
183    /// Get the error code for this error
184    pub fn code(&self) -> A2aErrorCode {
185        match self {
186            A2aError::RpcError { code, .. } => *code,
187            A2aError::TaskNotFound(_) => A2aErrorCode::TaskNotFound,
188            A2aError::TaskNotCancelable(_) => A2aErrorCode::TaskNotCancelable,
189            A2aError::InvalidStateTransition { .. } => A2aErrorCode::InvalidParams,
190            A2aError::UnsupportedOperation(_) => A2aErrorCode::UnsupportedOperation,
191            A2aError::ContentTypeNotSupported(_) => A2aErrorCode::ContentTypeNotSupported,
192            A2aError::Serialization(_) => A2aErrorCode::JsonParseError,
193            A2aError::Internal(_) => A2aErrorCode::InternalError,
194        }
195    }
196
197    /// Create a new RPC error
198    pub fn rpc(code: A2aErrorCode, message: impl Into<String>) -> Self {
199        A2aError::RpcError {
200            code,
201            message: message.into(),
202            data: None,
203        }
204    }
205
206    /// Create a new internal error
207    pub fn internal(message: impl Into<String>) -> Self {
208        A2aError::Internal(message.into())
209    }
210}
211
212/// A2A Result type alias
213pub type A2aResult<T> = Result<T, A2aError>;
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_error_code_conversion() {
221        assert_eq!(i32::from(A2aErrorCode::TaskNotFound), TASK_NOT_FOUND_ERROR);
222        assert_eq!(
223            A2aErrorCode::from(TASK_NOT_FOUND_ERROR),
224            A2aErrorCode::TaskNotFound
225        );
226    }
227
228    #[test]
229    fn test_error_code_display() {
230        assert_eq!(A2aErrorCode::TaskNotFound.to_string(), "Task not found");
231        assert_eq!(A2aErrorCode::MethodNotFound.to_string(), "Method not found");
232    }
233
234    #[test]
235    fn test_a2a_error_code() {
236        let err = A2aError::TaskNotFound("task-123".to_string());
237        assert_eq!(err.code(), A2aErrorCode::TaskNotFound);
238    }
239
240    #[test]
241    fn test_custom_error_code() {
242        let custom = A2aErrorCode::Custom(-32099);
243        assert_eq!(i32::from(custom), -32099);
244        assert_eq!(custom.to_string(), "Custom error (-32099)");
245    }
246}