Skip to main content

ic_query/nns/proposals/report/model/selection/
mod.rs

1//! Module: nns::proposals::report::model::selection
2//!
3//! Responsibility: NNS proposal filter, sort, and protocol vocabulary.
4//! Does not own: request DTOs, serialized report DTOs, or report rendering.
5//! Boundary: keeps public selection types and their canonical labels/codes together.
6
7use crate::report::SortDirection;
8use serde::{Deserialize, Serialize};
9
10macro_rules! governance_code_enum {
11    (
12        $(#[$enum_attr:meta])*
13        pub enum $name:ident {
14            $(#[$unspecified_attr:meta])* Unspecified = 0 => "unspecified",
15            $($(#[$variant_attr:meta])* $variant:ident = $code:literal => $label:literal,)*
16        }
17        filter $filter:ident;
18    ) => {
19        $(#[$enum_attr])*
20        pub enum $name {
21            $(#[$unspecified_attr])* #[serde(rename = "unspecified")] Unspecified,
22            $($(#[$variant_attr])* #[serde(rename = $label)] $variant,)*
23        }
24
25        impl $name {
26            /// Classify a native code, retaining the unspecified classification for unknown codes.
27            #[must_use]
28            pub const fn from_code(code: i32) -> Self {
29                match code {
30                    $($code => Self::$variant,)*
31                    _ => Self::Unspecified,
32                }
33            }
34
35            /// Return the canonical native code for this classification.
36            #[must_use]
37            pub const fn code(self) -> i32 {
38                match self { Self::Unspecified => 0, $(Self::$variant => $code,)* }
39            }
40
41            /// Return the stable JSON and text label.
42            #[must_use]
43            pub const fn as_str(self) -> &'static str {
44                match self { Self::Unspecified => "unspecified", $(Self::$variant => $label,)* }
45            }
46        }
47        ///
48        #[doc = stringify!($filter)]
49        ///
50        /// Selection of one known proposal classification or all proposals.
51        ///
52
53        #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
54        pub enum $filter {
55            /// Do not restrict this classification.
56            #[default]
57            Any,
58            $($(#[$variant_attr])* $variant,)*
59        }
60
61        impl $filter {
62            /// All supported filter choices in presentation order.
63            pub const ALL: &'static [Self] = &[Self::Any, $(Self::$variant,)*];
64
65            /// Return the canonical filter label.
66            #[must_use]
67            pub const fn as_str(self) -> &'static str {
68                match self.classification() { None => "any", Some(value) => value.as_str() }
69            }
70
71            const fn classification(self) -> Option<$name> {
72                match self { Self::Any => None, $(Self::$variant => Some($name::$variant),)* }
73            }
74        }
75    };
76}
77
78governance_code_enum! {
79    ///
80    /// NnsProposalStatus
81    ///
82    /// Native NNS Governance proposal decision status.
83    ///
84
85    #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
86    pub enum NnsProposalStatus {
87        /// Unspecified or unrecognized native status code.
88        Unspecified = 0 => "unspecified",
89        /// Proposal remains open for voting.
90        Open = 1 => "open",
91        /// Proposal was rejected.
92        Rejected = 2 => "rejected",
93        /// Proposal was adopted but has not completed execution.
94        Adopted = 3 => "adopted",
95        /// Proposal executed successfully.
96        Executed = 4 => "executed",
97        /// Proposal execution failed.
98        Failed = 5 => "failed",
99    }
100    filter NnsProposalStatusFilter;
101}
102
103governance_code_enum! {
104    ///
105    /// NnsProposalRewardStatus
106    ///
107    /// Native NNS Governance proposal reward-settlement status.
108    ///
109
110    #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
111    pub enum NnsProposalRewardStatus {
112        /// Unspecified or unrecognized native reward-status code.
113        Unspecified = 0 => "unspecified",
114        /// Proposal still accepts votes for reward purposes.
115        AcceptVotes = 1 => "accept-votes",
116        /// Proposal is ready for reward settlement.
117        ReadyToSettle = 2 => "ready-to-settle",
118        /// Proposal rewards have settled.
119        Settled = 3 => "settled",
120        /// Proposal is not eligible for voting rewards.
121        Ineligible = 4 => "ineligible",
122    }
123    filter NnsProposalRewardStatusFilter;
124}
125
126governance_code_enum! {
127    ///
128    /// NnsProposalTopic
129    ///
130    /// Native NNS Governance proposal topic.
131    ///
132
133    #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
134    pub enum NnsProposalTopic {
135        /// Unspecified or unrecognized native topic code.
136        Unspecified = 0 => "unspecified",
137        /// Neuron management.
138        NeuronManagement = 1 => "neuron-management",
139        /// Exchange-rate management.
140        ExchangeRate = 2 => "exchange-rate",
141        /// Network economics.
142        NetworkEconomics = 3 => "network-economics",
143        /// Governance policy.
144        Governance = 4 => "governance",
145        /// Node administration.
146        NodeAdmin = 5 => "node-admin",
147        /// Participant management.
148        ParticipantManagement = 6 => "participant-management",
149        /// Subnet management.
150        SubnetManagement = 7 => "subnet-management",
151        /// Network-canister management.
152        NetworkCanisterManagement = 8 => "network-canister-management",
153        /// Know-your-customer policy.
154        Kyc = 9 => "kyc",
155        /// Node-provider rewards.
156        NodeProviderRewards = 10 => "node-provider-rewards",
157        /// IC OS version deployment.
158        IcOsVersionDeployment = 12 => "ic-os-version-deployment",
159        /// IC OS version election.
160        IcOsVersionElection = 13 => "ic-os-version-election",
161        /// SNS and Community Fund policy.
162        SnsAndCommunityFund = 14 => "sns-and-community-fund",
163        /// API boundary-node management.
164        ApiBoundaryNodeManagement = 15 => "api-boundary-node-management",
165        /// Subnet rental.
166        SubnetRental = 16 => "subnet-rental",
167        /// Application-canister management.
168        ApplicationCanisterManagement = 17 => "application-canister-management",
169        /// Protocol-canister management.
170        ProtocolCanisterManagement = 18 => "protocol-canister-management",
171    }
172    filter NnsProposalTopicFilter;
173}
174
175macro_rules! proposal_sort {
176    ($($(#[$attr:meta])* $variant:ident => $label:literal,)*) => {
177        ///
178        /// NnsProposalListSort
179        ///
180        /// Report-model sort selector for NNS proposal listings.
181        ///
182
183        #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
184        pub enum NnsProposalListSort { $($(#[$attr])* $variant,)* }
185
186        impl NnsProposalListSort {
187            /// All supported sort choices in presentation order.
188            pub const ALL: &'static [Self] = &[$(Self::$variant,)*];
189
190            /// Return the canonical sort label.
191            #[must_use]
192            pub const fn as_str(self) -> &'static str {
193                match self { $(Self::$variant => $label,)* }
194            }
195        }
196    };
197}
198
199proposal_sort! {
200    /// Preserve the descending proposal-id order returned by Governance.
201    #[default]
202    Api => "api",
203    /// Sort by proposal id.
204    Id => "id",
205    /// Sort by the raw decision-status code.
206    Status => "status",
207    /// Sort by the raw reward-settlement code.
208    RewardStatus => "reward-status",
209    /// Sort by the raw topic code.
210    Topic => "topic",
211    /// Sort by proposer neuron id.
212    Proposer => "proposer",
213    /// Sort by proposal title.
214    Title => "title",
215    /// Sort by the human-readable action classification.
216    Action => "action",
217    /// Sort by affirmative voting power in the latest tally.
218    Yes => "yes",
219    /// Sort by negative voting power in the latest tally.
220    No => "no",
221    /// Sort by total voting power in the latest tally.
222    TotalVotes => "total-votes",
223    /// Sort by the latest tally timestamp.
224    TallyTime => "tally-time",
225    /// Sort by total potential voting power.
226    VotingPower => "voting-power",
227    /// Sort by ballot count returned by Governance.
228    Ballots => "ballots",
229    /// Sort by reject cost in ICP e8s.
230    RejectCost => "reject-cost",
231    /// Sort by reward event round.
232    RewardRound => "reward-round",
233    /// Sort by proposal creation time.
234    Proposed => "proposed",
235    /// Sort by voting deadline.
236    Deadline => "deadline",
237    /// Sort by decision time.
238    Decided => "decided",
239    /// Sort by execution time.
240    Executed => "executed",
241    /// Sort by execution failure time.
242    Failed => "failed",
243}
244
245impl NnsProposalListSort {
246    /// Return the default direction for this sort key.
247    #[must_use]
248    pub const fn default_direction(self) -> SortDirection {
249        match self {
250            Self::Status
251            | Self::RewardStatus
252            | Self::Topic
253            | Self::Proposer
254            | Self::Title
255            | Self::Action => SortDirection::Asc,
256            _ => SortDirection::Desc,
257        }
258    }
259
260    /// Return whether this key applies a local sort direction.
261    #[must_use]
262    pub const fn uses_local_direction(self) -> bool {
263        !matches!(self, Self::Api)
264    }
265
266    /// Return `none` for API order or the selected local direction label.
267    #[must_use]
268    pub const fn direction_label(self, direction: SortDirection) -> &'static str {
269        match self {
270            Self::Api => "none",
271            _ => direction.as_str(),
272        }
273    }
274}
275
276impl NnsProposalStatusFilter {
277    #[cfg(any(
278        feature = "nns-host",
279        all(feature = "canister", target_arch = "wasm32")
280    ))]
281    pub(in crate::nns) const fn governance_status_code(self) -> Option<i32> {
282        match self.classification() {
283            None => None,
284            Some(value) => Some(value.code()),
285        }
286    }
287}
288
289impl NnsProposalRewardStatusFilter {
290    #[cfg(any(
291        feature = "nns-host",
292        all(feature = "canister", target_arch = "wasm32")
293    ))]
294    pub(in crate::nns) const fn governance_reward_status_code(self) -> Option<i32> {
295        match self.classification() {
296            None => None,
297            Some(value) => Some(value.code()),
298        }
299    }
300}
301
302impl NnsProposalTopicFilter {
303    pub(in crate::nns) const fn topic_code(self) -> Option<i32> {
304        match self.classification() {
305            None => None,
306            Some(value) => Some(value.code()),
307        }
308    }
309}
310
311#[cfg(test)]
312mod tests;