1use crate::{
4 agent::{replica_api::RejectResponse, status::Status},
5 RequestIdError,
6};
7use ic_certification::Label;
8use leb128::read;
9use std::{
10 fmt::{Debug, Display, Formatter},
11 str::Utf8Error,
12};
13use thiserror::Error;
14
15#[derive(Error, Debug)]
17pub enum AgentError {
18 #[error(r#"Invalid Replica URL: "{0}""#)]
20 InvalidReplicaUrl(String),
21
22 #[error("The request timed out.")]
24 TimeoutWaitingForResponse(),
25
26 #[error("Identity had a signing error: {0}")]
28 SigningError(String),
29
30 #[error("Invalid CBOR data, could not deserialize: {0}")]
32 InvalidCborData(#[from] serde_cbor::Error),
33
34 #[error("Cannot calculate a RequestID: {0}")]
36 CannotCalculateRequestId(#[from] RequestIdError),
37
38 #[error("Candid returned an error: {0}")]
40 CandidError(Box<dyn Send + Sync + std::error::Error>),
41
42 #[error(r#"Cannot parse url: "{0}""#)]
44 UrlParseError(#[from] url::ParseError),
45
46 #[error(r#"Invalid method: "{0}""#)]
48 InvalidMethodError(#[from] http::method::InvalidMethod),
49
50 #[error("Cannot parse Principal: {0}")]
52 PrincipalError(#[from] crate::export::PrincipalError),
53
54 #[error("The replica returned a replica error: {0}")]
56 ReplicaError(RejectResponse),
57
58 #[error("The replica returned an HTTP Error: {0}")]
60 HttpError(HttpErrorPayload),
61
62 #[error("Status endpoint returned an invalid status.")]
64 InvalidReplicaStatus,
65
66 #[error("Call was marked as done but we never saw the reply. Request ID: {0}")]
68 RequestStatusDoneNoReply(String),
69
70 #[error("A tool returned a string message error: {0}")]
72 MessageError(String),
73
74 #[error("Error reading LEB128 value: {0}")]
76 Leb128ReadError(#[from] read::Error),
77
78 #[error("Error in UTF-8 string: {0}")]
80 Utf8ReadError(#[from] Utf8Error),
81
82 #[error("The lookup path ({0:?}) is absent in the certificate.")]
84 LookupPathAbsent(Vec<Label>),
85
86 #[error("The lookup path ({0:?}) is unknown in the certificate.")]
88 LookupPathUnknown(Vec<Label>),
89
90 #[error("The lookup path ({0:?}) does not make sense for the certificate.")]
92 LookupPathError(Vec<Label>),
93
94 #[error("The request status ({1}) at path {0:?} is invalid.")]
96 InvalidRequestStatus(Vec<Label>, String),
97
98 #[error("Certificate verification failed.")]
100 CertificateVerificationFailed(),
101
102 #[error("Certificate is not authorized to respond to queries for this canister. While developing: Did you forget to set effective_canister_id?")]
104 CertificateNotAuthorized(),
105
106 #[error(
108 r#"BLS DER-encoded public key must be ${expected} bytes long, but is {actual} bytes long."#
109 )]
110 DerKeyLengthMismatch {
111 expected: usize,
113 actual: usize,
115 },
116
117 #[error("BLS DER-encoded public key is invalid. Expected the following prefix: ${expected:?}, but got ${actual:?}")]
119 DerPrefixMismatch {
120 expected: Vec<u8>,
122 actual: Vec<u8>,
124 },
125
126 #[error("The status response did not contain a root key. Status: {0}")]
128 NoRootKeyInStatus(Status),
129
130 #[error("The invocation to the wallet call forward method failed with the error: {0}")]
132 WalletCallFailed(String),
133
134 #[error("The wallet operation failed: {0}")]
136 WalletError(String),
137
138 #[error("The wallet canister must be upgraded: {0}")]
140 WalletUpgradeRequired(String),
141
142 #[error("Missing replica transport in the Agent Builder.")]
144 MissingReplicaTransport(),
145
146 #[error("Response size exceeded limit.")]
148 ResponseSizeExceededLimit(),
149
150 #[error("An error happened during communication with the replica: {0}")]
152 TransportError(Box<dyn std::error::Error + Send + Sync>),
153
154 #[error("There is a mismatch between the CBOR encoded call and the arguments: field {field}, value in argument is {value_arg}, value in CBOR is {value_cbor}")]
156 CallDataMismatch {
157 field: String,
159 value_arg: String,
161 value_cbor: String,
163 },
164}
165
166impl PartialEq for AgentError {
167 fn eq(&self, other: &Self) -> bool {
168 format!("{:?}", self) == format!("{:?}", other)
171 }
172}
173
174impl Display for RejectResponse {
175 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
176 f.write_fmt(format_args!(
177 "Replica Error: reject code {:?}, reject message {}, error code {:?}",
178 self.reject_code, self.reject_message, self.error_code,
179 ))
180 }
181}
182
183pub struct HttpErrorPayload {
185 pub status: u16,
187 pub content_type: Option<String>,
189 pub content: Vec<u8>,
191}
192
193impl HttpErrorPayload {
194 fn fmt_human_readable(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
195 f.write_fmt(format_args!(
199 "Http Error: status {}, content type {:?}, content: {}",
200 http::StatusCode::from_u16(self.status)
201 .map_or_else(|_| format!("{}", self.status), |code| format!("{}", code)),
202 self.content_type.clone().unwrap_or_default(),
203 String::from_utf8(self.content.clone()).unwrap_or_else(|_| format!(
204 "(unable to decode content as UTF-8: {:?})",
205 self.content
206 ))
207 ))?;
208 Ok(())
209 }
210}
211
212impl Debug for HttpErrorPayload {
213 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
214 self.fmt_human_readable(f)
215 }
216}
217
218impl Display for HttpErrorPayload {
219 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
220 self.fmt_human_readable(f)
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::HttpErrorPayload;
227 use crate::AgentError;
228
229 #[test]
230 fn content_type_none_valid_utf8() {
231 let payload = HttpErrorPayload {
232 status: 420,
233 content_type: None,
234 content: vec![104, 101, 108, 108, 111],
235 };
236
237 assert_eq!(
238 format!("{}", AgentError::HttpError(payload)),
239 r#"The replica returned an HTTP Error: Http Error: status 420 <unknown status code>, content type "", content: hello"#,
240 );
241 }
242
243 #[test]
244 fn content_type_none_invalid_utf8() {
245 let payload = HttpErrorPayload {
246 status: 420,
247 content_type: None,
248 content: vec![195, 40],
249 };
250
251 assert_eq!(
252 format!("{}", AgentError::HttpError(payload)),
253 r#"The replica returned an HTTP Error: Http Error: status 420 <unknown status code>, content type "", content: (unable to decode content as UTF-8: [195, 40])"#,
254 );
255 }
256
257 #[test]
258 fn formats_text_plain() {
259 let payload = HttpErrorPayload {
260 status: 420,
261 content_type: Some("text/plain".to_string()),
262 content: vec![104, 101, 108, 108, 111],
263 };
264
265 assert_eq!(
266 format!("{}", AgentError::HttpError(payload)),
267 r#"The replica returned an HTTP Error: Http Error: status 420 <unknown status code>, content type "text/plain", content: hello"#,
268 );
269 }
270
271 #[test]
272 fn formats_text_plain_charset_utf8() {
273 let payload = HttpErrorPayload {
274 status: 420,
275 content_type: Some("text/plain; charset=utf-8".to_string()),
276 content: vec![104, 101, 108, 108, 111],
277 };
278
279 assert_eq!(
280 format!("{}", AgentError::HttpError(payload)),
281 r#"The replica returned an HTTP Error: Http Error: status 420 <unknown status code>, content type "text/plain; charset=utf-8", content: hello"#,
282 );
283 }
284
285 #[test]
286 fn formats_text_html() {
287 let payload = HttpErrorPayload {
288 status: 420,
289 content_type: Some("text/html".to_string()),
290 content: vec![119, 111, 114, 108, 100],
291 };
292
293 assert_eq!(
294 format!("{}", AgentError::HttpError(payload)),
295 r#"The replica returned an HTTP Error: Http Error: status 420 <unknown status code>, content type "text/html", content: world"#,
296 );
297 }
298}