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