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(any(
486        feature = "nns-host",
487        all(feature = "canister", target_arch = "wasm32"),
488        test
489    ))]
490    pub(in crate::nns) const fn governance_reward_status_code(self) -> Option<i32> {
491        match self {
492            Self::Any => None,
493            Self::AcceptVotes => Some(NnsProposalRewardStatus::AcceptVotes.code()),
494            Self::ReadyToSettle => Some(NnsProposalRewardStatus::ReadyToSettle.code()),
495            Self::Settled => Some(NnsProposalRewardStatus::Settled.code()),
496            Self::Ineligible => Some(NnsProposalRewardStatus::Ineligible.code()),
497        }
498    }
499}
500
501impl NnsProposalStatusFilter {
502    #[must_use]
503    pub const fn as_str(self) -> &'static str {
504        match self {
505            Self::Any => "any",
506            Self::Open => NnsProposalStatus::Open.as_str(),
507            Self::Rejected => NnsProposalStatus::Rejected.as_str(),
508            Self::Adopted => NnsProposalStatus::Adopted.as_str(),
509            Self::Executed => NnsProposalStatus::Executed.as_str(),
510            Self::Failed => NnsProposalStatus::Failed.as_str(),
511        }
512    }
513
514    #[cfg(any(
515        feature = "nns-host",
516        all(feature = "canister", target_arch = "wasm32"),
517        test
518    ))]
519    pub(in crate::nns) const fn governance_status_code(self) -> Option<i32> {
520        match self {
521            Self::Any => None,
522            Self::Open => Some(NnsProposalStatus::Open.code()),
523            Self::Rejected => Some(NnsProposalStatus::Rejected.code()),
524            Self::Adopted => Some(NnsProposalStatus::Adopted.code()),
525            Self::Executed => Some(NnsProposalStatus::Executed.code()),
526            Self::Failed => Some(NnsProposalStatus::Failed.code()),
527        }
528    }
529}
530
531///
532/// NnsProposalTopicFilter
533///
534/// Report-model topic filter for bounded NNS proposal listings.
535///
536
537#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
538pub enum NnsProposalTopicFilter {
539    #[default]
540    Any,
541    NeuronManagement,
542    ExchangeRate,
543    NetworkEconomics,
544    Governance,
545    NodeAdmin,
546    ParticipantManagement,
547    SubnetManagement,
548    NetworkCanisterManagement,
549    Kyc,
550    NodeProviderRewards,
551    IcOsVersionDeployment,
552    IcOsVersionElection,
553    SnsAndCommunityFund,
554    ApiBoundaryNodeManagement,
555    SubnetRental,
556    ApplicationCanisterManagement,
557    ProtocolCanisterManagement,
558}
559
560impl NnsProposalTopicFilter {
561    #[must_use]
562    pub const fn as_str(self) -> &'static str {
563        match self {
564            Self::Any => "any",
565            Self::NeuronManagement => NnsProposalTopic::NeuronManagement.as_str(),
566            Self::ExchangeRate => NnsProposalTopic::ExchangeRate.as_str(),
567            Self::NetworkEconomics => NnsProposalTopic::NetworkEconomics.as_str(),
568            Self::Governance => NnsProposalTopic::Governance.as_str(),
569            Self::NodeAdmin => NnsProposalTopic::NodeAdmin.as_str(),
570            Self::ParticipantManagement => NnsProposalTopic::ParticipantManagement.as_str(),
571            Self::SubnetManagement => NnsProposalTopic::SubnetManagement.as_str(),
572            Self::NetworkCanisterManagement => NnsProposalTopic::NetworkCanisterManagement.as_str(),
573            Self::Kyc => NnsProposalTopic::Kyc.as_str(),
574            Self::NodeProviderRewards => NnsProposalTopic::NodeProviderRewards.as_str(),
575            Self::IcOsVersionDeployment => NnsProposalTopic::IcOsVersionDeployment.as_str(),
576            Self::IcOsVersionElection => NnsProposalTopic::IcOsVersionElection.as_str(),
577            Self::SnsAndCommunityFund => NnsProposalTopic::SnsAndCommunityFund.as_str(),
578            Self::ApiBoundaryNodeManagement => NnsProposalTopic::ApiBoundaryNodeManagement.as_str(),
579            Self::SubnetRental => NnsProposalTopic::SubnetRental.as_str(),
580            Self::ApplicationCanisterManagement => {
581                NnsProposalTopic::ApplicationCanisterManagement.as_str()
582            }
583            Self::ProtocolCanisterManagement => {
584                NnsProposalTopic::ProtocolCanisterManagement.as_str()
585            }
586        }
587    }
588
589    pub(in crate::nns) const fn topic_code(self) -> Option<i32> {
590        match self {
591            Self::Any => None,
592            Self::NeuronManagement => Some(NnsProposalTopic::NeuronManagement.code()),
593            Self::ExchangeRate => Some(NnsProposalTopic::ExchangeRate.code()),
594            Self::NetworkEconomics => Some(NnsProposalTopic::NetworkEconomics.code()),
595            Self::Governance => Some(NnsProposalTopic::Governance.code()),
596            Self::NodeAdmin => Some(NnsProposalTopic::NodeAdmin.code()),
597            Self::ParticipantManagement => Some(NnsProposalTopic::ParticipantManagement.code()),
598            Self::SubnetManagement => Some(NnsProposalTopic::SubnetManagement.code()),
599            Self::NetworkCanisterManagement => {
600                Some(NnsProposalTopic::NetworkCanisterManagement.code())
601            }
602            Self::Kyc => Some(NnsProposalTopic::Kyc.code()),
603            Self::NodeProviderRewards => Some(NnsProposalTopic::NodeProviderRewards.code()),
604            Self::IcOsVersionDeployment => Some(NnsProposalTopic::IcOsVersionDeployment.code()),
605            Self::IcOsVersionElection => Some(NnsProposalTopic::IcOsVersionElection.code()),
606            Self::SnsAndCommunityFund => Some(NnsProposalTopic::SnsAndCommunityFund.code()),
607            Self::ApiBoundaryNodeManagement => {
608                Some(NnsProposalTopic::ApiBoundaryNodeManagement.code())
609            }
610            Self::SubnetRental => Some(NnsProposalTopic::SubnetRental.code()),
611            Self::ApplicationCanisterManagement => {
612                Some(NnsProposalTopic::ApplicationCanisterManagement.code())
613            }
614            Self::ProtocolCanisterManagement => {
615                Some(NnsProposalTopic::ProtocolCanisterManagement.code())
616            }
617        }
618    }
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624
625    #[test]
626    fn proposal_status_codes_and_labels_round_trip() {
627        for (status, code, label) in [
628            (NnsProposalStatus::Unspecified, 0, "unspecified"),
629            (NnsProposalStatus::Open, 1, "open"),
630            (NnsProposalStatus::Rejected, 2, "rejected"),
631            (NnsProposalStatus::Adopted, 3, "adopted"),
632            (NnsProposalStatus::Executed, 4, "executed"),
633            (NnsProposalStatus::Failed, 5, "failed"),
634        ] {
635            assert_eq!(status.code(), code);
636            assert_eq!(NnsProposalStatus::from_code(code), status);
637            assert_json_label(status, label);
638        }
639        assert_eq!(
640            NnsProposalStatus::from_code(99),
641            NnsProposalStatus::Unspecified
642        );
643    }
644
645    #[test]
646    fn proposal_reward_status_codes_and_labels_round_trip() {
647        for (status, code, label) in [
648            (NnsProposalRewardStatus::Unspecified, 0, "unspecified"),
649            (NnsProposalRewardStatus::AcceptVotes, 1, "accept-votes"),
650            (NnsProposalRewardStatus::ReadyToSettle, 2, "ready-to-settle"),
651            (NnsProposalRewardStatus::Settled, 3, "settled"),
652            (NnsProposalRewardStatus::Ineligible, 4, "ineligible"),
653        ] {
654            assert_eq!(status.code(), code);
655            assert_eq!(NnsProposalRewardStatus::from_code(code), status);
656            assert_json_label(status, label);
657        }
658        assert_eq!(
659            NnsProposalRewardStatus::from_code(99),
660            NnsProposalRewardStatus::Unspecified
661        );
662    }
663
664    #[test]
665    fn proposal_vote_codes_and_labels_round_trip() {
666        for (vote, code, label) in [
667            (NnsProposalVote::Unspecified, 0, "unspecified"),
668            (NnsProposalVote::Yes, 1, "yes"),
669            (NnsProposalVote::No, 2, "no"),
670        ] {
671            assert_eq!(vote.code(), code);
672            assert_eq!(NnsProposalVote::from_code(code), vote);
673            assert_json_label(vote, label);
674        }
675        assert_eq!(NnsProposalVote::from_code(99), NnsProposalVote::Unspecified);
676    }
677
678    #[test]
679    fn proposal_topic_codes_and_labels_round_trip() {
680        for (topic, code, label) in [
681            (NnsProposalTopic::Unspecified, 0, "unspecified"),
682            (NnsProposalTopic::NeuronManagement, 1, "neuron-management"),
683            (NnsProposalTopic::ExchangeRate, 2, "exchange-rate"),
684            (NnsProposalTopic::NetworkEconomics, 3, "network-economics"),
685            (NnsProposalTopic::Governance, 4, "governance"),
686            (NnsProposalTopic::NodeAdmin, 5, "node-admin"),
687            (
688                NnsProposalTopic::ParticipantManagement,
689                6,
690                "participant-management",
691            ),
692            (NnsProposalTopic::SubnetManagement, 7, "subnet-management"),
693            (
694                NnsProposalTopic::NetworkCanisterManagement,
695                8,
696                "network-canister-management",
697            ),
698            (NnsProposalTopic::Kyc, 9, "kyc"),
699            (
700                NnsProposalTopic::NodeProviderRewards,
701                10,
702                "node-provider-rewards",
703            ),
704            (
705                NnsProposalTopic::IcOsVersionDeployment,
706                12,
707                "ic-os-version-deployment",
708            ),
709            (
710                NnsProposalTopic::IcOsVersionElection,
711                13,
712                "ic-os-version-election",
713            ),
714            (
715                NnsProposalTopic::SnsAndCommunityFund,
716                14,
717                "sns-and-community-fund",
718            ),
719            (
720                NnsProposalTopic::ApiBoundaryNodeManagement,
721                15,
722                "api-boundary-node-management",
723            ),
724            (NnsProposalTopic::SubnetRental, 16, "subnet-rental"),
725            (
726                NnsProposalTopic::ApplicationCanisterManagement,
727                17,
728                "application-canister-management",
729            ),
730            (
731                NnsProposalTopic::ProtocolCanisterManagement,
732                18,
733                "protocol-canister-management",
734            ),
735        ] {
736            assert_eq!(topic.code(), code);
737            assert_eq!(NnsProposalTopic::from_code(code), topic);
738            assert_json_label(topic, label);
739        }
740        assert_eq!(
741            NnsProposalTopic::from_code(11),
742            NnsProposalTopic::Unspecified
743        );
744        assert_eq!(
745            NnsProposalTopic::from_code(99),
746            NnsProposalTopic::Unspecified
747        );
748    }
749
750    fn assert_json_label<T>(value: T, label: &str)
751    where
752        T: Copy + std::fmt::Debug + Eq + Serialize + serde::de::DeserializeOwned,
753    {
754        assert_eq!(
755            serde_json::to_string(&value).unwrap(),
756            format!("\"{label}\"")
757        );
758        assert_eq!(
759            serde_json::from_str::<T>(&format!("\"{label}\"")).unwrap(),
760            value
761        );
762    }
763}