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