Skip to main content

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}