ic_query/icrc/model/error.rs
1//! Module: icrc::model::error
2//!
3//! Responsibility: typed errors for generic ICRC parsing, reports, and live calls.
4//! Does not own: command dispatch, host calls, or output policy.
5//! Boundary: preserves one public error surface for reusable ICRC query behavior.
6
7#[cfg(feature = "icrc-host")]
8use crate::{HostCacheError, runtime::RuntimeError};
9use std::path::PathBuf;
10use thiserror::Error as ThisError;
11
12///
13/// IcrcError
14///
15/// Error surfaced by generic ICRC validation, report building, and live calls.
16///
17
18#[derive(Debug, ThisError)]
19pub enum IcrcError {
20 /// The native source runtime could not be constructed.
21 #[cfg(feature = "icrc-host")]
22 #[error("failed to create Tokio runtime for ICRC query: {0}")]
23 Runtime(#[from] RuntimeError),
24
25 /// The selected endpoint could not construct a replica agent.
26 #[error("failed to build IC agent for endpoint {endpoint}: {reason}")]
27 AgentBuild {
28 /// Rejected replica endpoint.
29 endpoint: String,
30 /// Agent construction failure.
31 reason: String,
32 },
33
34 /// A supplied principal field could not be parsed.
35 #[error("invalid {field}: {reason}")]
36 InvalidPrincipal {
37 /// Request field containing the principal.
38 field: &'static str,
39 /// Principal parsing failure.
40 reason: String,
41 },
42
43 /// The supplied subaccount is not valid hexadecimal.
44 #[error("invalid subaccount hex: {reason}")]
45 InvalidSubaccountHex {
46 /// Hexadecimal decoding failure.
47 reason: String,
48 },
49
50 /// The decoded subaccount does not contain exactly 32 bytes.
51 #[error("invalid subaccount length: expected 32 bytes, got {bytes}")]
52 InvalidSubaccountLength {
53 /// Observed decoded subaccount length.
54 bytes: usize,
55 },
56
57 /// Ledger or archive evidence violates the requested page bounds or callback provenance.
58 #[error("invalid ICRC transaction page: {reason}")]
59 InvalidTransactionPage {
60 /// Source-result invariant that failed.
61 reason: String,
62 },
63
64 /// The typed Candid request could not be encoded.
65 #[error("failed to encode Candid request for {message}: {reason}")]
66 CandidEncode {
67 /// Wire operation whose encoding or decoding failed.
68 message: &'static str,
69 /// Transport, encoding, or decoding failure detail.
70 reason: String,
71 },
72
73 /// The read-only replica query failed.
74 #[error("ICRC ledger method {method} failed: {reason}")]
75 AgentCall {
76 /// Replica method or archive callback context.
77 method: &'static str,
78 /// Transport, encoding, or decoding failure detail.
79 reason: String,
80 },
81
82 /// The response violates its typed Candid contract or decoder budget.
83 #[error("failed to decode Candid response {message}: {reason}")]
84 CandidDecode {
85 /// Wire operation whose encoding or decoding failed.
86 message: &'static str,
87 /// Transport, encoding, or decoding failure detail.
88 reason: String,
89 },
90}
91
92///
93/// IcrcAccountTransactionError
94///
95/// Error surfaced while resolving and querying an ICRC account index.
96///
97
98#[derive(Debug, ThisError)]
99pub enum IcrcAccountTransactionError {
100 /// A cache identity omitted its endpoint.
101 #[error("invalid ICRC account transaction source endpoint {value:?}: {reason}")]
102 InvalidSourceEndpoint {
103 /// Rejected endpoint.
104 value: String,
105 /// Validation failure.
106 reason: String,
107 },
108
109 /// A page or refresh requested an unsupported page size.
110 #[error(
111 "invalid ICRC account transaction page size {page_size}; expected between 1 and {max_page_size}"
112 )]
113 InvalidPageSize {
114 /// Rejected page size.
115 page_size: u32,
116 /// Largest supported page size.
117 max_page_size: u32,
118 },
119
120 /// A custom page source returned data that violates the public page contract.
121 #[error("invalid ICRC account transaction page: {reason}")]
122 InvalidPage {
123 /// Source-result invariant that failed.
124 reason: String,
125 },
126
127 /// A cache view requested no rows.
128 #[error("invalid ICRC account transaction list limit {limit}; expected at least 1")]
129 InvalidListLimit {
130 /// Rejected view limit.
131 limit: u32,
132 },
133
134 /// A diagnostic refresh bound cannot prove any collection progress.
135 #[error("invalid ICRC account transaction max pages {max_pages}; expected at least 1")]
136 InvalidMaxPages {
137 /// Rejected page bound.
138 max_pages: u32,
139 },
140
141 /// A caller supplied a non-decimal or otherwise invalid candid Nat cursor.
142 #[error("invalid ICRC account transaction cursor {value:?}: {reason}")]
143 InvalidCursor {
144 /// Rejected cursor text.
145 value: String,
146 /// Validation failure.
147 reason: String,
148 },
149
150 /// A ledger, index, principal, Candid, or transport operation failed.
151 #[error(transparent)]
152 Query(#[from] IcrcError),
153
154 /// ICRC-106 discovery could not be queried or decoded.
155 #[error(
156 "failed to discover an index for ledger {ledger_canister_id}; supply an explicit index canister: {source}"
157 )]
158 IndexDiscovery {
159 /// Ledger queried for index discovery.
160 ledger_canister_id: String,
161 /// Underlying transport or Candid failure.
162 #[source]
163 source: IcrcError,
164 },
165
166 /// ICRC-106 discovery did not yield an index canister.
167 #[error("ledger {ledger_canister_id} has no usable ICRC index: {reason}")]
168 IndexUnavailable {
169 /// Ledger queried for index discovery.
170 ledger_canister_id: String,
171 /// Discovery result explaining why no index is usable.
172 reason: String,
173 },
174
175 /// The selected index reports a different ledger identity.
176 #[error(
177 "ICRC index {index_canister_id} reports ledger {actual_ledger_canister_id}, expected {expected_ledger_canister_id}"
178 )]
179 IndexLedgerMismatch {
180 /// Index whose `ledger_id` response was checked.
181 index_canister_id: String,
182 /// Ledger requested by the caller.
183 expected_ledger_canister_id: String,
184 /// Ledger reported by the index.
185 actual_ledger_canister_id: String,
186 },
187
188 /// The index returned an application-level account-history error.
189 #[error("ICRC index {index_canister_id} account transaction query failed: {message}")]
190 IndexQuery {
191 /// Index that returned the error.
192 index_canister_id: String,
193 /// Index-provided error message.
194 message: String,
195 },
196
197 /// Complete collection stopped before the source API was exhausted.
198 #[error(
199 "incomplete ICRC account transaction collection after {pages_fetched} page(s) and {rows_fetched} row(s): {reason}"
200 )]
201 IncompleteCollection {
202 /// Verified index used for collection when resolution completed.
203 index_canister_id: Option<String>,
204 /// Successfully fetched pages.
205 pages_fetched: u32,
206 /// Rows retained.
207 rows_fetched: usize,
208 /// Last exclusive cursor when present.
209 last_cursor: Option<String>,
210 /// Reason the collection could not be proven complete.
211 reason: String,
212 },
213
214 /// A page fetch failed after collection had begun.
215 #[error(
216 "ICRC account transaction collection failed after {pages_fetched} page(s) and {rows_fetched} row(s): {source}"
217 )]
218 CollectionPage {
219 /// Verified index used for collection when resolution completed.
220 index_canister_id: Option<String>,
221 /// Successfully fetched pages before the failure.
222 pages_fetched: u32,
223 /// Rows retained before the failure.
224 rows_fetched: usize,
225 /// Last exclusive cursor when present.
226 last_cursor: Option<String>,
227 /// Underlying typed page failure.
228 #[source]
229 source: Box<Self>,
230 },
231
232 /// A custom account-transaction source returned evidence for a different explicit index.
233 #[error(
234 "ICRC account transaction source returned index {actual_index_canister_id}, expected explicitly requested index {expected_index_canister_id}"
235 )]
236 CollectionIndexMismatch {
237 /// Index explicitly requested by the caller.
238 expected_index_canister_id: String,
239 /// Index claimed by the completed collection.
240 actual_index_canister_id: String,
241 },
242
243 /// A complete cache failed semantic validation.
244 #[error("invalid ICRC account transaction cache at {}: {reason}", path.display())]
245 InvalidCache {
246 /// Cache path.
247 path: PathBuf,
248 /// Validation failure.
249 reason: String,
250 },
251
252 /// A refresh-attempt sidecar failed semantic validation.
253 #[error(
254 "invalid ICRC account transaction refresh attempt at {}: {reason}",
255 path.display()
256 )]
257 InvalidRefreshAttempt {
258 /// Attempt sidecar path.
259 path: PathBuf,
260 /// Validation failure.
261 reason: String,
262 },
263
264 /// A cache load, lock, or atomic-write operation failed.
265 #[cfg(feature = "icrc-host")]
266 #[error(transparent)]
267 Cache(#[from] HostCacheError),
268}