1use std::fmt;
8
9macro_rules! native_code_classification {
10 (
11 $classification:literal;
12 $(#[$enum_meta:meta])*
13 pub enum $name:ident {
14 $(
15 $(#[$variant_meta:meta])*
16 $variant:ident = $code:literal => $label:literal,
17 )+
18 ; unknown $unknown:ident(i32),
19 }
20 ) => {
21 $(#[$enum_meta])*
22 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
23 pub enum $name {
24 $(
25 $(#[$variant_meta])*
26 $variant,
27 )+
28 $unknown(i32),
30 }
31
32 impl $name {
33 #[must_use]
35 pub const fn from_code(code: i32) -> Self {
36 match code {
37 $($code => Self::$variant,)+
38 code => Self::$unknown(code),
39 }
40 }
41
42 #[must_use]
44 pub const fn code(self) -> i32 {
45 match self {
46 $(Self::$variant => $code,)+
47 Self::$unknown(code) => code,
48 }
49 }
50 }
51
52 impl fmt::Display for $name {
53 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 $(Self::$variant => formatter.write_str($label),)+
56 Self::$unknown(code) => write!(formatter, "unknown({code})"),
57 }
58 }
59 }
60
61 impl serde::Serialize for $name {
62 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63 where
64 S: serde::Serializer,
65 {
66 serializer.collect_str(self)
67 }
68 }
69
70 impl<'de> serde::Deserialize<'de> for $name {
71 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72 where
73 D: serde::Deserializer<'de>,
74 {
75 deserialize_code_label(
76 deserializer,
77 $classification,
78 |label| match label {
79 $($label => Some(Self::$variant),)+
80 _ => None,
81 },
82 Self::$unknown,
83 )
84 }
85 }
86 };
87}
88
89macro_rules! native_optional_code_classification {
90 (
91 $classification:literal;
92 $(#[$enum_meta:meta])*
93 pub enum $name:ident {
94 $(#[$absent_meta:meta])*
95 $absent:ident,
96 $(
97 $(#[$variant_meta:meta])*
98 $variant:ident = $code:literal => $label:literal,
99 )+
100 ; unknown $unknown:ident(i32),
101 }
102 ) => {
103 $(#[$enum_meta])*
104 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
105 pub enum $name {
106 $(#[$absent_meta])*
107 $absent,
108 $(
109 $(#[$variant_meta])*
110 $variant,
111 )+
112 $unknown(i32),
114 }
115
116 impl $name {
117 #[must_use]
119 pub const fn from_code(code: Option<i32>) -> Self {
120 match code {
121 None => Self::$absent,
122 $(Some($code) => Self::$variant,)+
123 Some(code) => Self::$unknown(code),
124 }
125 }
126
127 #[must_use]
129 pub const fn code(self) -> Option<i32> {
130 match self {
131 Self::$absent => None,
132 $(Self::$variant => Some($code),)+
133 Self::$unknown(code) => Some(code),
134 }
135 }
136 }
137
138 impl fmt::Display for $name {
139 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 Self::$absent => formatter.write_str("unknown"),
142 $(Self::$variant => formatter.write_str($label),)+
143 Self::$unknown(code) => write!(formatter, "unknown({code})"),
144 }
145 }
146 }
147
148 impl serde::Serialize for $name {
149 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150 where
151 S: serde::Serializer,
152 {
153 serializer.collect_str(self)
154 }
155 }
156
157 impl<'de> serde::Deserialize<'de> for $name {
158 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159 where
160 D: serde::Deserializer<'de>,
161 {
162 deserialize_code_label(
163 deserializer,
164 $classification,
165 |label| match label {
166 "unknown" => Some(Self::$absent),
167 $($label => Some(Self::$variant),)+
168 _ => None,
169 },
170 Self::$unknown,
171 )
172 }
173 }
174 };
175}
176
177native_code_classification! {
178 "state";
179 pub enum NnsNeuronState {
186 Unspecified = 0 => "unspecified",
188 NotDissolving = 1 => "not-dissolving",
190 Dissolving = 2 => "dissolving",
192 Dissolved = 3 => "dissolved",
194 Spawning = 4 => "spawning",
196 ; unknown Unknown(i32),
197 }
198}
199
200native_optional_code_classification! {
201 "visibility";
202 pub enum NnsNeuronVisibility {
209 Unknown,
211 Unspecified = 0 => "unspecified",
213 Private = 1 => "private",
215 Public = 2 => "public",
217 ; unknown UnknownCode(i32),
218 }
219}
220
221native_optional_code_classification! {
222 "type";
223 pub enum NnsNeuronType {
230 Unknown,
232 Unspecified = 0 => "unspecified",
234 Seed = 1 => "seed",
236 Ect = 2 => "ect",
238 ; unknown UnknownCode(i32),
239 }
240}
241
242native_code_classification! {
243 "vote";
244 pub enum NnsNeuronVote {
251 Unspecified = 0 => "unspecified",
253 Yes = 1 => "yes",
255 No = 2 => "no",
257 ; unknown Unknown(i32),
258 }
259}
260
261fn deserialize_code_label<'de, D, T>(
262 deserializer: D,
263 classification: &str,
264 known: fn(&str) -> Option<T>,
265 unknown: fn(i32) -> T,
266) -> Result<T, D::Error>
267where
268 D: serde::Deserializer<'de>,
269{
270 use serde::de::Error as _;
271
272 let label = <String as serde::Deserialize>::deserialize(deserializer)?;
273 if let Some(value) = known(&label) {
274 return Ok(value);
275 }
276 let code = parse_unknown_code(&label).ok_or_else(|| {
277 D::Error::custom(format!(
278 "invalid NNS neuron {classification} label {label:?}"
279 ))
280 })?;
281 Ok(unknown(code))
282}
283
284fn parse_unknown_code(label: &str) -> Option<i32> {
285 let code = label
286 .strip_prefix("unknown(")?
287 .strip_suffix(')')?
288 .parse::<i32>()
289 .ok()?;
290 (label == format!("unknown({code})")).then_some(code)
291}
292
293#[cfg(test)]
294mod tests {
295 use super::{NnsNeuronState, NnsNeuronType, NnsNeuronVisibility, NnsNeuronVote};
296
297 #[test]
298 fn required_classifications_preserve_codes_and_labels() {
299 for (code, state, label) in [
300 (0, NnsNeuronState::Unspecified, "unspecified"),
301 (1, NnsNeuronState::NotDissolving, "not-dissolving"),
302 (2, NnsNeuronState::Dissolving, "dissolving"),
303 (3, NnsNeuronState::Dissolved, "dissolved"),
304 (4, NnsNeuronState::Spawning, "spawning"),
305 (99, NnsNeuronState::Unknown(99), "unknown(99)"),
306 ] {
307 assert_eq!(NnsNeuronState::from_code(code), state);
308 assert_eq!(state.code(), code);
309 assert_eq!(state.to_string(), label);
310 assert_eq!(serde_json::to_value(state).expect("serialize state"), label);
311 }
312 for (code, vote, label) in [
313 (0, NnsNeuronVote::Unspecified, "unspecified"),
314 (1, NnsNeuronVote::Yes, "yes"),
315 (2, NnsNeuronVote::No, "no"),
316 (99, NnsNeuronVote::Unknown(99), "unknown(99)"),
317 ] {
318 assert_eq!(NnsNeuronVote::from_code(code), vote);
319 assert_eq!(vote.code(), code);
320 assert_eq!(vote.to_string(), label);
321 assert_eq!(serde_json::to_value(vote).expect("serialize vote"), label);
322 }
323 }
324
325 #[test]
326 fn optional_classifications_distinguish_absent_and_unknown_codes() {
327 for (code, visibility, label) in [
328 (None, NnsNeuronVisibility::Unknown, "unknown"),
329 (Some(0), NnsNeuronVisibility::Unspecified, "unspecified"),
330 (Some(1), NnsNeuronVisibility::Private, "private"),
331 (Some(2), NnsNeuronVisibility::Public, "public"),
332 (
333 Some(99),
334 NnsNeuronVisibility::UnknownCode(99),
335 "unknown(99)",
336 ),
337 ] {
338 assert_eq!(NnsNeuronVisibility::from_code(code), visibility);
339 assert_eq!(visibility.code(), code);
340 assert_eq!(visibility.to_string(), label);
341 assert_eq!(
342 serde_json::to_value(visibility).expect("serialize visibility"),
343 label
344 );
345 }
346 for (code, neuron_type, label) in [
347 (None, NnsNeuronType::Unknown, "unknown"),
348 (Some(0), NnsNeuronType::Unspecified, "unspecified"),
349 (Some(1), NnsNeuronType::Seed, "seed"),
350 (Some(2), NnsNeuronType::Ect, "ect"),
351 (Some(99), NnsNeuronType::UnknownCode(99), "unknown(99)"),
352 ] {
353 assert_eq!(NnsNeuronType::from_code(code), neuron_type);
354 assert_eq!(neuron_type.code(), code);
355 assert_eq!(neuron_type.to_string(), label);
356 assert_eq!(
357 serde_json::to_value(neuron_type).expect("serialize neuron type"),
358 label
359 );
360 }
361 }
362
363 #[test]
364 fn classifications_read_canonical_cache_labels_only() {
365 assert_eq!(
366 serde_json::from_str::<NnsNeuronState>("\"unknown(-9)\"")
367 .expect("deserialize unknown state"),
368 NnsNeuronState::Unknown(-9)
369 );
370 assert_eq!(
371 serde_json::from_str::<NnsNeuronVisibility>("\"unknown\"")
372 .expect("deserialize absent visibility"),
373 NnsNeuronVisibility::Unknown
374 );
375 assert_eq!(
376 serde_json::from_str::<NnsNeuronType>("\"unknown(9)\"")
377 .expect("deserialize unknown neuron type"),
378 NnsNeuronType::UnknownCode(9)
379 );
380 assert_eq!(
381 serde_json::from_str::<NnsNeuronVote>("\"yes\"").expect("deserialize known vote"),
382 NnsNeuronVote::Yes
383 );
384 assert!(serde_json::from_str::<NnsNeuronState>("\"unknown(+9)\"").is_err());
385 assert!(serde_json::from_str::<NnsNeuronVote>("\"maybe\"").is_err());
386 }
387}