Skip to main content

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}