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