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