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 cache view requested no rows.
84    #[error("invalid ICRC account transaction list limit {limit}; expected at least 1")]
85    InvalidListLimit {
86        /// Rejected view limit.
87        limit: u32,
88    },
89
90    /// A diagnostic refresh bound cannot prove any collection progress.
91    #[error("invalid ICRC account transaction max pages {max_pages}; expected at least 1")]
92    InvalidMaxPages {
93        /// Rejected page bound.
94        max_pages: u32,
95    },
96
97    /// A caller supplied a non-decimal or otherwise invalid candid Nat cursor.
98    #[error("invalid ICRC account transaction cursor {value:?}: {reason}")]
99    InvalidCursor {
100        /// Rejected cursor text.
101        value: String,
102        /// Validation failure.
103        reason: String,
104    },
105
106    /// A ledger, index, principal, Candid, or transport operation failed.
107    #[error(transparent)]
108    Query(#[from] IcrcError),
109
110    /// ICRC-106 discovery could not be queried or decoded.
111    #[error(
112        "failed to discover an index for ledger {ledger_canister_id}; supply an explicit index canister: {source}"
113    )]
114    IndexDiscovery {
115        /// Ledger queried for index discovery.
116        ledger_canister_id: String,
117        /// Underlying transport or Candid failure.
118        #[source]
119        source: IcrcError,
120    },
121
122    /// ICRC-106 discovery did not yield an index canister.
123    #[error("ledger {ledger_canister_id} has no usable ICRC index: {reason}")]
124    IndexUnavailable {
125        /// Ledger queried for index discovery.
126        ledger_canister_id: String,
127        /// Discovery result explaining why no index is usable.
128        reason: String,
129    },
130
131    /// The selected index reports a different ledger identity.
132    #[error(
133        "ICRC index {index_canister_id} reports ledger {actual_ledger_canister_id}, expected {expected_ledger_canister_id}"
134    )]
135    IndexLedgerMismatch {
136        /// Index whose `ledger_id` response was checked.
137        index_canister_id: String,
138        /// Ledger requested by the caller.
139        expected_ledger_canister_id: String,
140        /// Ledger reported by the index.
141        actual_ledger_canister_id: String,
142    },
143
144    /// The index returned an application-level account-history error.
145    #[error("ICRC index {index_canister_id} account transaction query failed: {message}")]
146    IndexQuery {
147        /// Index that returned the error.
148        index_canister_id: String,
149        /// Index-provided error message.
150        message: String,
151    },
152
153    /// Complete collection stopped before the source API was exhausted.
154    #[error(
155        "incomplete ICRC account transaction collection after {pages_fetched} page(s) and {rows_fetched} row(s): {reason}"
156    )]
157    IncompleteCollection {
158        /// Verified index used for collection when resolution completed.
159        index_canister_id: Option<String>,
160        /// Successfully fetched pages.
161        pages_fetched: u32,
162        /// Rows retained.
163        rows_fetched: usize,
164        /// Last exclusive cursor when present.
165        last_cursor: Option<String>,
166        /// Reason the collection could not be proven complete.
167        reason: String,
168    },
169
170    /// A page fetch failed after collection had begun.
171    #[error(
172        "ICRC account transaction collection failed after {pages_fetched} page(s) and {rows_fetched} row(s): {source}"
173    )]
174    CollectionPage {
175        /// Verified index used for collection when resolution completed.
176        index_canister_id: Option<String>,
177        /// Successfully fetched pages before the failure.
178        pages_fetched: u32,
179        /// Rows retained before the failure.
180        rows_fetched: usize,
181        /// Last exclusive cursor when present.
182        last_cursor: Option<String>,
183        /// Underlying typed page failure.
184        #[source]
185        source: Box<Self>,
186    },
187
188    /// A custom collection source returned evidence for a different explicit index.
189    #[error(
190        "ICRC account transaction source returned index {actual_index_canister_id}, expected explicitly requested index {expected_index_canister_id}"
191    )]
192    CollectionIndexMismatch {
193        /// Index explicitly requested by the caller.
194        expected_index_canister_id: String,
195        /// Index claimed by the completed collection.
196        actual_index_canister_id: String,
197    },
198
199    /// A complete cache failed semantic validation.
200    #[error("invalid ICRC account transaction cache at {}: {reason}", path.display())]
201    InvalidCache {
202        /// Cache path.
203        path: PathBuf,
204        /// Validation failure.
205        reason: String,
206    },
207
208    /// A refresh-attempt sidecar failed semantic validation.
209    #[error(
210        "invalid ICRC account transaction refresh attempt at {}: {reason}",
211        path.display()
212    )]
213    InvalidRefreshAttempt {
214        /// Attempt sidecar path.
215        path: PathBuf,
216        /// Validation failure.
217        reason: String,
218    },
219
220    /// A cache load, lock, or atomic-write operation failed.
221    #[cfg(feature = "host")]
222    #[error(transparent)]
223    Cache(#[from] HostCacheError),
224}