Skip to main content

ic_query/nns/proposals/report/model/
selection.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 serde::{Deserialize, Serialize};
8
9pub(in crate::nns) const NNS_PROPOSAL_SORT_API_LABEL: &str = "api";
10pub(in crate::nns) const NNS_PROPOSAL_SORT_ID_LABEL: &str = "id";
11pub(in crate::nns) const NNS_PROPOSAL_SORT_STATUS_LABEL: &str = "status";
12pub(in crate::nns) const NNS_PROPOSAL_SORT_REWARD_STATUS_LABEL: &str = "reward-status";
13pub(in crate::nns) const NNS_PROPOSAL_SORT_TOPIC_LABEL: &str = "topic";
14pub(in crate::nns) const NNS_PROPOSAL_SORT_PROPOSER_LABEL: &str = "proposer";
15pub(in crate::nns) const NNS_PROPOSAL_SORT_TITLE_LABEL: &str = "title";
16pub(in crate::nns) const NNS_PROPOSAL_SORT_ACTION_LABEL: &str = "action";
17pub(in crate::nns) const NNS_PROPOSAL_SORT_YES_LABEL: &str = "yes";
18pub(in crate::nns) const NNS_PROPOSAL_SORT_NO_LABEL: &str = "no";
19pub(in crate::nns) const NNS_PROPOSAL_SORT_TOTAL_VOTES_LABEL: &str = "total-votes";
20pub(in crate::nns) const NNS_PROPOSAL_SORT_TALLY_TIME_LABEL: &str = "tally-time";
21pub(in crate::nns) const NNS_PROPOSAL_SORT_VOTING_POWER_LABEL: &str = "voting-power";
22pub(in crate::nns) const NNS_PROPOSAL_SORT_BALLOTS_LABEL: &str = "ballots";
23pub(in crate::nns) const NNS_PROPOSAL_SORT_REJECT_COST_LABEL: &str = "reject-cost";
24pub(in crate::nns) const NNS_PROPOSAL_SORT_REWARD_ROUND_LABEL: &str = "reward-round";
25pub(in crate::nns) const NNS_PROPOSAL_SORT_PROPOSED_LABEL: &str = "proposed";
26pub(in crate::nns) const NNS_PROPOSAL_SORT_DEADLINE_LABEL: &str = "deadline";
27pub(in crate::nns) const NNS_PROPOSAL_SORT_DECIDED_LABEL: &str = "decided";
28pub(in crate::nns) const NNS_PROPOSAL_SORT_EXECUTED_LABEL: &str = "executed";
29pub(in crate::nns) const NNS_PROPOSAL_SORT_FAILED_LABEL: &str = "failed";
30pub(in crate::nns) const NNS_PROPOSAL_SORT_ASC_LABEL: &str = "asc";
31pub(in crate::nns) const NNS_PROPOSAL_SORT_DESC_LABEL: &str = "desc";
32pub(in crate::nns) const NNS_PROPOSAL_SORT_NONE_LABEL: &str = "none";
33
34///
35/// NnsProposalStatus
36///
37/// Native NNS Governance proposal decision status.
38///
39
40#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
41#[serde(rename_all = "kebab-case")]
42pub enum NnsProposalStatus {
43    /// Unspecified or unrecognized native status code.
44    Unspecified,
45    /// Proposal remains open for voting.
46    Open,
47    /// Proposal was rejected.
48    Rejected,
49    /// Proposal was adopted but has not completed execution.
50    Adopted,
51    /// Proposal executed successfully.
52    Executed,
53    /// Proposal execution failed.
54    Failed,
55}
56
57impl NnsProposalStatus {
58    /// Classify one raw native status code.
59    #[must_use]
60    pub const fn from_code(code: i32) -> Self {
61        match code {
62            1 => Self::Open,
63            2 => Self::Rejected,
64            3 => Self::Adopted,
65            4 => Self::Executed,
66            5 => Self::Failed,
67            _ => Self::Unspecified,
68        }
69    }
70
71    /// Return the canonical native code for this classification.
72    #[must_use]
73    pub const fn code(self) -> i32 {
74        match self {
75            Self::Unspecified => 0,
76            Self::Open => 1,
77            Self::Rejected => 2,
78            Self::Adopted => 3,
79            Self::Executed => 4,
80            Self::Failed => 5,
81        }
82    }
83
84    /// Return the stable JSON and text label.
85    #[must_use]
86    pub const fn as_str(self) -> &'static str {
87        match self {
88            Self::Unspecified => "unspecified",
89            Self::Open => "open",
90            Self::Rejected => "rejected",
91            Self::Adopted => "adopted",
92            Self::Executed => "executed",
93            Self::Failed => "failed",
94        }
95    }
96}
97
98///
99/// NnsProposalRewardStatus
100///
101/// Native NNS Governance proposal reward-settlement status.
102///
103
104#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
105#[serde(rename_all = "kebab-case")]
106pub enum NnsProposalRewardStatus {
107    /// Unspecified or unrecognized native reward-status code.
108    Unspecified,
109    /// Proposal still accepts votes for reward purposes.
110    AcceptVotes,
111    /// Proposal is ready for reward settlement.
112    ReadyToSettle,
113    /// Proposal rewards have settled.
114    Settled,
115    /// Proposal is not eligible for voting rewards.
116    Ineligible,
117}
118
119impl NnsProposalRewardStatus {
120    /// Classify one raw native reward-status code.
121    #[must_use]
122    pub const fn from_code(code: i32) -> Self {
123        match code {
124            1 => Self::AcceptVotes,
125            2 => Self::ReadyToSettle,
126            3 => Self::Settled,
127            4 => Self::Ineligible,
128            _ => Self::Unspecified,
129        }
130    }
131
132    /// Return the canonical native code for this classification.
133    #[must_use]
134    pub const fn code(self) -> i32 {
135        match self {
136            Self::Unspecified => 0,
137            Self::AcceptVotes => 1,
138            Self::ReadyToSettle => 2,
139            Self::Settled => 3,
140            Self::Ineligible => 4,
141        }
142    }
143
144    /// Return the stable JSON and text label.
145    #[must_use]
146    pub const fn as_str(self) -> &'static str {
147        match self {
148            Self::Unspecified => "unspecified",
149            Self::AcceptVotes => "accept-votes",
150            Self::ReadyToSettle => "ready-to-settle",
151            Self::Settled => "settled",
152            Self::Ineligible => "ineligible",
153        }
154    }
155}
156
157#[cfg(feature = "host")]
158pub(in crate::nns) const NNS_PROPOSAL_VOTE_UNSPECIFIED_LABEL: &str = "unspecified";
159#[cfg(feature = "host")]
160pub(in crate::nns) const NNS_PROPOSAL_VOTE_YES_LABEL: &str = "yes";
161#[cfg(feature = "host")]
162pub(in crate::nns) const NNS_PROPOSAL_VOTE_NO_LABEL: &str = "no";
163#[cfg(feature = "host")]
164pub(in crate::nns) const NNS_PROPOSAL_VOTE_YES_CODE: i32 = 1;
165#[cfg(feature = "host")]
166pub(in crate::nns) const NNS_PROPOSAL_VOTE_NO_CODE: i32 = 2;
167
168pub(in crate::nns) const NNS_PROPOSAL_TOPIC_ANY_LABEL: &str = "any";
169pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NEURON_MANAGEMENT_LABEL: &str = "neuron-management";
170pub(in crate::nns) const NNS_PROPOSAL_TOPIC_EXCHANGE_RATE_LABEL: &str = "exchange-rate";
171pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NETWORK_ECONOMICS_LABEL: &str = "network-economics";
172pub(in crate::nns) const NNS_PROPOSAL_TOPIC_GOVERNANCE_LABEL: &str = "governance";
173pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NODE_ADMIN_LABEL: &str = "node-admin";
174pub(in crate::nns) const NNS_PROPOSAL_TOPIC_PARTICIPANT_MANAGEMENT_LABEL: &str =
175    "participant-management";
176pub(in crate::nns) const NNS_PROPOSAL_TOPIC_SUBNET_MANAGEMENT_LABEL: &str = "subnet-management";
177pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NETWORK_CANISTER_MANAGEMENT_LABEL: &str =
178    "network-canister-management";
179pub(in crate::nns) const NNS_PROPOSAL_TOPIC_KYC_LABEL: &str = "kyc";
180pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NODE_PROVIDER_REWARDS_LABEL: &str =
181    "node-provider-rewards";
182pub(in crate::nns) const NNS_PROPOSAL_TOPIC_IC_OS_VERSION_DEPLOYMENT_LABEL: &str =
183    "ic-os-version-deployment";
184pub(in crate::nns) const NNS_PROPOSAL_TOPIC_IC_OS_VERSION_ELECTION_LABEL: &str =
185    "ic-os-version-election";
186pub(in crate::nns) const NNS_PROPOSAL_TOPIC_SNS_AND_COMMUNITY_FUND_LABEL: &str =
187    "sns-and-community-fund";
188pub(in crate::nns) const NNS_PROPOSAL_TOPIC_API_BOUNDARY_NODE_MANAGEMENT_LABEL: &str =
189    "api-boundary-node-management";
190pub(in crate::nns) const NNS_PROPOSAL_TOPIC_SUBNET_RENTAL_LABEL: &str = "subnet-rental";
191pub(in crate::nns) const NNS_PROPOSAL_TOPIC_APPLICATION_CANISTER_MANAGEMENT_LABEL: &str =
192    "application-canister-management";
193pub(in crate::nns) const NNS_PROPOSAL_TOPIC_PROTOCOL_CANISTER_MANAGEMENT_LABEL: &str =
194    "protocol-canister-management";
195#[cfg(feature = "host")]
196pub(in crate::nns) const NNS_PROPOSAL_TOPIC_UNSPECIFIED_LABEL: &str = "unspecified";
197#[cfg(feature = "host")]
198pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NEURON_MANAGEMENT_CODE: i32 = 1;
199#[cfg(feature = "host")]
200pub(in crate::nns) const NNS_PROPOSAL_TOPIC_EXCHANGE_RATE_CODE: i32 = 2;
201#[cfg(feature = "host")]
202pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NETWORK_ECONOMICS_CODE: i32 = 3;
203#[cfg(feature = "host")]
204pub(in crate::nns) const NNS_PROPOSAL_TOPIC_GOVERNANCE_CODE: i32 = 4;
205#[cfg(feature = "host")]
206pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NODE_ADMIN_CODE: i32 = 5;
207#[cfg(feature = "host")]
208pub(in crate::nns) const NNS_PROPOSAL_TOPIC_PARTICIPANT_MANAGEMENT_CODE: i32 = 6;
209#[cfg(feature = "host")]
210pub(in crate::nns) const NNS_PROPOSAL_TOPIC_SUBNET_MANAGEMENT_CODE: i32 = 7;
211#[cfg(feature = "host")]
212pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NETWORK_CANISTER_MANAGEMENT_CODE: i32 = 8;
213#[cfg(feature = "host")]
214pub(in crate::nns) const NNS_PROPOSAL_TOPIC_KYC_CODE: i32 = 9;
215#[cfg(feature = "host")]
216pub(in crate::nns) const NNS_PROPOSAL_TOPIC_NODE_PROVIDER_REWARDS_CODE: i32 = 10;
217#[cfg(feature = "host")]
218pub(in crate::nns) const NNS_PROPOSAL_TOPIC_IC_OS_VERSION_DEPLOYMENT_CODE: i32 = 12;
219#[cfg(feature = "host")]
220pub(in crate::nns) const NNS_PROPOSAL_TOPIC_IC_OS_VERSION_ELECTION_CODE: i32 = 13;
221#[cfg(feature = "host")]
222pub(in crate::nns) const NNS_PROPOSAL_TOPIC_SNS_AND_COMMUNITY_FUND_CODE: i32 = 14;
223#[cfg(feature = "host")]
224pub(in crate::nns) const NNS_PROPOSAL_TOPIC_API_BOUNDARY_NODE_MANAGEMENT_CODE: i32 = 15;
225#[cfg(feature = "host")]
226pub(in crate::nns) const NNS_PROPOSAL_TOPIC_SUBNET_RENTAL_CODE: i32 = 16;
227#[cfg(feature = "host")]
228pub(in crate::nns) const NNS_PROPOSAL_TOPIC_APPLICATION_CANISTER_MANAGEMENT_CODE: i32 = 17;
229#[cfg(feature = "host")]
230pub(in crate::nns) const NNS_PROPOSAL_TOPIC_PROTOCOL_CANISTER_MANAGEMENT_CODE: i32 = 18;
231
232///
233/// NnsProposalListSort
234///
235/// Report-model sort selector for NNS proposal listings.
236///
237
238#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
239pub enum NnsProposalListSort {
240    #[default]
241    Api,
242    Id,
243    Status,
244    RewardStatus,
245    Topic,
246    Proposer,
247    Title,
248    Action,
249    Yes,
250    No,
251    TotalVotes,
252    TallyTime,
253    VotingPower,
254    Ballots,
255    RejectCost,
256    RewardRound,
257    Proposed,
258    Deadline,
259    Decided,
260    Executed,
261    Failed,
262}
263
264impl NnsProposalListSort {
265    #[must_use]
266    pub const fn as_str(self) -> &'static str {
267        match self {
268            Self::Api => NNS_PROPOSAL_SORT_API_LABEL,
269            Self::Id => NNS_PROPOSAL_SORT_ID_LABEL,
270            Self::Status => NNS_PROPOSAL_SORT_STATUS_LABEL,
271            Self::RewardStatus => NNS_PROPOSAL_SORT_REWARD_STATUS_LABEL,
272            Self::Topic => NNS_PROPOSAL_SORT_TOPIC_LABEL,
273            Self::Proposer => NNS_PROPOSAL_SORT_PROPOSER_LABEL,
274            Self::Title => NNS_PROPOSAL_SORT_TITLE_LABEL,
275            Self::Action => NNS_PROPOSAL_SORT_ACTION_LABEL,
276            Self::Yes => NNS_PROPOSAL_SORT_YES_LABEL,
277            Self::No => NNS_PROPOSAL_SORT_NO_LABEL,
278            Self::TotalVotes => NNS_PROPOSAL_SORT_TOTAL_VOTES_LABEL,
279            Self::TallyTime => NNS_PROPOSAL_SORT_TALLY_TIME_LABEL,
280            Self::VotingPower => NNS_PROPOSAL_SORT_VOTING_POWER_LABEL,
281            Self::Ballots => NNS_PROPOSAL_SORT_BALLOTS_LABEL,
282            Self::RejectCost => NNS_PROPOSAL_SORT_REJECT_COST_LABEL,
283            Self::RewardRound => NNS_PROPOSAL_SORT_REWARD_ROUND_LABEL,
284            Self::Proposed => NNS_PROPOSAL_SORT_PROPOSED_LABEL,
285            Self::Deadline => NNS_PROPOSAL_SORT_DEADLINE_LABEL,
286            Self::Decided => NNS_PROPOSAL_SORT_DECIDED_LABEL,
287            Self::Executed => NNS_PROPOSAL_SORT_EXECUTED_LABEL,
288            Self::Failed => NNS_PROPOSAL_SORT_FAILED_LABEL,
289        }
290    }
291
292    #[must_use]
293    pub const fn default_direction(self) -> NnsProposalSortDirection {
294        match self {
295            Self::Status
296            | Self::RewardStatus
297            | Self::Topic
298            | Self::Proposer
299            | Self::Title
300            | Self::Action => NnsProposalSortDirection::Asc,
301            _ => NnsProposalSortDirection::Desc,
302        }
303    }
304
305    #[must_use]
306    pub const fn uses_local_direction(self) -> bool {
307        !matches!(self, Self::Api)
308    }
309
310    #[must_use]
311    pub const fn direction_label(self, direction: NnsProposalSortDirection) -> &'static str {
312        match self {
313            Self::Api => NNS_PROPOSAL_SORT_NONE_LABEL,
314            _ => direction.as_str(),
315        }
316    }
317}
318
319///
320/// NnsProposalSortDirection
321///
322/// Report-model direction selector for local NNS proposal sorting.
323///
324
325#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
326pub enum NnsProposalSortDirection {
327    Asc,
328    #[default]
329    Desc,
330}
331
332impl NnsProposalSortDirection {
333    #[must_use]
334    pub const fn as_str(self) -> &'static str {
335        match self {
336            Self::Asc => NNS_PROPOSAL_SORT_ASC_LABEL,
337            Self::Desc => NNS_PROPOSAL_SORT_DESC_LABEL,
338        }
339    }
340}
341
342///
343/// NnsProposalStatusFilter
344///
345/// Report-model status filter for bounded NNS proposal listings.
346///
347
348#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
349pub enum NnsProposalStatusFilter {
350    #[default]
351    Any,
352    Open,
353    Rejected,
354    Adopted,
355    Executed,
356    Failed,
357}
358
359///
360/// NnsProposalRewardStatusFilter
361///
362/// Report-model reward status filter for bounded NNS proposal listings.
363///
364
365#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
366pub enum NnsProposalRewardStatusFilter {
367    #[default]
368    Any,
369    AcceptVotes,
370    ReadyToSettle,
371    Settled,
372    Ineligible,
373}
374
375impl NnsProposalRewardStatusFilter {
376    #[must_use]
377    pub const fn as_str(self) -> &'static str {
378        match self {
379            Self::Any => "any",
380            Self::AcceptVotes => NnsProposalRewardStatus::AcceptVotes.as_str(),
381            Self::ReadyToSettle => NnsProposalRewardStatus::ReadyToSettle.as_str(),
382            Self::Settled => NnsProposalRewardStatus::Settled.as_str(),
383            Self::Ineligible => NnsProposalRewardStatus::Ineligible.as_str(),
384        }
385    }
386
387    #[cfg(feature = "host")]
388    pub(in crate::nns) const fn governance_reward_status_code(self) -> Option<i32> {
389        match self {
390            Self::Any => None,
391            Self::AcceptVotes => Some(NnsProposalRewardStatus::AcceptVotes.code()),
392            Self::ReadyToSettle => Some(NnsProposalRewardStatus::ReadyToSettle.code()),
393            Self::Settled => Some(NnsProposalRewardStatus::Settled.code()),
394            Self::Ineligible => Some(NnsProposalRewardStatus::Ineligible.code()),
395        }
396    }
397}
398
399impl NnsProposalStatusFilter {
400    #[must_use]
401    pub const fn as_str(self) -> &'static str {
402        match self {
403            Self::Any => "any",
404            Self::Open => NnsProposalStatus::Open.as_str(),
405            Self::Rejected => NnsProposalStatus::Rejected.as_str(),
406            Self::Adopted => NnsProposalStatus::Adopted.as_str(),
407            Self::Executed => NnsProposalStatus::Executed.as_str(),
408            Self::Failed => NnsProposalStatus::Failed.as_str(),
409        }
410    }
411
412    #[cfg(feature = "host")]
413    pub(in crate::nns) const fn governance_status_code(self) -> Option<i32> {
414        match self {
415            Self::Any => None,
416            Self::Open => Some(NnsProposalStatus::Open.code()),
417            Self::Rejected => Some(NnsProposalStatus::Rejected.code()),
418            Self::Adopted => Some(NnsProposalStatus::Adopted.code()),
419            Self::Executed => Some(NnsProposalStatus::Executed.code()),
420            Self::Failed => Some(NnsProposalStatus::Failed.code()),
421        }
422    }
423}
424
425///
426/// NnsProposalTopicFilter
427///
428/// Report-model topic filter for bounded NNS proposal listings.
429///
430
431#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
432pub enum NnsProposalTopicFilter {
433    #[default]
434    Any,
435    NeuronManagement,
436    ExchangeRate,
437    NetworkEconomics,
438    Governance,
439    NodeAdmin,
440    ParticipantManagement,
441    SubnetManagement,
442    NetworkCanisterManagement,
443    Kyc,
444    NodeProviderRewards,
445    IcOsVersionDeployment,
446    IcOsVersionElection,
447    SnsAndCommunityFund,
448    ApiBoundaryNodeManagement,
449    SubnetRental,
450    ApplicationCanisterManagement,
451    ProtocolCanisterManagement,
452}
453
454impl NnsProposalTopicFilter {
455    #[must_use]
456    pub const fn as_str(self) -> &'static str {
457        match self {
458            Self::Any => NNS_PROPOSAL_TOPIC_ANY_LABEL,
459            Self::NeuronManagement => NNS_PROPOSAL_TOPIC_NEURON_MANAGEMENT_LABEL,
460            Self::ExchangeRate => NNS_PROPOSAL_TOPIC_EXCHANGE_RATE_LABEL,
461            Self::NetworkEconomics => NNS_PROPOSAL_TOPIC_NETWORK_ECONOMICS_LABEL,
462            Self::Governance => NNS_PROPOSAL_TOPIC_GOVERNANCE_LABEL,
463            Self::NodeAdmin => NNS_PROPOSAL_TOPIC_NODE_ADMIN_LABEL,
464            Self::ParticipantManagement => NNS_PROPOSAL_TOPIC_PARTICIPANT_MANAGEMENT_LABEL,
465            Self::SubnetManagement => NNS_PROPOSAL_TOPIC_SUBNET_MANAGEMENT_LABEL,
466            Self::NetworkCanisterManagement => NNS_PROPOSAL_TOPIC_NETWORK_CANISTER_MANAGEMENT_LABEL,
467            Self::Kyc => NNS_PROPOSAL_TOPIC_KYC_LABEL,
468            Self::NodeProviderRewards => NNS_PROPOSAL_TOPIC_NODE_PROVIDER_REWARDS_LABEL,
469            Self::IcOsVersionDeployment => NNS_PROPOSAL_TOPIC_IC_OS_VERSION_DEPLOYMENT_LABEL,
470            Self::IcOsVersionElection => NNS_PROPOSAL_TOPIC_IC_OS_VERSION_ELECTION_LABEL,
471            Self::SnsAndCommunityFund => NNS_PROPOSAL_TOPIC_SNS_AND_COMMUNITY_FUND_LABEL,
472            Self::ApiBoundaryNodeManagement => {
473                NNS_PROPOSAL_TOPIC_API_BOUNDARY_NODE_MANAGEMENT_LABEL
474            }
475            Self::SubnetRental => NNS_PROPOSAL_TOPIC_SUBNET_RENTAL_LABEL,
476            Self::ApplicationCanisterManagement => {
477                NNS_PROPOSAL_TOPIC_APPLICATION_CANISTER_MANAGEMENT_LABEL
478            }
479            Self::ProtocolCanisterManagement => {
480                NNS_PROPOSAL_TOPIC_PROTOCOL_CANISTER_MANAGEMENT_LABEL
481            }
482        }
483    }
484
485    #[cfg(feature = "host")]
486    pub(in crate::nns) const fn topic_code(self) -> Option<i32> {
487        match self {
488            Self::Any => None,
489            Self::NeuronManagement => Some(NNS_PROPOSAL_TOPIC_NEURON_MANAGEMENT_CODE),
490            Self::ExchangeRate => Some(NNS_PROPOSAL_TOPIC_EXCHANGE_RATE_CODE),
491            Self::NetworkEconomics => Some(NNS_PROPOSAL_TOPIC_NETWORK_ECONOMICS_CODE),
492            Self::Governance => Some(NNS_PROPOSAL_TOPIC_GOVERNANCE_CODE),
493            Self::NodeAdmin => Some(NNS_PROPOSAL_TOPIC_NODE_ADMIN_CODE),
494            Self::ParticipantManagement => Some(NNS_PROPOSAL_TOPIC_PARTICIPANT_MANAGEMENT_CODE),
495            Self::SubnetManagement => Some(NNS_PROPOSAL_TOPIC_SUBNET_MANAGEMENT_CODE),
496            Self::NetworkCanisterManagement => {
497                Some(NNS_PROPOSAL_TOPIC_NETWORK_CANISTER_MANAGEMENT_CODE)
498            }
499            Self::Kyc => Some(NNS_PROPOSAL_TOPIC_KYC_CODE),
500            Self::NodeProviderRewards => Some(NNS_PROPOSAL_TOPIC_NODE_PROVIDER_REWARDS_CODE),
501            Self::IcOsVersionDeployment => Some(NNS_PROPOSAL_TOPIC_IC_OS_VERSION_DEPLOYMENT_CODE),
502            Self::IcOsVersionElection => Some(NNS_PROPOSAL_TOPIC_IC_OS_VERSION_ELECTION_CODE),
503            Self::SnsAndCommunityFund => Some(NNS_PROPOSAL_TOPIC_SNS_AND_COMMUNITY_FUND_CODE),
504            Self::ApiBoundaryNodeManagement => {
505                Some(NNS_PROPOSAL_TOPIC_API_BOUNDARY_NODE_MANAGEMENT_CODE)
506            }
507            Self::SubnetRental => Some(NNS_PROPOSAL_TOPIC_SUBNET_RENTAL_CODE),
508            Self::ApplicationCanisterManagement => {
509                Some(NNS_PROPOSAL_TOPIC_APPLICATION_CANISTER_MANAGEMENT_CODE)
510            }
511            Self::ProtocolCanisterManagement => {
512                Some(NNS_PROPOSAL_TOPIC_PROTOCOL_CANISTER_MANAGEMENT_CODE)
513            }
514        }
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    #[test]
523    fn proposal_status_codes_and_labels_round_trip() {
524        for (status, code, label) in [
525            (NnsProposalStatus::Unspecified, 0, "unspecified"),
526            (NnsProposalStatus::Open, 1, "open"),
527            (NnsProposalStatus::Rejected, 2, "rejected"),
528            (NnsProposalStatus::Adopted, 3, "adopted"),
529            (NnsProposalStatus::Executed, 4, "executed"),
530            (NnsProposalStatus::Failed, 5, "failed"),
531        ] {
532            assert_eq!(status.code(), code);
533            assert_eq!(NnsProposalStatus::from_code(code), status);
534            assert_json_label(status, label);
535        }
536        assert_eq!(
537            NnsProposalStatus::from_code(99),
538            NnsProposalStatus::Unspecified
539        );
540    }
541
542    #[test]
543    fn proposal_reward_status_codes_and_labels_round_trip() {
544        for (status, code, label) in [
545            (NnsProposalRewardStatus::Unspecified, 0, "unspecified"),
546            (NnsProposalRewardStatus::AcceptVotes, 1, "accept-votes"),
547            (NnsProposalRewardStatus::ReadyToSettle, 2, "ready-to-settle"),
548            (NnsProposalRewardStatus::Settled, 3, "settled"),
549            (NnsProposalRewardStatus::Ineligible, 4, "ineligible"),
550        ] {
551            assert_eq!(status.code(), code);
552            assert_eq!(NnsProposalRewardStatus::from_code(code), status);
553            assert_json_label(status, label);
554        }
555        assert_eq!(
556            NnsProposalRewardStatus::from_code(99),
557            NnsProposalRewardStatus::Unspecified
558        );
559    }
560
561    fn assert_json_label<T>(value: T, label: &str)
562    where
563        T: Copy + std::fmt::Debug + Eq + Serialize + serde::de::DeserializeOwned,
564    {
565        assert_eq!(
566            serde_json::to_string(&value).unwrap(),
567            format!("\"{label}\"")
568        );
569        assert_eq!(
570            serde_json::from_str::<T>(&format!("\"{label}\"")).unwrap(),
571            value
572        );
573    }
574}