1use crate::report::SortDirection;
8use serde::{Deserialize, Serialize};
9
10macro_rules! governance_code_enum {
11 (
12 $(#[$enum_attr:meta])*
13 pub enum $name:ident {
14 $($(#[$variant_attr:meta])* $variant:ident = $code:literal => $label:literal,)*
15 }
16 ) => {
17 $(#[$enum_attr])*
18 pub enum $name {
19 $($(#[$variant_attr])* #[serde(rename = $label)] $variant,)*
20 }
21
22 impl $name {
23 #[must_use]
25 pub const fn from_code(code: i32) -> Self {
26 match code {
27 $($code => Self::$variant,)*
28 _ => Self::Unspecified,
29 }
30 }
31
32 #[must_use]
34 pub const fn code(self) -> i32 {
35 match self { $(Self::$variant => $code,)* }
36 }
37
38 #[must_use]
40 pub const fn as_str(self) -> &'static str {
41 match self { $(Self::$variant => $label,)* }
42 }
43 }
44 };
45}
46
47governance_code_enum! {
48 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
55 pub enum NnsProposalStatus {
56 Unspecified = 0 => "unspecified",
58 Open = 1 => "open",
60 Rejected = 2 => "rejected",
62 Adopted = 3 => "adopted",
64 Executed = 4 => "executed",
66 Failed = 5 => "failed",
68 }
69}
70
71governance_code_enum! {
72 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
79 pub enum NnsProposalRewardStatus {
80 Unspecified = 0 => "unspecified",
82 AcceptVotes = 1 => "accept-votes",
84 ReadyToSettle = 2 => "ready-to-settle",
86 Settled = 3 => "settled",
88 Ineligible = 4 => "ineligible",
90 }
91}
92
93governance_code_enum! {
94 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
101 pub enum NnsProposalTopic {
102 Unspecified = 0 => "unspecified",
104 NeuronManagement = 1 => "neuron-management",
106 ExchangeRate = 2 => "exchange-rate",
108 NetworkEconomics = 3 => "network-economics",
110 Governance = 4 => "governance",
112 NodeAdmin = 5 => "node-admin",
114 ParticipantManagement = 6 => "participant-management",
116 SubnetManagement = 7 => "subnet-management",
118 NetworkCanisterManagement = 8 => "network-canister-management",
120 Kyc = 9 => "kyc",
122 NodeProviderRewards = 10 => "node-provider-rewards",
124 IcOsVersionDeployment = 12 => "ic-os-version-deployment",
126 IcOsVersionElection = 13 => "ic-os-version-election",
128 SnsAndCommunityFund = 14 => "sns-and-community-fund",
130 ApiBoundaryNodeManagement = 15 => "api-boundary-node-management",
132 SubnetRental = 16 => "subnet-rental",
134 ApplicationCanisterManagement = 17 => "application-canister-management",
136 ProtocolCanisterManagement = 18 => "protocol-canister-management",
138 }
139}
140
141#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
148pub enum NnsProposalListSort {
149 #[default]
151 Api,
152 Id,
154 Status,
156 RewardStatus,
158 Topic,
160 Proposer,
162 Title,
164 Action,
166 Yes,
168 No,
170 TotalVotes,
172 TallyTime,
174 VotingPower,
176 Ballots,
178 RejectCost,
180 RewardRound,
182 Proposed,
184 Deadline,
186 Decided,
188 Executed,
190 Failed,
192}
193
194impl NnsProposalListSort {
195 #[must_use]
197 pub const fn as_str(self) -> &'static str {
198 match self {
199 Self::Api => "api",
200 Self::Id => "id",
201 Self::Status => "status",
202 Self::RewardStatus => "reward-status",
203 Self::Topic => "topic",
204 Self::Proposer => "proposer",
205 Self::Title => "title",
206 Self::Action => "action",
207 Self::Yes => "yes",
208 Self::No => "no",
209 Self::TotalVotes => "total-votes",
210 Self::TallyTime => "tally-time",
211 Self::VotingPower => "voting-power",
212 Self::Ballots => "ballots",
213 Self::RejectCost => "reject-cost",
214 Self::RewardRound => "reward-round",
215 Self::Proposed => "proposed",
216 Self::Deadline => "deadline",
217 Self::Decided => "decided",
218 Self::Executed => "executed",
219 Self::Failed => "failed",
220 }
221 }
222
223 #[must_use]
225 pub const fn default_direction(self) -> SortDirection {
226 match self {
227 Self::Status
228 | Self::RewardStatus
229 | Self::Topic
230 | Self::Proposer
231 | Self::Title
232 | Self::Action => SortDirection::Asc,
233 _ => SortDirection::Desc,
234 }
235 }
236
237 #[must_use]
239 pub const fn uses_local_direction(self) -> bool {
240 !matches!(self, Self::Api)
241 }
242
243 #[must_use]
245 pub const fn direction_label(self, direction: SortDirection) -> &'static str {
246 match self {
247 Self::Api => "none",
248 _ => direction.as_str(),
249 }
250 }
251}
252
253#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
260pub enum NnsProposalStatusFilter {
261 #[default]
263 Any,
264 Open,
266 Rejected,
268 Adopted,
270 Executed,
272 Failed,
274}
275
276#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
283pub enum NnsProposalRewardStatusFilter {
284 #[default]
286 Any,
287 AcceptVotes,
289 ReadyToSettle,
291 Settled,
293 Ineligible,
295}
296
297impl NnsProposalRewardStatusFilter {
298 #[must_use]
300 pub const fn as_str(self) -> &'static str {
301 match self.classification() {
302 None => "any",
303 Some(value) => value.as_str(),
304 }
305 }
306
307 #[cfg(any(
308 feature = "nns-host",
309 all(feature = "canister", target_arch = "wasm32")
310 ))]
311 pub(in crate::nns) const fn governance_reward_status_code(self) -> Option<i32> {
312 match self.classification() {
313 None => None,
314 Some(value) => Some(value.code()),
315 }
316 }
317
318 const fn classification(self) -> Option<NnsProposalRewardStatus> {
319 match self {
320 Self::Any => None,
321 Self::AcceptVotes => Some(NnsProposalRewardStatus::AcceptVotes),
322 Self::ReadyToSettle => Some(NnsProposalRewardStatus::ReadyToSettle),
323 Self::Settled => Some(NnsProposalRewardStatus::Settled),
324 Self::Ineligible => Some(NnsProposalRewardStatus::Ineligible),
325 }
326 }
327}
328
329impl NnsProposalStatusFilter {
330 #[must_use]
332 pub const fn as_str(self) -> &'static str {
333 match self.classification() {
334 None => "any",
335 Some(value) => value.as_str(),
336 }
337 }
338
339 #[cfg(any(
340 feature = "nns-host",
341 all(feature = "canister", target_arch = "wasm32")
342 ))]
343 pub(in crate::nns) const fn governance_status_code(self) -> Option<i32> {
344 match self.classification() {
345 None => None,
346 Some(value) => Some(value.code()),
347 }
348 }
349
350 const fn classification(self) -> Option<NnsProposalStatus> {
351 match self {
352 Self::Any => None,
353 Self::Open => Some(NnsProposalStatus::Open),
354 Self::Rejected => Some(NnsProposalStatus::Rejected),
355 Self::Adopted => Some(NnsProposalStatus::Adopted),
356 Self::Executed => Some(NnsProposalStatus::Executed),
357 Self::Failed => Some(NnsProposalStatus::Failed),
358 }
359 }
360}
361
362#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
369pub enum NnsProposalTopicFilter {
370 #[default]
372 Any,
373 NeuronManagement,
375 ExchangeRate,
377 NetworkEconomics,
379 Governance,
381 NodeAdmin,
383 ParticipantManagement,
385 SubnetManagement,
387 NetworkCanisterManagement,
389 Kyc,
391 NodeProviderRewards,
393 IcOsVersionDeployment,
395 IcOsVersionElection,
397 SnsAndCommunityFund,
399 ApiBoundaryNodeManagement,
401 SubnetRental,
403 ApplicationCanisterManagement,
405 ProtocolCanisterManagement,
407}
408
409impl NnsProposalTopicFilter {
410 #[must_use]
412 pub const fn as_str(self) -> &'static str {
413 match self.classification() {
414 None => "any",
415 Some(value) => value.as_str(),
416 }
417 }
418
419 pub(in crate::nns) const fn topic_code(self) -> Option<i32> {
420 match self.classification() {
421 None => None,
422 Some(value) => Some(value.code()),
423 }
424 }
425
426 const fn classification(self) -> Option<NnsProposalTopic> {
427 match self {
428 Self::Any => None,
429 Self::NeuronManagement => Some(NnsProposalTopic::NeuronManagement),
430 Self::ExchangeRate => Some(NnsProposalTopic::ExchangeRate),
431 Self::NetworkEconomics => Some(NnsProposalTopic::NetworkEconomics),
432 Self::Governance => Some(NnsProposalTopic::Governance),
433 Self::NodeAdmin => Some(NnsProposalTopic::NodeAdmin),
434 Self::ParticipantManagement => Some(NnsProposalTopic::ParticipantManagement),
435 Self::SubnetManagement => Some(NnsProposalTopic::SubnetManagement),
436 Self::NetworkCanisterManagement => Some(NnsProposalTopic::NetworkCanisterManagement),
437 Self::Kyc => Some(NnsProposalTopic::Kyc),
438 Self::NodeProviderRewards => Some(NnsProposalTopic::NodeProviderRewards),
439 Self::IcOsVersionDeployment => Some(NnsProposalTopic::IcOsVersionDeployment),
440 Self::IcOsVersionElection => Some(NnsProposalTopic::IcOsVersionElection),
441 Self::SnsAndCommunityFund => Some(NnsProposalTopic::SnsAndCommunityFund),
442 Self::ApiBoundaryNodeManagement => Some(NnsProposalTopic::ApiBoundaryNodeManagement),
443 Self::SubnetRental => Some(NnsProposalTopic::SubnetRental),
444 Self::ApplicationCanisterManagement => {
445 Some(NnsProposalTopic::ApplicationCanisterManagement)
446 }
447 Self::ProtocolCanisterManagement => Some(NnsProposalTopic::ProtocolCanisterManagement),
448 }
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[test]
457 fn proposal_status_codes_and_labels_round_trip() {
458 for (status, code, label) in [
459 (NnsProposalStatus::Unspecified, 0, "unspecified"),
460 (NnsProposalStatus::Open, 1, "open"),
461 (NnsProposalStatus::Rejected, 2, "rejected"),
462 (NnsProposalStatus::Adopted, 3, "adopted"),
463 (NnsProposalStatus::Executed, 4, "executed"),
464 (NnsProposalStatus::Failed, 5, "failed"),
465 ] {
466 assert_eq!(status.code(), code);
467 assert_eq!(NnsProposalStatus::from_code(code), status);
468 assert_json_label(status, label);
469 }
470 assert_eq!(
471 NnsProposalStatus::from_code(99),
472 NnsProposalStatus::Unspecified
473 );
474 }
475
476 #[test]
477 fn proposal_reward_status_codes_and_labels_round_trip() {
478 for (status, code, label) in [
479 (NnsProposalRewardStatus::Unspecified, 0, "unspecified"),
480 (NnsProposalRewardStatus::AcceptVotes, 1, "accept-votes"),
481 (NnsProposalRewardStatus::ReadyToSettle, 2, "ready-to-settle"),
482 (NnsProposalRewardStatus::Settled, 3, "settled"),
483 (NnsProposalRewardStatus::Ineligible, 4, "ineligible"),
484 ] {
485 assert_eq!(status.code(), code);
486 assert_eq!(NnsProposalRewardStatus::from_code(code), status);
487 assert_json_label(status, label);
488 }
489 assert_eq!(
490 NnsProposalRewardStatus::from_code(99),
491 NnsProposalRewardStatus::Unspecified
492 );
493 }
494
495 #[test]
496 fn proposal_topic_codes_and_labels_round_trip() {
497 for (topic, code, label) in [
498 (NnsProposalTopic::Unspecified, 0, "unspecified"),
499 (NnsProposalTopic::NeuronManagement, 1, "neuron-management"),
500 (NnsProposalTopic::ExchangeRate, 2, "exchange-rate"),
501 (NnsProposalTopic::NetworkEconomics, 3, "network-economics"),
502 (NnsProposalTopic::Governance, 4, "governance"),
503 (NnsProposalTopic::NodeAdmin, 5, "node-admin"),
504 (
505 NnsProposalTopic::ParticipantManagement,
506 6,
507 "participant-management",
508 ),
509 (NnsProposalTopic::SubnetManagement, 7, "subnet-management"),
510 (
511 NnsProposalTopic::NetworkCanisterManagement,
512 8,
513 "network-canister-management",
514 ),
515 (NnsProposalTopic::Kyc, 9, "kyc"),
516 (
517 NnsProposalTopic::NodeProviderRewards,
518 10,
519 "node-provider-rewards",
520 ),
521 (
522 NnsProposalTopic::IcOsVersionDeployment,
523 12,
524 "ic-os-version-deployment",
525 ),
526 (
527 NnsProposalTopic::IcOsVersionElection,
528 13,
529 "ic-os-version-election",
530 ),
531 (
532 NnsProposalTopic::SnsAndCommunityFund,
533 14,
534 "sns-and-community-fund",
535 ),
536 (
537 NnsProposalTopic::ApiBoundaryNodeManagement,
538 15,
539 "api-boundary-node-management",
540 ),
541 (NnsProposalTopic::SubnetRental, 16, "subnet-rental"),
542 (
543 NnsProposalTopic::ApplicationCanisterManagement,
544 17,
545 "application-canister-management",
546 ),
547 (
548 NnsProposalTopic::ProtocolCanisterManagement,
549 18,
550 "protocol-canister-management",
551 ),
552 ] {
553 assert_eq!(topic.code(), code);
554 assert_eq!(NnsProposalTopic::from_code(code), topic);
555 assert_json_label(topic, label);
556 }
557 assert_eq!(
558 NnsProposalTopic::from_code(11),
559 NnsProposalTopic::Unspecified
560 );
561 assert_eq!(
562 NnsProposalTopic::from_code(99),
563 NnsProposalTopic::Unspecified
564 );
565 }
566
567 fn assert_json_label<T>(value: T, label: &str)
568 where
569 T: Copy + std::fmt::Debug + Eq + Serialize + serde::de::DeserializeOwned,
570 {
571 assert_eq!(
572 serde_json::to_string(&value).unwrap(),
573 format!("\"{label}\"")
574 );
575 assert_eq!(
576 serde_json::from_str::<T>(&format!("\"{label}\"")).unwrap(),
577 value
578 );
579 }
580}