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