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, 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}