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::{
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    /// Portable neuron collection or validation failed.
128    #[error(transparent)]
129    Neuron(#[from] NnsNeuronError),
130
131    /// A capped or stalled refresh stopped before proving API exhaustion.
132    #[error(
133        "NNS neuron refresh stopped after {pages_fetched} pages and {rows_fetched} rows: {reason}"
134    )]
135    IncompleteRefresh {
136        /// Pages retained before the stop.
137        pages_fetched: u32,
138        /// Rows retained before the stop.
139        rows_fetched: usize,
140        /// Completion invariant that failed.
141        reason: String,
142    },
143
144    /// A stored neuron snapshot did not match its cache key.
145    #[error(
146        "cached NNS neuron snapshot identity mismatch at {}: {field} is {actual}, expected {expected}",
147        path.display()
148    )]
149    CacheIdentityMismatch {
150        /// Cache path being validated.
151        path: PathBuf,
152        /// Identity field that did not match.
153        field: &'static str,
154        /// Identity required by the cache key.
155        expected: String,
156        /// Identity stored in the snapshot.
157        actual: String,
158    },
159
160    /// A stored neuron snapshot failed family-specific validation.
161    #[error("invalid NNS neuron cache at {}: {reason}", path.display())]
162    InvalidCache {
163        /// Cache path being validated.
164        path: PathBuf,
165        /// Cache invariant that failed.
166        reason: String,
167    },
168
169    /// A stored refresh-attempt sidecar failed identity or lifecycle validation.
170    #[error("invalid NNS neuron refresh attempt at {}: {reason}", path.display())]
171    InvalidRefreshAttempt {
172        /// Attempt sidecar path being validated.
173        path: PathBuf,
174        /// Attempt invariant that failed.
175        reason: String,
176    },
177
178    /// Shared cache IO or lock handling failed.
179    #[error(transparent)]
180    Cache(#[from] HostCacheError),
181
182    /// The synchronous host runtime could not execute the live query.
183    #[error(transparent)]
184    Runtime(#[from] RuntimeError),
185}
186
187#[cfg(feature = "nns-host")]
188impl From<NnsGovernanceAttemptReadError> for NnsNeuronHostError {
189    fn from(error: NnsGovernanceAttemptReadError) -> Self {
190        match error {
191            NnsGovernanceAttemptReadError::Cache(error) => Self::Cache(error),
192            NnsGovernanceAttemptReadError::Invalid { path, reason } => {
193                Self::InvalidRefreshAttempt { path, reason }
194            }
195        }
196    }
197}