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