Skip to main content

ic_query/nns/proposals/report/
error.rs

1//! Module: nns::proposals::report::error
2//!
3//! Responsibility: expose portable and native-wrapper NNS proposal failures.
4//! Does not own: transport execution, cache mechanics, or process presentation.
5//! Boundary: keeps canister/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/// NnsProposalError
18///
19/// Portable failure returned while collecting or assembling proposal reports.
20///
21
22#[derive(Debug, ThisError)]
23pub enum NnsProposalError {
24    /// Shared Governance request, transport, or provenance validation failed.
25    #[error(transparent)]
26    Governance(#[from] NnsGovernanceError),
27
28    /// The requested page size is outside the bounded proposal contract.
29    #[error("invalid NNS proposal page limit {limit}; expected 1..={maximum}")]
30    InvalidLimit { limit: u32, maximum: u32 },
31
32    /// Governance returned more proposal rows than requested.
33    #[error("NNS proposal page returned {actual} rows; requested at most {requested}")]
34    PageTooLarge { actual: usize, requested: u32 },
35
36    /// A proposal page row did not include its required identifier.
37    #[error("NNS proposal list page returned a row without a proposal id")]
38    MissingProposalIdInPage,
39
40    /// A proposal page used the reserved zero identifier.
41    #[error("NNS proposal list page returned proposal id zero")]
42    InvalidProposalIdInPage,
43
44    /// A proposal page repeated an identifier.
45    #[error("NNS proposal page returned duplicate proposal id {proposal_id}")]
46    DuplicateProposalId { proposal_id: u64 },
47
48    /// A proposal did not satisfy the exclusive page cursor.
49    #[error(
50        "NNS proposal page returned id {proposal_id}; expected every id below {before_proposal_id}"
51    )]
52    ProposalCursorMismatch {
53        proposal_id: u64,
54        before_proposal_id: u64,
55    },
56
57    /// Governance did not return the requested proposal.
58    #[error("NNS proposal {proposal_id} was not found")]
59    ProposalNotFound { proposal_id: u64 },
60
61    /// Governance returned a different proposal than requested.
62    #[error("NNS proposal detail returned id {actual:?}; expected {expected}")]
63    ProposalIdMismatch { expected: u64, actual: Option<u64> },
64
65    /// A resumable collection was configured without any page capacity.
66    #[error("invalid NNS proposal collection page limit 0; expected at least one page")]
67    InvalidCollectionMaxPages,
68
69    /// Serialized or caller-modified resumable collection state is inconsistent.
70    #[error("invalid NNS proposal collection state: {reason}")]
71    InvalidCollectionState {
72        /// Deterministic state invariant that failed.
73        reason: String,
74    },
75
76    /// A continuation request changed an identity fixed when collection started.
77    #[error(
78        "NNS proposal collection request changed {field}: received {actual}, expected {expected}"
79    )]
80    CollectionRequestMismatch {
81        /// Fixed request field that changed.
82        field: &'static str,
83        /// Value retained by the collection state.
84        expected: String,
85        /// Value supplied by the continuation request.
86        actual: String,
87    },
88
89    /// A caller attempted to advance a collection that already exhausted the API.
90    #[error("NNS proposal collection is already complete after {pages_fetched} pages")]
91    CollectionComplete {
92        /// Pages admitted before API exhaustion.
93        pages_fetched: u32,
94    },
95
96    /// A caller attempted to advance a collection after consuming its page budget.
97    #[error(
98        "NNS proposal collection reached its {max_pages}-page limit after {pages_fetched} pages"
99    )]
100    CollectionPageLimitReached {
101        /// Pages admitted before the stopped advance.
102        pages_fetched: u32,
103        /// Configured cumulative page ceiling.
104        max_pages: u32,
105    },
106
107    /// A later collection page was returned by a different collector.
108    #[error("NNS proposal collection source changed: received {actual:?}, expected {expected:?}")]
109    CollectionSourceChanged {
110        /// Concrete source retained from the first admitted page.
111        expected: NnsGovernanceSourceProvenance,
112        /// Concrete source returned with the candidate page.
113        actual: NnsGovernanceSourceProvenance,
114    },
115
116    /// Cumulative page or row accounting exceeded its integer representation.
117    #[error("NNS proposal collection accounting overflow")]
118    CollectionAccountingOverflow,
119}
120
121///
122/// NnsProposalHostError
123///
124/// Native wrapper failure for proposal live calls, caches, and refreshes.
125///
126
127#[cfg(feature = "nns-host")]
128#[derive(Debug, ThisError)]
129pub enum NnsProposalHostError {
130    /// Portable proposal collection or validation failed.
131    #[error(transparent)]
132    Proposal(#[from] NnsProposalError),
133
134    #[error(transparent)]
135    Cache(#[from] HostCacheError),
136
137    #[error(
138        "cached NNS proposal snapshot identity mismatch at {}: {field} is {actual}, expected {expected}",
139        path.display()
140    )]
141    CacheIdentityMismatch {
142        path: PathBuf,
143        field: &'static str,
144        expected: String,
145        actual: String,
146    },
147
148    #[error(
149        "NNS proposal refresh did not publish a complete snapshot after {pages_fetched} pages and {rows_fetched} rows: {reason}"
150    )]
151    IncompleteRefresh {
152        pages_fetched: u32,
153        rows_fetched: usize,
154        reason: String,
155    },
156
157    #[error("invalid NNS proposal refresh page size {page_size}; expected 1..={max_page_size}")]
158    InvalidRefreshPageSize { page_size: u32, max_page_size: u32 },
159
160    #[error("NNS proposals cache is missing at {}\n\nRun `icq nns proposal refresh` to fetch a complete snapshot.", path.display())]
161    MissingProposalCache { path: PathBuf },
162
163    #[error("invalid NNS proposal refresh attempt at {}: {reason}", path.display())]
164    InvalidRefreshAttempt { path: PathBuf, reason: String },
165
166    #[error("invalid NNS proposal cache at {}: {reason}", path.display())]
167    InvalidCache { path: PathBuf, reason: String },
168
169    #[error("failed to create Tokio runtime for NNS proposal query: {0}")]
170    Runtime(#[from] RuntimeError),
171}
172
173#[cfg(feature = "nns-host")]
174impl From<NnsGovernanceAttemptReadError> for NnsProposalHostError {
175    fn from(error: NnsGovernanceAttemptReadError) -> Self {
176        match error {
177            NnsGovernanceAttemptReadError::Cache(error) => Self::Cache(error),
178            NnsGovernanceAttemptReadError::Invalid { path, reason } => {
179                Self::InvalidRefreshAttempt { path, reason }
180            }
181        }
182    }
183}