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    #[default]
339    Api,
340    Id,
341    Status,
342    RewardStatus,
343    Topic,
344    Proposer,
345    Title,
346    Action,
347    Yes,
348    No,
349    TotalVotes,
350    TallyTime,
351    VotingPower,
352    Ballots,
353    RejectCost,
354    RewardRound,
355    Proposed,
356    Deadline,
357    Decided,
358    Executed,
359    Failed,
360}
361
362impl NnsProposalListSort {
363    #[must_use]
364    pub const fn as_str(self) -> &'static str {
365        match self {
366            Self::Api => NNS_PROPOSAL_SORT_API_LABEL,
367            Self::Id => NNS_PROPOSAL_SORT_ID_LABEL,
368            Self::Status => NNS_PROPOSAL_SORT_STATUS_LABEL,
369            Self::RewardStatus => NNS_PROPOSAL_SORT_REWARD_STATUS_LABEL,
370            Self::Topic => NNS_PROPOSAL_SORT_TOPIC_LABEL,
371            Self::Proposer => NNS_PROPOSAL_SORT_PROPOSER_LABEL,
372            Self::Title => NNS_PROPOSAL_SORT_TITLE_LABEL,
373            Self::Action => NNS_PROPOSAL_SORT_ACTION_LABEL,
374            Self::Yes => NNS_PROPOSAL_SORT_YES_LABEL,
375            Self::No => NNS_PROPOSAL_SORT_NO_LABEL,
376            Self::TotalVotes => NNS_PROPOSAL_SORT_TOTAL_VOTES_LABEL,
377            Self::TallyTime => NNS_PROPOSAL_SORT_TALLY_TIME_LABEL,
378            Self::VotingPower => NNS_PROPOSAL_SORT_VOTING_POWER_LABEL,
379            Self::Ballots => NNS_PROPOSAL_SORT_BALLOTS_LABEL,
380            Self::RejectCost => NNS_PROPOSAL_SORT_REJECT_COST_LABEL,
381            Self::RewardRound => NNS_PROPOSAL_SORT_REWARD_ROUND_LABEL,
382            Self::Proposed => NNS_PROPOSAL_SORT_PROPOSED_LABEL,
383            Self::Deadline => NNS_PROPOSAL_SORT_DEADLINE_LABEL,
384            Self::Decided => NNS_PROPOSAL_SORT_DECIDED_LABEL,
385            Self::Executed => NNS_PROPOSAL_SORT_EXECUTED_LABEL,
386            Self::Failed => NNS_PROPOSAL_SORT_FAILED_LABEL,
387        }
388    }
389
390    #[must_use]
391    pub const fn default_direction(self) -> NnsProposalSortDirection {
392        match self {
393            Self::Status
394            | Self::RewardStatus
395            | Self::Topic
396            | Self::Proposer
397            | Self::Title
398            | Self::Action => NnsProposalSortDirection::Asc,
399            _ => NnsProposalSortDirection::Desc,
400        }
401    }
402
403    #[must_use]
404    pub const fn uses_local_direction(self) -> bool {
405        !matches!(self, Self::Api)
406    }
407
408    #[must_use]
409    pub const fn direction_label(self, direction: NnsProposalSortDirection) -> &'static str {
410        match self {
411            Self::Api => NNS_PROPOSAL_SORT_NONE_LABEL,
412            _ => direction.as_str(),
413        }
414    }
415}
416
417///
418/// NnsProposalSortDirection
419///
420/// Report-model direction selector for local NNS proposal sorting.
421///
422
423#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
424pub enum NnsProposalSortDirection {
425    Asc,
426    #[default]
427    Desc,
428}
429
430impl NnsProposalSortDirection {
431    #[must_use]
432    pub const fn as_str(self) -> &'static str {
433        match self {
434            Self::Asc => NNS_PROPOSAL_SORT_ASC_LABEL,
435            Self::Desc => NNS_PROPOSAL_SORT_DESC_LABEL,
436        }
437    }
438}
439
440///
441/// NnsProposalStatusFilter
442///
443/// Report-model status filter for bounded NNS proposal listings.
444///
445
446#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
447pub enum NnsProposalStatusFilter {
448    #[default]
449    Any,
450    Open,
451    Rejected,
452    Adopted,
453    Executed,
454    Failed,
455}
456
457///
458/// NnsProposalRewardStatusFilter
459///
460/// Report-model reward status filter for bounded NNS proposal listings.
461///
462
463#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
464pub enum NnsProposalRewardStatusFilter {
465    #[default]
466    Any,
467    AcceptVotes,
468    ReadyToSettle,
469    Settled,
470    Ineligible,
471}
472
473impl NnsProposalRewardStatusFilter {
474    #[must_use]
475    pub const fn as_str(self) -> &'static str {
476        match self {
477            Self::Any => "any",
478            Self::AcceptVotes => NnsProposalRewardStatus::AcceptVotes.as_str(),
479            Self::ReadyToSettle => NnsProposalRewardStatus::ReadyToSettle.as_str(),
480            Self::Settled => NnsProposalRewardStatus::Settled.as_str(),
481            Self::Ineligible => NnsProposalRewardStatus::Ineligible.as_str(),
482        }
483    }
484
485    #[cfg(feature = "host")]
486    pub(in crate::nns) const fn governance_reward_status_code(self) -> Option<i32> {
487        match self {
488            Self::Any => None,
489            Self::AcceptVotes => Some(NnsProposalRewardStatus::AcceptVotes.code()),
490            Self::ReadyToSettle => Some(NnsProposalRewardStatus::ReadyToSettle.code()),
491            Self::Settled => Some(NnsProposalRewardStatus::Settled.code()),
492            Self::Ineligible => Some(NnsProposalRewardStatus::Ineligible.code()),
493        }
494    }
495}
496
497impl NnsProposalStatusFilter {
498    #[must_use]
499    pub const fn as_str(self) -> &'static str {
500        match self {
501            Self::Any => "any",
502            Self::Open => NnsProposalStatus::Open.as_str(),
503            Self::Rejected => NnsProposalStatus::Rejected.as_str(),
504            Self::Adopted => NnsProposalStatus::Adopted.as_str(),
505            Self::Executed => NnsProposalStatus::Executed.as_str(),
506            Self::Failed => NnsProposalStatus::Failed.as_str(),
507        }
508    }
509
510    #[cfg(feature = "host")]
511    pub(in crate::nns) const fn governance_status_code(self) -> Option<i32> {
512        match self {
513            Self::Any => None,
514            Self::Open => Some(NnsProposalStatus::Open.code()),
515            Self::Rejected => Some(NnsProposalStatus::Rejected.code()),
516            Self::Adopted => Some(NnsProposalStatus::Adopted.code()),
517            Self::Executed => Some(NnsProposalStatus::Executed.code()),
518            Self::Failed => Some(NnsProposalStatus::Failed.code()),
519        }
520    }
521}
522
523///
524/// NnsProposalTopicFilter
525///
526/// Report-model topic filter for bounded NNS proposal listings.
527///
528
529#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
530pub enum NnsProposalTopicFilter {
531    #[default]
532    Any,
533    NeuronManagement,
534    ExchangeRate,
535    NetworkEconomics,
536    Governance,
537    NodeAdmin,
538    ParticipantManagement,
539    SubnetManagement,
540    NetworkCanisterManagement,
541    Kyc,
542    NodeProviderRewards,
543    IcOsVersionDeployment,
544    IcOsVersionElection,
545    SnsAndCommunityFund,
546    ApiBoundaryNodeManagement,
547    SubnetRental,
548    ApplicationCanisterManagement,
549    ProtocolCanisterManagement,
550}
551
552impl NnsProposalTopicFilter {
553    #[must_use]
554    pub const fn as_str(self) -> &'static str {
555        match self {
556            Self::Any => "any",
557            Self::NeuronManagement => NnsProposalTopic::NeuronManagement.as_str(),
558            Self::ExchangeRate => NnsProposalTopic::ExchangeRate.as_str(),
559            Self::NetworkEconomics => NnsProposalTopic::NetworkEconomics.as_str(),
560            Self::Governance => NnsProposalTopic::Governance.as_str(),
561            Self::NodeAdmin => NnsProposalTopic::NodeAdmin.as_str(),
562            Self::ParticipantManagement => NnsProposalTopic::ParticipantManagement.as_str(),
563            Self::SubnetManagement => NnsProposalTopic::SubnetManagement.as_str(),
564            Self::NetworkCanisterManagement => NnsProposalTopic::NetworkCanisterManagement.as_str(),
565            Self::Kyc => NnsProposalTopic::Kyc.as_str(),
566            Self::NodeProviderRewards => NnsProposalTopic::NodeProviderRewards.as_str(),
567            Self::IcOsVersionDeployment => NnsProposalTopic::IcOsVersionDeployment.as_str(),
568            Self::IcOsVersionElection => NnsProposalTopic::IcOsVersionElection.as_str(),
569            Self::SnsAndCommunityFund => NnsProposalTopic::SnsAndCommunityFund.as_str(),
570            Self::ApiBoundaryNodeManagement => NnsProposalTopic::ApiBoundaryNodeManagement.as_str(),
571            Self::SubnetRental => NnsProposalTopic::SubnetRental.as_str(),
572            Self::ApplicationCanisterManagement => {
573                NnsProposalTopic::ApplicationCanisterManagement.as_str()
574            }
575            Self::ProtocolCanisterManagement => {
576                NnsProposalTopic::ProtocolCanisterManagement.as_str()
577            }
578        }
579    }
580
581    #[cfg(feature = "host")]
582    pub(in crate::nns) const fn topic_code(self) -> Option<i32> {
583        match self {
584            Self::Any => None,
585            Self::NeuronManagement => Some(NnsProposalTopic::NeuronManagement.code()),
586            Self::ExchangeRate => Some(NnsProposalTopic::ExchangeRate.code()),
587            Self::NetworkEconomics => Some(NnsProposalTopic::NetworkEconomics.code()),
588            Self::Governance => Some(NnsProposalTopic::Governance.code()),
589            Self::NodeAdmin => Some(NnsProposalTopic::NodeAdmin.code()),
590            Self::ParticipantManagement => Some(NnsProposalTopic::ParticipantManagement.code()),
591            Self::SubnetManagement => Some(NnsProposalTopic::SubnetManagement.code()),
592            Self::NetworkCanisterManagement => {
593                Some(NnsProposalTopic::NetworkCanisterManagement.code())
594            }
595            Self::Kyc => Some(NnsProposalTopic::Kyc.code()),
596            Self::NodeProviderRewards => Some(NnsProposalTopic::NodeProviderRewards.code()),
597            Self::IcOsVersionDeployment => Some(NnsProposalTopic::IcOsVersionDeployment.code()),
598            Self::IcOsVersionElection => Some(NnsProposalTopic::IcOsVersionElection.code()),
599            Self::SnsAndCommunityFund => Some(NnsProposalTopic::SnsAndCommunityFund.code()),
600            Self::ApiBoundaryNodeManagement => {
601                Some(NnsProposalTopic::ApiBoundaryNodeManagement.code())
602            }
603            Self::SubnetRental => Some(NnsProposalTopic::SubnetRental.code()),
604            Self::ApplicationCanisterManagement => {
605                Some(NnsProposalTopic::ApplicationCanisterManagement.code())
606            }
607            Self::ProtocolCanisterManagement => {
608                Some(NnsProposalTopic::ProtocolCanisterManagement.code())
609            }
610        }
611    }
612}
613
614#[cfg(test)]
615mod tests {
616    use super::*;
617
618    #[test]
619    fn proposal_status_codes_and_labels_round_trip() {
620        for (status, code, label) in [
621            (NnsProposalStatus::Unspecified, 0, "unspecified"),
622            (NnsProposalStatus::Open, 1, "open"),
623            (NnsProposalStatus::Rejected, 2, "rejected"),
624            (NnsProposalStatus::Adopted, 3, "adopted"),
625            (NnsProposalStatus::Executed, 4, "executed"),
626            (NnsProposalStatus::Failed, 5, "failed"),
627        ] {
628            assert_eq!(status.code(), code);
629            assert_eq!(NnsProposalStatus::from_code(code), status);
630            assert_json_label(status, label);
631        }
632        assert_eq!(
633            NnsProposalStatus::from_code(99),
634            NnsProposalStatus::Unspecified
635        );
636    }
637
638    #[test]
639    fn proposal_reward_status_codes_and_labels_round_trip() {
640        for (status, code, label) in [
641            (NnsProposalRewardStatus::Unspecified, 0, "unspecified"),
642            (NnsProposalRewardStatus::AcceptVotes, 1, "accept-votes"),
643            (NnsProposalRewardStatus::ReadyToSettle, 2, "ready-to-settle"),
644            (NnsProposalRewardStatus::Settled, 3, "settled"),
645            (NnsProposalRewardStatus::Ineligible, 4, "ineligible"),
646        ] {
647            assert_eq!(status.code(), code);
648            assert_eq!(NnsProposalRewardStatus::from_code(code), status);
649            assert_json_label(status, label);
650        }
651        assert_eq!(
652            NnsProposalRewardStatus::from_code(99),
653            NnsProposalRewardStatus::Unspecified
654        );
655    }
656
657    #[test]
658    fn proposal_vote_codes_and_labels_round_trip() {
659        for (vote, code, label) in [
660            (NnsProposalVote::Unspecified, 0, "unspecified"),
661            (NnsProposalVote::Yes, 1, "yes"),
662            (NnsProposalVote::No, 2, "no"),
663        ] {
664            assert_eq!(vote.code(), code);
665            assert_eq!(NnsProposalVote::from_code(code), vote);
666            assert_json_label(vote, label);
667        }
668        assert_eq!(NnsProposalVote::from_code(99), NnsProposalVote::Unspecified);
669    }
670
671    #[test]
672    fn proposal_topic_codes_and_labels_round_trip() {
673        for (topic, code, label) in [
674            (NnsProposalTopic::Unspecified, 0, "unspecified"),
675            (NnsProposalTopic::NeuronManagement, 1, "neuron-management"),
676            (NnsProposalTopic::ExchangeRate, 2, "exchange-rate"),
677            (NnsProposalTopic::NetworkEconomics, 3, "network-economics"),
678            (NnsProposalTopic::Governance, 4, "governance"),
679            (NnsProposalTopic::NodeAdmin, 5, "node-admin"),
680            (
681                NnsProposalTopic::ParticipantManagement,
682                6,
683                "participant-management",
684            ),
685            (NnsProposalTopic::SubnetManagement, 7, "subnet-management"),
686            (
687                NnsProposalTopic::NetworkCanisterManagement,
688                8,
689                "network-canister-management",
690            ),
691            (NnsProposalTopic::Kyc, 9, "kyc"),
692            (
693                NnsProposalTopic::NodeProviderRewards,
694                10,
695                "node-provider-rewards",
696            ),
697            (
698                NnsProposalTopic::IcOsVersionDeployment,
699                12,
700                "ic-os-version-deployment",
701            ),
702            (
703                NnsProposalTopic::IcOsVersionElection,
704                13,
705                "ic-os-version-election",
706            ),
707            (
708                NnsProposalTopic::SnsAndCommunityFund,
709                14,
710                "sns-and-community-fund",
711            ),
712            (
713                NnsProposalTopic::ApiBoundaryNodeManagement,
714                15,
715                "api-boundary-node-management",
716            ),
717            (NnsProposalTopic::SubnetRental, 16, "subnet-rental"),
718            (
719                NnsProposalTopic::ApplicationCanisterManagement,
720                17,
721                "application-canister-management",
722            ),
723            (
724                NnsProposalTopic::ProtocolCanisterManagement,
725                18,
726                "protocol-canister-management",
727            ),
728        ] {
729            assert_eq!(topic.code(), code);
730            assert_eq!(NnsProposalTopic::from_code(code), topic);
731            assert_json_label(topic, label);
732        }
733        assert_eq!(
734            NnsProposalTopic::from_code(11),
735            NnsProposalTopic::Unspecified
736        );
737        assert_eq!(
738            NnsProposalTopic::from_code(99),
739            NnsProposalTopic::Unspecified
740        );
741    }
742
743    fn assert_json_label<T>(value: T, label: &str)
744    where
745        T: Copy + std::fmt::Debug + Eq + Serialize + serde::de::DeserializeOwned,
746    {
747        assert_eq!(
748            serde_json::to_string(&value).unwrap(),
749            format!("\"{label}\"")
750        );
751        assert_eq!(
752            serde_json::from_str::<T>(&format!("\"{label}\"")).unwrap(),
753            value
754        );
755    }
756}