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}