agent_client_protocol/error.rs
1//! Error handling for the Agent Client Protocol.
2//!
3//! This module provides error types and codes following the JSON-RPC 2.0 specification,
4//! with additional protocol-specific error codes for authentication and other ACP-specific scenarios.
5//!
6//! All methods in the protocol follow standard JSON-RPC 2.0 error handling:
7//! - Successful responses include a `result` field
8//! - Errors include an `error` object with `code` and `message`
9//! - Notifications never receive responses (success or error)
10//!
11//! See: [Error Handling](https://agentclientprotocol.com/protocol/overview#error-handling)
12
13use std::fmt::Display;
14
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17
18/// JSON-RPC error object.
19///
20/// Represents an error that occurred during method execution, following the
21/// JSON-RPC 2.0 error object specification with optional additional data.
22///
23/// See protocol docs: [JSON-RPC Error Object](https://www.jsonrpc.org/specification#error_object)
24#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
25pub struct Error {
26 /// A number indicating the error type that occurred.
27 /// This must be an integer as defined in the JSON-RPC specification.
28 pub code: i32,
29 /// A string providing a short description of the error.
30 /// The message should be limited to a concise single sentence.
31 pub message: String,
32 /// Optional primitive or structured value that contains additional information about the error.
33 /// This may include debugging information or context-specific details.
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub data: Option<serde_json::Value>,
36}
37
38impl Error {
39 /// Creates a new error with the given code and message.
40 ///
41 /// The code parameter can be an `ErrorCode` constant or a tuple of (code, message).
42 pub fn new(code: impl Into<(i32, String)>) -> Self {
43 let (code, message) = code.into();
44 Error {
45 code,
46 message,
47 data: None,
48 }
49 }
50
51 /// Adds additional data to the error.
52 ///
53 /// This method is chainable and allows attaching context-specific information
54 /// to help with debugging or provide more details about the error.
55 #[must_use]
56 pub fn with_data(mut self, data: impl Into<serde_json::Value>) -> Self {
57 self.data = Some(data.into());
58 self
59 }
60
61 /// Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
62 #[must_use]
63 pub fn parse_error() -> Self {
64 Error::new(ErrorCode::PARSE_ERROR)
65 }
66
67 /// The JSON sent is not a valid Request object.
68 #[must_use]
69 pub fn invalid_request() -> Self {
70 Error::new(ErrorCode::INVALID_REQUEST)
71 }
72
73 /// The method does not exist / is not available.
74 #[must_use]
75 pub fn method_not_found() -> Self {
76 Error::new(ErrorCode::METHOD_NOT_FOUND)
77 }
78
79 /// Invalid method parameter(s).
80 #[must_use]
81 pub fn invalid_params() -> Self {
82 Error::new(ErrorCode::INVALID_PARAMS)
83 }
84
85 /// Internal JSON-RPC error.
86 #[must_use]
87 pub fn internal_error() -> Self {
88 Error::new(ErrorCode::INTERNAL_ERROR)
89 }
90
91 /// Authentication required.
92 #[must_use]
93 pub fn auth_required() -> Self {
94 Error::new(ErrorCode::AUTH_REQUIRED)
95 }
96
97 /// A given resource, such as a file, was not found.
98 #[must_use]
99 pub fn resource_not_found(uri: Option<String>) -> Self {
100 let err = Error::new(ErrorCode::RESOURCE_NOT_FOUND);
101 if let Some(uri) = uri {
102 err.with_data(serde_json::json!({ "uri": uri }))
103 } else {
104 err
105 }
106 }
107
108 /// Converts a standard error into an internal JSON-RPC error.
109 ///
110 /// The error's string representation is included as additional data.
111 pub fn into_internal_error(err: impl std::error::Error) -> Self {
112 Error::internal_error().with_data(err.to_string())
113 }
114}
115
116/// Predefined error codes for common JSON-RPC and ACP-specific errors.
117///
118/// These codes follow the JSON-RPC 2.0 specification for standard errors
119/// and use the reserved range (-32000 to -32099) for protocol-specific errors.
120#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
121pub struct ErrorCode {
122 /// The numeric error code.
123 pub code: i32,
124 /// The standard error message for this code.
125 pub message: &'static str,
126}
127
128impl ErrorCode {
129 /// Invalid JSON was received by the server.
130 /// An error occurred on the server while parsing the JSON text.
131 pub const PARSE_ERROR: ErrorCode = ErrorCode {
132 code: -32700,
133 message: "Parse error",
134 };
135
136 /// The JSON sent is not a valid Request object.
137 pub const INVALID_REQUEST: ErrorCode = ErrorCode {
138 code: -32600,
139 message: "Invalid Request",
140 };
141
142 /// The method does not exist or is not available.
143 pub const METHOD_NOT_FOUND: ErrorCode = ErrorCode {
144 code: -32601,
145 message: "Method not found",
146 };
147
148 /// Invalid method parameter(s).
149 pub const INVALID_PARAMS: ErrorCode = ErrorCode {
150 code: -32602,
151 message: "Invalid params",
152 };
153
154 /// Internal JSON-RPC error.
155 /// Reserved for implementation-defined server errors.
156 pub const INTERNAL_ERROR: ErrorCode = ErrorCode {
157 code: -32603,
158 message: "Internal error",
159 };
160
161 /// Authentication is required before this operation can be performed.
162 /// This is an ACP-specific error code in the reserved range.
163 pub const AUTH_REQUIRED: ErrorCode = ErrorCode {
164 code: -32000,
165 message: "Authentication required",
166 };
167
168 /// A given resource, such as a file, was not found.
169 /// This is an ACP-specific error code in the reserved range.
170 pub const RESOURCE_NOT_FOUND: ErrorCode = ErrorCode {
171 code: -32002,
172 message: "Resource not found",
173 };
174}
175
176impl From<ErrorCode> for (i32, String) {
177 fn from(error_code: ErrorCode) -> Self {
178 (error_code.code, error_code.message.to_string())
179 }
180}
181
182impl From<ErrorCode> for Error {
183 fn from(error_code: ErrorCode) -> Self {
184 Error::new(error_code)
185 }
186}
187
188impl std::error::Error for Error {}
189
190impl Display for Error {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 if self.message.is_empty() {
193 write!(f, "{}", self.code)?;
194 } else {
195 write!(f, "{}", self.message)?;
196 }
197
198 if let Some(data) = &self.data {
199 let pretty = serde_json::to_string_pretty(data).unwrap_or_else(|_| data.to_string());
200 write!(f, ": {pretty}")?;
201 }
202
203 Ok(())
204 }
205}
206
207impl From<anyhow::Error> for Error {
208 fn from(error: anyhow::Error) -> Self {
209 match error.downcast::<Self>() {
210 Ok(error) => error,
211 Err(error) => Error::into_internal_error(&*error),
212 }
213 }
214}
215
216impl From<serde_json::Error> for Error {
217 fn from(error: serde_json::Error) -> Self {
218 Error::invalid_params().with_data(error.to_string())
219 }
220}