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///
158/// NnsProposalVote
159///
160/// Native NNS Governance ballot vote.
161///
162
163#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
164#[serde(rename_all = "kebab-case")]
165pub enum NnsProposalVote {
166    /// Unspecified or unrecognized native vote code.
167    Unspecified,
168    /// Affirmative ballot.
169    Yes,
170    /// Negative ballot.
171    No,
172}
173
174impl NnsProposalVote {
175    /// Classify one raw native vote code.
176    #[must_use]
177    pub const fn from_code(code: i32) -> Self {
178        match code {
179            1 => Self::Yes,
180            2 => Self::No,
181            _ => Self::Unspecified,
182        }
183    }
184
185    /// Return the canonical native code for this classification.
186    #[must_use]
187    pub const fn code(self) -> i32 {
188        match self {
189            Self::Unspecified => 0,
190            Self::Yes => 1,
191            Self::No => 2,
192        }
193    }
194
195    /// Return the stable JSON and text label.
196    #[must_use]
197    pub const fn as_str(self) -> &'static str {
198        match self {
199            Self::Unspecified => "unspecified",
200            Self::Yes => "yes",
201            Self::No => "no",
202        }
203    }
204}
205
206///
207/// NnsProposalTopic
208///
209/// Native NNS Governance proposal topic.
210///
211
212#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
213#[serde(rename_all = "kebab-case")]
214pub enum NnsProposalTopic {
215    /// Unspecified or unrecognized native topic code.
216    Unspecified,
217    /// Neuron management.
218    NeuronManagement,
219    /// Exchange-rate management.
220    ExchangeRate,
221    /// Network economics.
222    NetworkEconomics,
223    /// Governance policy.
224    Governance,
225    /// Node administration.
226    NodeAdmin,
227    /// Participant management.
228    ParticipantManagement,
229    /// Subnet management.
230    SubnetManagement,
231    /// Network-canister management.
232    NetworkCanisterManagement,
233    /// Know-your-customer policy.
234    Kyc,
235    /// Node-provider rewards.
236    NodeProviderRewards,
237    /// IC OS version deployment.
238    IcOsVersionDeployment,
239    /// IC OS version election.
240    IcOsVersionElection,
241    /// SNS and Community Fund policy.
242    SnsAndCommunityFund,
243    /// API boundary-node management.
244    ApiBoundaryNodeManagement,
245    /// Subnet rental.
246    SubnetRental,
247    /// Application-canister management.
248    ApplicationCanisterManagement,
249    /// Protocol-canister management.
250    ProtocolCanisterManagement,
251}
252
253impl NnsProposalTopic {
254    /// Classify one raw native topic code.
255    #[must_use]
256    pub const fn from_code(code: i32) -> Self {
257        match code {
258            1 => Self::NeuronManagement,
259            2 => Self::ExchangeRate,
260            3 => Self::NetworkEconomics,
261            4 => Self::Governance,
262            5 => Self::NodeAdmin,
263            6 => Self::ParticipantManagement,
264            7 => Self::SubnetManagement,
265            8 => Self::NetworkCanisterManagement,
266            9 => Self::Kyc,
267            10 => Self::NodeProviderRewards,
268            12 => Self::IcOsVersionDeployment,
269            13 => Self::IcOsVersionElection,
270            14 => Self::SnsAndCommunityFund,
271            15 => Self::ApiBoundaryNodeManagement,
272            16 => Self::SubnetRental,
273            17 => Self::ApplicationCanisterManagement,
274            18 => Self::ProtocolCanisterManagement,
275            _ => Self::Unspecified,
276        }
277    }
278
279    /// Return the canonical native code for this classification.
280    #[must_use]
281    pub const fn code(self) -> i32 {
282        match self {
283            Self::Unspecified => 0,
284            Self::NeuronManagement => 1,
285            Self::ExchangeRate => 2,
286            Self::NetworkEconomics => 3,
287            Self::Governance => 4,
288            Self::NodeAdmin => 5,
289            Self::ParticipantManagement => 6,
290            Self::SubnetManagement => 7,
291            Self::NetworkCanisterManagement => 8,
292            Self::Kyc => 9,
293            Self::NodeProviderRewards => 10,
294            Self::IcOsVersionDeployment => 12,
295            Self::IcOsVersionElection => 13,
296            Self::SnsAndCommunityFund => 14,
297            Self::ApiBoundaryNodeManagement => 15,
298            Self::SubnetRental => 16,
299            Self::ApplicationCanisterManagement => 17,
300            Self::ProtocolCanisterManagement => 18,
301        }
302    }
303
304    /// Return the stable JSON and text label.
305    #[must_use]
306    pub const fn as_str(self) -> &'static str {
307        match self {
308            Self::Unspecified => "unspecified",
309            Self::NeuronManagement => "neuron-management",
310            Self::ExchangeRate => "exchange-rate",
311            Self::NetworkEconomics => "network-economics",
312            Self::Governance => "governance",
313            Self::NodeAdmin => "node-admin",
314            Self::ParticipantManagement => "participant-management",
315            Self::SubnetManagement => "subnet-management",
316            Self::NetworkCanisterManagement => "network-canister-management",
317            Self::Kyc => "kyc",
318            Self::NodeProviderRewards => "node-provider-rewards",
319            Self::IcOsVersionDeployment => "ic-os-version-deployment",
320            Self::IcOsVersionElection => "ic-os-version-election",
321            Self::SnsAndCommunityFund => "sns-and-community-fund",
322            Self::ApiBoundaryNodeManagement => "api-boundary-node-management",
323            Self::SubnetRental => "subnet-rental",
324            Self::ApplicationCanisterManagement => "application-canister-management",
325            Self::ProtocolCanisterManagement => "protocol-canister-management",
326        }
327    }
328}
329
330///
331/// NnsProposalListSort
332///
333/// Report-model sort selector for NNS proposal listings.
334///
335
336#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
337pub enum NnsProposalListSort {
338    /// Preserve the descending proposal-id order returned by Governance.
339    #[default]
340    Api,
341    /// Sort by proposal id.
342    Id,
343    /// Sort by the raw decision-status code.
344    Status,
345    /// Sort by the raw reward-settlement code.
346    RewardStatus,
347    /// Sort by the raw topic code.
348    Topic,
349    /// Sort by proposer neuron id.
350    Proposer,
351    /// Sort by proposal title.
352    Title,
353    /// Sort by the human-readable action classification.
354    Action,
355    /// Sort by affirmative voting power in the latest tally.
356    Yes,
357    /// Sort by negative voting power in the latest tally.
358    No,
359    /// Sort by total voting power in the latest tally.
360    TotalVotes,
361    /// Sort by the latest tally timestamp.
362    TallyTime,
363    /// Sort by total potential voting power.
364    VotingPower,
365    /// Sort by ballot count returned by Governance.
366    Ballots,
367    /// Sort by reject cost in ICP e8s.
368    RejectCost,
369    /// Sort by reward event round.
370    RewardRound,
371    /// Sort by proposal creation time.
372    Proposed,
373    /// Sort by voting deadline.
374    Deadline,
375    /// Sort by decision time.
376    Decided,
377    /// Sort by execution time.
378    Executed,
379    /// Sort by execution failure time.
380    Failed,
381}
382
383impl NnsProposalListSort {
384    /// Return the canonical filter or sort label used in reports.
385    #[must_use]
386    pub const fn as_str(self) -> &'static str {
387        match self {
388            Self::Api => NNS_PROPOSAL_SORT_API_LABEL,
389            Self::Id => NNS_PROPOSAL_SORT_ID_LABEL,
390            Self::Status => NNS_PROPOSAL_SORT_STATUS_LABEL,
391            Self::RewardStatus => NNS_PROPOSAL_SORT_REWARD_STATUS_LABEL,
392            Self::Topic => NNS_PROPOSAL_SORT_TOPIC_LABEL,
393            Self::Proposer => NNS_PROPOSAL_SORT_PROPOSER_LABEL,
394            Self::Title => NNS_PROPOSAL_SORT_TITLE_LABEL,
395            Self::Action => NNS_PROPOSAL_SORT_ACTION_LABEL,
396            Self::Yes => NNS_PROPOSAL_SORT_YES_LABEL,
397            Self::No => NNS_PROPOSAL_SORT_NO_LABEL,
398            Self::TotalVotes => NNS_PROPOSAL_SORT_TOTAL_VOTES_LABEL,
399            Self::TallyTime => NNS_PROPOSAL_SORT_TALLY_TIME_LABEL,
400            Self::VotingPower => NNS_PROPOSAL_SORT_VOTING_POWER_LABEL,
401            Self::Ballots => NNS_PROPOSAL_SORT_BALLOTS_LABEL,
402            Self::RejectCost => NNS_PROPOSAL_SORT_REJECT_COST_LABEL,
403            Self::RewardRound => NNS_PROPOSAL_SORT_REWARD_ROUND_LABEL,
404            Self::Proposed => NNS_PROPOSAL_SORT_PROPOSED_LABEL,
405            Self::Deadline => NNS_PROPOSAL_SORT_DEADLINE_LABEL,
406            Self::Decided => NNS_PROPOSAL_SORT_DECIDED_LABEL,
407            Self::Executed => NNS_PROPOSAL_SORT_EXECUTED_LABEL,
408            Self::Failed => NNS_PROPOSAL_SORT_FAILED_LABEL,
409        }
410    }
411
412    /// Return the default direction for this sort key.
413    #[must_use]
414    pub const fn default_direction(self) -> NnsProposalSortDirection {
415        match self {
416            Self::Status
417            | Self::RewardStatus
418            | Self::Topic
419            | Self::Proposer
420            | Self::Title
421            | Self::Action => NnsProposalSortDirection::Asc,
422            _ => NnsProposalSortDirection::Desc,
423        }
424    }
425
426    /// Return whether this key applies a local sort direction.
427    #[must_use]
428    pub const fn uses_local_direction(self) -> bool {
429        !matches!(self, Self::Api)
430    }
431
432    /// Return `none` for API order or the selected local direction label.
433    #[must_use]
434    pub const fn direction_label(self, direction: NnsProposalSortDirection) -> &'static str {
435        match self {
436            Self::Api => NNS_PROPOSAL_SORT_NONE_LABEL,
437            _ => direction.as_str(),
438        }
439    }
440}
441
442///
443/// NnsProposalSortDirection
444///
445/// Report-model direction selector for local NNS proposal sorting.
446///
447
448#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
449pub enum NnsProposalSortDirection {
450    /// Order local sort values from lowest to highest.
451    Asc,
452    /// Order local sort values from highest to lowest.
453    #[default]
454    Desc,
455}
456
457impl NnsProposalSortDirection {
458    /// Return the canonical filter or sort label used in reports.
459    #[must_use]
460    pub const fn as_str(self) -> &'static str {
461        match self {
462            Self::Asc => NNS_PROPOSAL_SORT_ASC_LABEL,
463            Self::Desc => NNS_PROPOSAL_SORT_DESC_LABEL,
464        }
465    }
466}
467
468///
469/// NnsProposalStatusFilter
470///
471/// Report-model status filter for bounded NNS proposal listings.
472///
473
474#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
475pub enum NnsProposalStatusFilter {
476    /// Include proposals without restricting this classification.
477    #[default]
478    Any,
479    /// Include only proposals still open for voting.
480    Open,
481    /// Include only rejected proposals.
482    Rejected,
483    /// Include only adopted proposals awaiting successful execution.
484    Adopted,
485    /// Include only successfully executed proposals.
486    Executed,
487    /// Include only proposals whose execution failed.
488    Failed,
489}
490
491///
492/// NnsProposalRewardStatusFilter
493///
494/// Report-model reward status filter for bounded NNS proposal listings.
495///
496
497#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
498pub enum NnsProposalRewardStatusFilter {
499    /// Include proposals without restricting this classification.
500    #[default]
501    Any,
502    /// Include only proposals accepting votes for rewards.
503    AcceptVotes,
504    /// Include only proposals ready for reward settlement.
505    ReadyToSettle,
506    /// Include only proposals with settled rewards.
507    Settled,
508    /// Include only proposals ineligible for voting rewards.
509    Ineligible,
510}
511
512impl NnsProposalRewardStatusFilter {
513    /// Return the canonical filter or sort label used in reports.
514    #[must_use]
515    pub const fn as_str(self) -> &'static str {
516        match self {
517            Self::Any => "any",
518            Self::AcceptVotes => NnsProposalRewardStatus::AcceptVotes.as_str(),
519            Self::ReadyToSettle => NnsProposalRewardStatus::ReadyToSettle.as_str(),
520            Self::Settled => NnsProposalRewardStatus::Settled.as_str(),
521            Self::Ineligible => NnsProposalRewardStatus::Ineligible.as_str(),
522        }
523    }
524
525    #[cfg(any(
526        feature = "nns-host",
527        all(feature = "canister", target_arch = "wasm32")
528    ))]
529    pub(in crate::nns) const fn governance_reward_status_code(self) -> Option<i32> {
530        match self {
531            Self::Any => None,
532            Self::AcceptVotes => Some(NnsProposalRewardStatus::AcceptVotes.code()),
533            Self::ReadyToSettle => Some(NnsProposalRewardStatus::ReadyToSettle.code()),
534            Self::Settled => Some(NnsProposalRewardStatus::Settled.code()),
535            Self::Ineligible => Some(NnsProposalRewardStatus::Ineligible.code()),
536        }
537    }
538}
539
540impl NnsProposalStatusFilter {
541    /// Return the canonical filter or sort label used in reports.
542    #[must_use]
543    pub const fn as_str(self) -> &'static str {
544        match self {
545            Self::Any => "any",
546            Self::Open => NnsProposalStatus::Open.as_str(),
547            Self::Rejected => NnsProposalStatus::Rejected.as_str(),
548            Self::Adopted => NnsProposalStatus::Adopted.as_str(),
549            Self::Executed => NnsProposalStatus::Executed.as_str(),
550            Self::Failed => NnsProposalStatus::Failed.as_str(),
551        }
552    }
553
554    #[cfg(any(
555        feature = "nns-host",
556        all(feature = "canister", target_arch = "wasm32")
557    ))]
558    pub(in crate::nns) const fn governance_status_code(self) -> Option<i32> {
559        match self {
560            Self::Any => None,
561            Self::Open => Some(NnsProposalStatus::Open.code()),
562            Self::Rejected => Some(NnsProposalStatus::Rejected.code()),
563            Self::Adopted => Some(NnsProposalStatus::Adopted.code()),
564            Self::Executed => Some(NnsProposalStatus::Executed.code()),
565            Self::Failed => Some(NnsProposalStatus::Failed.code()),
566        }
567    }
568}
569
570///
571/// NnsProposalTopicFilter
572///
573/// Report-model topic filter for bounded NNS proposal listings.
574///
575
576#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
577pub enum NnsProposalTopicFilter {
578    /// Include proposals without restricting this classification.
579    #[default]
580    Any,
581    /// Include only proposals about neuron management.
582    NeuronManagement,
583    /// Include only proposals about exchange-rate management.
584    ExchangeRate,
585    /// Include only proposals about network economics.
586    NetworkEconomics,
587    /// Include only proposals about Governance policy.
588    Governance,
589    /// Include only proposals about node administration.
590    NodeAdmin,
591    /// Include only proposals about participant management.
592    ParticipantManagement,
593    /// Include only proposals about Subnet management.
594    SubnetManagement,
595    /// Include only proposals about network-canister management.
596    NetworkCanisterManagement,
597    /// Include only proposals about know-your-customer policy.
598    Kyc,
599    /// Include only proposals about node-provider rewards.
600    NodeProviderRewards,
601    /// Include only proposals about IC OS version deployment.
602    IcOsVersionDeployment,
603    /// Include only proposals about IC OS version election.
604    IcOsVersionElection,
605    /// Include only proposals about SNS and community-fund policy.
606    SnsAndCommunityFund,
607    /// Include only proposals about API boundary-node management.
608    ApiBoundaryNodeManagement,
609    /// Include only proposals about Subnet rental.
610    SubnetRental,
611    /// Include only proposals about application-canister management.
612    ApplicationCanisterManagement,
613    /// Include only proposals about protocol-canister management.
614    ProtocolCanisterManagement,
615}
616
617impl NnsProposalTopicFilter {
618    /// Return the canonical filter or sort label used in reports.
619    #[must_use]
620    pub const fn as_str(self) -> &'static str {
621        match self {
622            Self::Any => "any",
623            Self::NeuronManagement => NnsProposalTopic::NeuronManagement.as_str(),
624            Self::ExchangeRate => NnsProposalTopic::ExchangeRate.as_str(),
625            Self::NetworkEconomics => NnsProposalTopic::NetworkEconomics.as_str(),
626            Self::Governance => NnsProposalTopic::Governance.as_str(),
627            Self::NodeAdmin => NnsProposalTopic::NodeAdmin.as_str(),
628            Self::ParticipantManagement => NnsProposalTopic::ParticipantManagement.as_str(),
629            Self::SubnetManagement => NnsProposalTopic::SubnetManagement.as_str(),
630            Self::NetworkCanisterManagement => NnsProposalTopic::NetworkCanisterManagement.as_str(),
631            Self::Kyc => NnsProposalTopic::Kyc.as_str(),
632            Self::NodeProviderRewards => NnsProposalTopic::NodeProviderRewards.as_str(),
633            Self::IcOsVersionDeployment => NnsProposalTopic::IcOsVersionDeployment.as_str(),
634            Self::IcOsVersionElection => NnsProposalTopic::IcOsVersionElection.as_str(),
635            Self::SnsAndCommunityFund => NnsProposalTopic::SnsAndCommunityFund.as_str(),
636            Self::ApiBoundaryNodeManagement => NnsProposalTopic::ApiBoundaryNodeManagement.as_str(),
637            Self::SubnetRental => NnsProposalTopic::SubnetRental.as_str(),
638            Self::ApplicationCanisterManagement => {
639                NnsProposalTopic::ApplicationCanisterManagement.as_str()
640            }
641            Self::ProtocolCanisterManagement => {
642                NnsProposalTopic::ProtocolCanisterManagement.as_str()
643            }
644        }
645    }
646
647    pub(in crate::nns) const fn topic_code(self) -> Option<i32> {
648        match self {
649            Self::Any => None,
650            Self::NeuronManagement => Some(NnsProposalTopic::NeuronManagement.code()),
651            Self::ExchangeRate => Some(NnsProposalTopic::ExchangeRate.code()),
652            Self::NetworkEconomics => Some(NnsProposalTopic::NetworkEconomics.code()),
653            Self::Governance => Some(NnsProposalTopic::Governance.code()),
654            Self::NodeAdmin => Some(NnsProposalTopic::NodeAdmin.code()),
655            Self::ParticipantManagement => Some(NnsProposalTopic::ParticipantManagement.code()),
656            Self::SubnetManagement => Some(NnsProposalTopic::SubnetManagement.code()),
657            Self::NetworkCanisterManagement => {
658                Some(NnsProposalTopic::NetworkCanisterManagement.code())
659            }
660            Self::Kyc => Some(NnsProposalTopic::Kyc.code()),
661            Self::NodeProviderRewards => Some(NnsProposalTopic::NodeProviderRewards.code()),
662            Self::IcOsVersionDeployment => Some(NnsProposalTopic::IcOsVersionDeployment.code()),
663            Self::IcOsVersionElection => Some(NnsProposalTopic::IcOsVersionElection.code()),
664            Self::SnsAndCommunityFund => Some(NnsProposalTopic::SnsAndCommunityFund.code()),
665            Self::ApiBoundaryNodeManagement => {
666                Some(NnsProposalTopic::ApiBoundaryNodeManagement.code())
667            }
668            Self::SubnetRental => Some(NnsProposalTopic::SubnetRental.code()),
669            Self::ApplicationCanisterManagement => {
670                Some(NnsProposalTopic::ApplicationCanisterManagement.code())
671            }
672            Self::ProtocolCanisterManagement => {
673                Some(NnsProposalTopic::ProtocolCanisterManagement.code())
674            }
675        }
676    }
677}
678
679#[cfg(test)]
680mod tests {
681    use super::*;
682
683    #[test]
684    fn proposal_status_codes_and_labels_round_trip() {
685        for (status, code, label) in [
686            (NnsProposalStatus::Unspecified, 0, "unspecified"),
687            (NnsProposalStatus::Open, 1, "open"),
688            (NnsProposalStatus::Rejected, 2, "rejected"),
689            (NnsProposalStatus::Adopted, 3, "adopted"),
690            (NnsProposalStatus::Executed, 4, "executed"),
691            (NnsProposalStatus::Failed, 5, "failed"),
692        ] {
693            assert_eq!(status.code(), code);
694            assert_eq!(NnsProposalStatus::from_code(code), status);
695            assert_json_label(status, label);
696        }
697        assert_eq!(
698            NnsProposalStatus::from_code(99),
699            NnsProposalStatus::Unspecified
700        );
701    }
702
703    #[test]
704    fn proposal_reward_status_codes_and_labels_round_trip() {
705        for (status, code, label) in [
706            (NnsProposalRewardStatus::Unspecified, 0, "unspecified"),
707            (NnsProposalRewardStatus::AcceptVotes, 1, "accept-votes"),
708            (NnsProposalRewardStatus::ReadyToSettle, 2, "ready-to-settle"),
709            (NnsProposalRewardStatus::Settled, 3, "settled"),
710            (NnsProposalRewardStatus::Ineligible, 4, "ineligible"),
711        ] {
712            assert_eq!(status.code(), code);
713            assert_eq!(NnsProposalRewardStatus::from_code(code), status);
714            assert_json_label(status, label);
715        }
716        assert_eq!(
717            NnsProposalRewardStatus::from_code(99),
718            NnsProposalRewardStatus::Unspecified
719        );
720    }
721
722    #[test]
723    fn proposal_vote_codes_and_labels_round_trip() {
724        for (vote, code, label) in [
725            (NnsProposalVote::Unspecified, 0, "unspecified"),
726            (NnsProposalVote::Yes, 1, "yes"),
727            (NnsProposalVote::No, 2, "no"),
728        ] {
729            assert_eq!(vote.code(), code);
730            assert_eq!(NnsProposalVote::from_code(code), vote);
731            assert_json_label(vote, label);
732        }
733        assert_eq!(NnsProposalVote::from_code(99), NnsProposalVote::Unspecified);
734    }
735
736    #[test]
737    fn proposal_topic_codes_and_labels_round_trip() {
738        for (topic, code, label) in [
739            (NnsProposalTopic::Unspecified, 0, "unspecified"),
740            (NnsProposalTopic::NeuronManagement, 1, "neuron-management"),
741            (NnsProposalTopic::ExchangeRate, 2, "exchange-rate"),
742            (NnsProposalTopic::NetworkEconomics, 3, "network-economics"),
743            (NnsProposalTopic::Governance, 4, "governance"),
744            (NnsProposalTopic::NodeAdmin, 5, "node-admin"),
745            (
746                NnsProposalTopic::ParticipantManagement,
747                6,
748                "participant-management",
749            ),
750            (NnsProposalTopic::SubnetManagement, 7, "subnet-management"),
751            (
752                NnsProposalTopic::NetworkCanisterManagement,
753                8,
754                "network-canister-management",
755            ),
756            (NnsProposalTopic::Kyc, 9, "kyc"),
757            (
758                NnsProposalTopic::NodeProviderRewards,
759                10,
760                "node-provider-rewards",
761            ),
762            (
763                NnsProposalTopic::IcOsVersionDeployment,
764                12,
765                "ic-os-version-deployment",
766            ),
767            (
768                NnsProposalTopic::IcOsVersionElection,
769                13,
770                "ic-os-version-election",
771            ),
772            (
773                NnsProposalTopic::SnsAndCommunityFund,
774                14,
775                "sns-and-community-fund",
776            ),
777            (
778                NnsProposalTopic::ApiBoundaryNodeManagement,
779                15,
780                "api-boundary-node-management",
781            ),
782            (NnsProposalTopic::SubnetRental, 16, "subnet-rental"),
783            (
784                NnsProposalTopic::ApplicationCanisterManagement,
785                17,
786                "application-canister-management",
787            ),
788            (
789                NnsProposalTopic::ProtocolCanisterManagement,
790                18,
791                "protocol-canister-management",
792            ),
793        ] {
794            assert_eq!(topic.code(), code);
795            assert_eq!(NnsProposalTopic::from_code(code), topic);
796            assert_json_label(topic, label);
797        }
798        assert_eq!(
799            NnsProposalTopic::from_code(11),
800            NnsProposalTopic::Unspecified
801        );
802        assert_eq!(
803            NnsProposalTopic::from_code(99),
804            NnsProposalTopic::Unspecified
805        );
806    }
807
808    fn assert_json_label<T>(value: T, label: &str)
809    where
810        T: Copy + std::fmt::Debug + Eq + Serialize + serde::de::DeserializeOwned,
811    {
812        assert_eq!(
813            serde_json::to_string(&value).unwrap(),
814            format!("\"{label}\"")
815        );
816        assert_eq!(
817            serde_json::from_str::<T>(&format!("\"{label}\"")).unwrap(),
818            value
819        );
820    }
821}