ic_query/nns/neuron/report/error.rs
1//! Module: nns::neuron::report::error
2//!
3//! Responsibility: expose portable and native-wrapper NNS neuron failures.
4//! Does not own: transport execution, cache mechanics, or process presentation.
5//! Boundary: keeps canister and custom callers independent of native host dependencies.
6
7use crate::nns::governance::{NnsGovernanceError, NnsGovernanceSourceProvenance};
8#[cfg(feature = "nns-host")]
9use crate::{
10 HostCacheError, nns::governance::NnsGovernanceAttemptReadError, runtime::RuntimeError,
11};
12#[cfg(feature = "nns-host")]
13use std::path::PathBuf;
14use thiserror::Error as ThisError;
15
16///
17/// NnsNeuronError
18///
19/// Portable failure returned while collecting or assembling neuron reports.
20///
21
22#[derive(Debug, ThisError)]
23pub enum NnsNeuronError {
24 /// Shared Governance request, transport, or provenance validation failed.
25 #[error(transparent)]
26 Governance(#[from] NnsGovernanceError),
27
28 /// The requested page size is outside Governance's supported range.
29 #[error("invalid NNS neuron page size {page_size}; expected 1..={max_page_size}")]
30 InvalidPageSize {
31 /// Rejected page size.
32 page_size: u32,
33 /// Largest page supported by Governance.
34 max_page_size: u32,
35 },
36
37 /// Governance returned its typed application-level error.
38 #[error("NNS Governance rejected the neuron query with code {error_type}: {message}")]
39 GovernanceResponse {
40 /// Raw Governance error type.
41 error_type: i32,
42 /// Governance error message.
43 message: String,
44 },
45
46 /// Governance has no publicly readable view for the requested neuron id.
47 #[error("NNS neuron {neuron_id} was not found")]
48 NeuronNotFound {
49 /// Requested neuron identifier.
50 neuron_id: u64,
51 },
52
53 /// Governance returned a neuron row without its required identifier.
54 #[error("NNS Governance returned a neuron row without an id")]
55 MissingNeuronId,
56
57 /// A source page or detail row violated the neuron contract.
58 #[error("invalid NNS neuron response: {reason}")]
59 InvalidResponse {
60 /// Response invariant that failed.
61 reason: String,
62 },
63
64 /// A resumable collection was configured without any page capacity.
65 #[error("invalid NNS neuron collection page limit 0; expected at least one page")]
66 InvalidCollectionMaxPages,
67
68 /// Serialized or caller-modified resumable collection state is inconsistent.
69 #[error("invalid NNS neuron collection state: {reason}")]
70 InvalidCollectionState {
71 /// Deterministic state invariant that failed.
72 reason: String,
73 },
74
75 /// A continuation request changed an identity fixed when collection started.
76 #[error(
77 "NNS neuron collection request changed {field}: received {actual}, expected {expected}"
78 )]
79 CollectionRequestMismatch {
80 /// Fixed request field that changed.
81 field: &'static str,
82 /// Value retained by the collection state.
83 expected: String,
84 /// Value supplied by the continuation request.
85 actual: String,
86 },
87
88 /// A caller attempted to advance a collection that already exhausted the API.
89 #[error("NNS neuron collection is already complete after {pages_fetched} pages")]
90 CollectionComplete {
91 /// Pages admitted before API exhaustion.
92 pages_fetched: u32,
93 },
94
95 /// A caller attempted to advance a collection after consuming its page budget.
96 #[error("NNS neuron collection reached its {max_pages}-page limit after {pages_fetched} pages")]
97 CollectionPageLimitReached {
98 /// Pages admitted before the stopped advance.
99 pages_fetched: u32,
100 /// Configured cumulative page ceiling.
101 max_pages: u32,
102 },
103
104 /// A later collection page was returned by a different collector.
105 #[error("NNS neuron collection source changed: received {actual:?}, expected {expected:?}")]
106 CollectionSourceChanged {
107 /// Concrete source retained from the first admitted page.
108 expected: NnsGovernanceSourceProvenance,
109 /// Concrete source returned with the candidate page.
110 actual: NnsGovernanceSourceProvenance,
111 },
112
113 /// Cumulative page or row accounting exceeded its integer representation.
114 #[error("NNS neuron collection accounting overflow")]
115 CollectionAccountingOverflow,
116}
117
118///
119/// NnsNeuronHostError
120///
121/// Native wrapper failure for neuron live calls, caches, and refreshes.
122///
123
124#[cfg(feature = "nns-host")]
125#[derive(Debug, ThisError)]
126pub enum NnsNeuronHostError {
127 /// A cache-only read could not find the required complete snapshot.
128 #[error("NNS neurons cache is missing at {}\n\nRun `icq nns neuron refresh` to fetch a complete snapshot.", path.display())]
129 MissingNeuronCache {
130 /// Expected complete snapshot path.
131 path: PathBuf,
132 },
133
134 /// Local distribution input or aggregation failed.
135 #[error(transparent)]
136 Distribution(#[from] super::NnsNeuronDistributionError),
137
138 /// Portable neuron collection or validation failed.
139 #[error(transparent)]
140 Neuron(#[from] NnsNeuronError),
141
142 /// A capped or stalled refresh stopped before proving API exhaustion.
143 #[error(
144 "NNS neuron refresh stopped after {pages_fetched} pages and {rows_fetched} rows: {reason}"
145 )]
146 IncompleteRefresh {
147 /// Pages retained before the stop.
148 pages_fetched: u32,
149 /// Rows retained before the stop.
150 rows_fetched: usize,
151 /// Completion invariant that failed.
152 reason: String,
153 },
154
155 /// A stored neuron snapshot did not match its cache key.
156 #[error(
157 "cached NNS neuron snapshot identity mismatch at {}: {field} is {actual}, expected {expected}",
158 path.display()
159 )]
160 CacheIdentityMismatch {
161 /// Cache path being validated.
162 path: PathBuf,
163 /// Identity field that did not match.
164 field: &'static str,
165 /// Identity required by the cache key.
166 expected: String,
167 /// Identity stored in the snapshot.
168 actual: String,
169 },
170
171 /// A stored neuron snapshot failed family-specific validation.
172 #[error("invalid NNS neuron cache at {}: {reason}", path.display())]
173 InvalidCache {
174 /// Cache path being validated.
175 path: PathBuf,
176 /// Cache invariant that failed.
177 reason: String,
178 },
179
180 /// A stored refresh-attempt sidecar failed identity or lifecycle validation.
181 #[error("invalid NNS neuron refresh attempt at {}: {reason}", path.display())]
182 InvalidRefreshAttempt {
183 /// Attempt sidecar path being validated.
184 path: PathBuf,
185 /// Attempt invariant that failed.
186 reason: String,
187 },
188
189 /// Shared cache IO or lock handling failed.
190 #[error(transparent)]
191 Cache(#[from] HostCacheError),
192
193 /// The synchronous host runtime could not execute the live query.
194 #[error(transparent)]
195 Runtime(#[from] RuntimeError),
196}
197
198#[cfg(feature = "nns-host")]
199impl From<NnsGovernanceAttemptReadError> for NnsNeuronHostError {
200 fn from(error: NnsGovernanceAttemptReadError) -> Self {
201 match error {
202 NnsGovernanceAttemptReadError::Cache(error) => Self::Cache(error),
203 NnsGovernanceAttemptReadError::Invalid { path, reason } => {
204 Self::InvalidRefreshAttempt { path, reason }
205 }
206 }
207 }
208}