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        /// Successfully fetched pages.
159        pages_fetched: u32,
160        /// Unique rows retained.
161        rows_fetched: usize,
162        /// Last exclusive cursor when present.
163        last_cursor: Option<String>,
164        /// Reason the collection could not be proven complete.
165        reason: String,
166    },
167
168    /// A page fetch failed after collection had begun.
169    #[error(
170        "ICRC account transaction collection failed after {pages_fetched} page(s) and {rows_fetched} row(s): {source}"
171    )]
172    CollectionPage {
173        /// Successfully fetched pages before the failure.
174        pages_fetched: u32,
175        /// Unique rows retained before the failure.
176        rows_fetched: usize,
177        /// Last exclusive cursor when present.
178        last_cursor: Option<String>,
179        /// Underlying typed page failure.
180        #[source]
181        source: Box<Self>,
182    },
183
184    /// A complete cache failed semantic validation.
185    #[error("invalid ICRC account transaction cache at {}: {reason}", path.display())]
186    InvalidCache {
187        /// Cache path.
188        path: PathBuf,
189        /// Validation failure.
190        reason: String,
191    },
192
193    /// A refresh-attempt sidecar failed semantic validation.
194    #[error(
195        "invalid ICRC account transaction refresh attempt at {}: {reason}",
196        path.display()
197    )]
198    InvalidRefreshAttempt {
199        /// Attempt sidecar path.
200        path: PathBuf,
201        /// Validation failure.
202        reason: String,
203    },
204
205    /// A cache load, lock, or atomic-write operation failed.
206    #[cfg(feature = "host")]
207    #[error(transparent)]
208    Cache(#[from] HostCacheError),
209}