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 $(#[$unspecified_attr:meta])* Unspecified = 0 => "unspecified",
15 $($(#[$variant_attr:meta])* $variant:ident = $code:literal => $label:literal,)*
16 }
17 filter $filter:ident;
18 ) => {
19 $(#[$enum_attr])*
20 pub enum $name {
21 $(#[$unspecified_attr])* #[serde(rename = "unspecified")] Unspecified,
22 $($(#[$variant_attr])* #[serde(rename = $label)] $variant,)*
23 }
24
25 impl $name {
26 #[must_use]
28 pub const fn from_code(code: i32) -> Self {
29 match code {
30 $($code => Self::$variant,)*
31 _ => Self::Unspecified,
32 }
33 }
34
35 #[must_use]
37 pub const fn code(self) -> i32 {
38 match self { Self::Unspecified => 0, $(Self::$variant => $code,)* }
39 }
40
41 #[must_use]
43 pub const fn as_str(self) -> &'static str {
44 match self { Self::Unspecified => "unspecified", $(Self::$variant => $label,)* }
45 }
46 }
47 #[doc = stringify!($filter)]
49 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
54 pub enum $filter {
55 #[default]
57 Any,
58 $($(#[$variant_attr])* $variant,)*
59 }
60
61 impl $filter {
62 pub const ALL: &'static [Self] = &[Self::Any, $(Self::$variant,)*];
64
65 #[must_use]
67 pub const fn as_str(self) -> &'static str {
68 match self.classification() { None => "any", Some(value) => value.as_str() }
69 }
70
71 const fn classification(self) -> Option<$name> {
72 match self { Self::Any => None, $(Self::$variant => Some($name::$variant),)* }
73 }
74 }
75 };
76}
77
78governance_code_enum! {
79 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
86 pub enum NnsProposalStatus {
87 Unspecified = 0 => "unspecified",
89 Open = 1 => "open",
91 Rejected = 2 => "rejected",
93 Adopted = 3 => "adopted",
95 Executed = 4 => "executed",
97 Failed = 5 => "failed",
99 }
100 filter NnsProposalStatusFilter;
101}
102
103governance_code_enum! {
104 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
111 pub enum NnsProposalRewardStatus {
112 Unspecified = 0 => "unspecified",
114 AcceptVotes = 1 => "accept-votes",
116 ReadyToSettle = 2 => "ready-to-settle",
118 Settled = 3 => "settled",
120 Ineligible = 4 => "ineligible",
122 }
123 filter NnsProposalRewardStatusFilter;
124}
125
126governance_code_enum! {
127 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
134 pub enum NnsProposalTopic {
135 Unspecified = 0 => "unspecified",
137 NeuronManagement = 1 => "neuron-management",
139 ExchangeRate = 2 => "exchange-rate",
141 NetworkEconomics = 3 => "network-economics",
143 Governance = 4 => "governance",
145 NodeAdmin = 5 => "node-admin",
147 ParticipantManagement = 6 => "participant-management",
149 SubnetManagement = 7 => "subnet-management",
151 NetworkCanisterManagement = 8 => "network-canister-management",
153 Kyc = 9 => "kyc",
155 NodeProviderRewards = 10 => "node-provider-rewards",
157 IcOsVersionDeployment = 12 => "ic-os-version-deployment",
159 IcOsVersionElection = 13 => "ic-os-version-election",
161 SnsAndCommunityFund = 14 => "sns-and-community-fund",
163 ApiBoundaryNodeManagement = 15 => "api-boundary-node-management",
165 SubnetRental = 16 => "subnet-rental",
167 ApplicationCanisterManagement = 17 => "application-canister-management",
169 ProtocolCanisterManagement = 18 => "protocol-canister-management",
171 }
172 filter NnsProposalTopicFilter;
173}
174
175macro_rules! proposal_sort {
176 ($($(#[$attr:meta])* $variant:ident => $label:literal,)*) => {
177 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
184 pub enum NnsProposalListSort { $($(#[$attr])* $variant,)* }
185
186 impl NnsProposalListSort {
187 pub const ALL: &'static [Self] = &[$(Self::$variant,)*];
189
190 #[must_use]
192 pub const fn as_str(self) -> &'static str {
193 match self { $(Self::$variant => $label,)* }
194 }
195 }
196 };
197}
198
199proposal_sort! {
200 #[default]
202 Api => "api",
203 Id => "id",
205 Status => "status",
207 RewardStatus => "reward-status",
209 Topic => "topic",
211 Proposer => "proposer",
213 Title => "title",
215 Action => "action",
217 Yes => "yes",
219 No => "no",
221 TotalVotes => "total-votes",
223 TallyTime => "tally-time",
225 VotingPower => "voting-power",
227 Ballots => "ballots",
229 RejectCost => "reject-cost",
231 RewardRound => "reward-round",
233 Proposed => "proposed",
235 Deadline => "deadline",
237 Decided => "decided",
239 Executed => "executed",
241 Failed => "failed",
243}
244
245impl NnsProposalListSort {
246 #[must_use]
248 pub const fn default_direction(self) -> SortDirection {
249 match self {
250 Self::Status
251 | Self::RewardStatus
252 | Self::Topic
253 | Self::Proposer
254 | Self::Title
255 | Self::Action => SortDirection::Asc,
256 _ => SortDirection::Desc,
257 }
258 }
259
260 #[must_use]
262 pub const fn uses_local_direction(self) -> bool {
263 !matches!(self, Self::Api)
264 }
265
266 #[must_use]
268 pub const fn direction_label(self, direction: SortDirection) -> &'static str {
269 match self {
270 Self::Api => "none",
271 _ => direction.as_str(),
272 }
273 }
274}
275
276impl NnsProposalStatusFilter {
277 #[cfg(any(
278 feature = "nns-host",
279 all(feature = "canister", target_arch = "wasm32")
280 ))]
281 pub(in crate::nns) const fn governance_status_code(self) -> Option<i32> {
282 match self.classification() {
283 None => None,
284 Some(value) => Some(value.code()),
285 }
286 }
287}
288
289impl NnsProposalRewardStatusFilter {
290 #[cfg(any(
291 feature = "nns-host",
292 all(feature = "canister", target_arch = "wasm32")
293 ))]
294 pub(in crate::nns) const fn governance_reward_status_code(self) -> Option<i32> {
295 match self.classification() {
296 None => None,
297 Some(value) => Some(value.code()),
298 }
299 }
300}
301
302impl NnsProposalTopicFilter {
303 pub(in crate::nns) const fn topic_code(self) -> Option<i32> {
304 match self.classification() {
305 None => None,
306 Some(value) => Some(value.code()),
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn proposal_status_codes_and_labels_round_trip() {
317 for (status, code, label) in [
318 (NnsProposalStatus::Unspecified, 0, "unspecified"),
319 (NnsProposalStatus::Open, 1, "open"),
320 (NnsProposalStatus::Rejected, 2, "rejected"),
321 (NnsProposalStatus::Adopted, 3, "adopted"),
322 (NnsProposalStatus::Executed, 4, "executed"),
323 (NnsProposalStatus::Failed, 5, "failed"),
324 ] {
325 assert_eq!(status.code(), code);
326 assert_eq!(NnsProposalStatus::from_code(code), status);
327 assert_json_label(status, label);
328 }
329 assert_eq!(
330 NnsProposalStatus::from_code(99),
331 NnsProposalStatus::Unspecified
332 );
333 }
334
335 #[test]
336 fn proposal_reward_status_codes_and_labels_round_trip() {
337 for (status, code, label) in [
338 (NnsProposalRewardStatus::Unspecified, 0, "unspecified"),
339 (NnsProposalRewardStatus::AcceptVotes, 1, "accept-votes"),
340 (NnsProposalRewardStatus::ReadyToSettle, 2, "ready-to-settle"),
341 (NnsProposalRewardStatus::Settled, 3, "settled"),
342 (NnsProposalRewardStatus::Ineligible, 4, "ineligible"),
343 ] {
344 assert_eq!(status.code(), code);
345 assert_eq!(NnsProposalRewardStatus::from_code(code), status);
346 assert_json_label(status, label);
347 }
348 assert_eq!(
349 NnsProposalRewardStatus::from_code(99),
350 NnsProposalRewardStatus::Unspecified
351 );
352 }
353
354 #[test]
355 fn proposal_topic_codes_and_labels_round_trip() {
356 for (topic, code, label) in [
357 (NnsProposalTopic::Unspecified, 0, "unspecified"),
358 (NnsProposalTopic::NeuronManagement, 1, "neuron-management"),
359 (NnsProposalTopic::ExchangeRate, 2, "exchange-rate"),
360 (NnsProposalTopic::NetworkEconomics, 3, "network-economics"),
361 (NnsProposalTopic::Governance, 4, "governance"),
362 (NnsProposalTopic::NodeAdmin, 5, "node-admin"),
363 (
364 NnsProposalTopic::ParticipantManagement,
365 6,
366 "participant-management",
367 ),
368 (NnsProposalTopic::SubnetManagement, 7, "subnet-management"),
369 (
370 NnsProposalTopic::NetworkCanisterManagement,
371 8,
372 "network-canister-management",
373 ),
374 (NnsProposalTopic::Kyc, 9, "kyc"),
375 (
376 NnsProposalTopic::NodeProviderRewards,
377 10,
378 "node-provider-rewards",
379 ),
380 (
381 NnsProposalTopic::IcOsVersionDeployment,
382 12,
383 "ic-os-version-deployment",
384 ),
385 (
386 NnsProposalTopic::IcOsVersionElection,
387 13,
388 "ic-os-version-election",
389 ),
390 (
391 NnsProposalTopic::SnsAndCommunityFund,
392 14,
393 "sns-and-community-fund",
394 ),
395 (
396 NnsProposalTopic::ApiBoundaryNodeManagement,
397 15,
398 "api-boundary-node-management",
399 ),
400 (NnsProposalTopic::SubnetRental, 16, "subnet-rental"),
401 (
402 NnsProposalTopic::ApplicationCanisterManagement,
403 17,
404 "application-canister-management",
405 ),
406 (
407 NnsProposalTopic::ProtocolCanisterManagement,
408 18,
409 "protocol-canister-management",
410 ),
411 ] {
412 assert_eq!(topic.code(), code);
413 assert_eq!(NnsProposalTopic::from_code(code), topic);
414 assert_json_label(topic, label);
415 }
416 assert_eq!(
417 NnsProposalTopic::from_code(11),
418 NnsProposalTopic::Unspecified
419 );
420 assert_eq!(
421 NnsProposalTopic::from_code(99),
422 NnsProposalTopic::Unspecified
423 );
424 }
425
426 fn assert_json_label<T>(value: T, label: &str)
427 where
428 T: Copy + std::fmt::Debug + Eq + Serialize + serde::de::DeserializeOwned,
429 {
430 assert_eq!(
431 serde_json::to_string(&value).unwrap(),
432 format!("\"{label}\"")
433 );
434 assert_eq!(
435 serde_json::from_str::<T>(&format!("\"{label}\"")).unwrap(),
436 value
437 );
438 }
439}