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 {
185 Unspecified = 0 => "unspecified",
187 NotDissolving = 1 => "not-dissolving",
189 Dissolving = 2 => "dissolving",
191 Dissolved = 3 => "dissolved",
193 Spawning = 4 => "spawning",
195 ; unknown Unknown(i32),
196 }
197}
198
199native_optional_code_classification! {
200 "visibility";
201 pub enum NnsNeuronVisibility {
207 Unknown,
209 Unspecified = 0 => "unspecified",
211 Private = 1 => "private",
213 Public = 2 => "public",
215 ; unknown UnknownCode(i32),
216 }
217}
218
219native_optional_code_classification! {
220 "type";
221 pub enum NnsNeuronType {
227 Unknown,
229 Unspecified = 0 => "unspecified",
231 Seed = 1 => "seed",
233 Ect = 2 => "ect",
235 ; unknown UnknownCode(i32),
236 }
237}
238
239native_code_classification! {
240 "vote";
241 pub enum NnsNeuronVote {
247 Unspecified = 0 => "unspecified",
249 Yes = 1 => "yes",
251 No = 2 => "no",
253 ; unknown Unknown(i32),
254 }
255}
256
257fn deserialize_code_label<'de, D, T>(
258 deserializer: D,
259 classification: &str,
260 known: fn(&str) -> Option<T>,
261 unknown: fn(i32) -> T,
262) -> Result<T, D::Error>
263where
264 D: serde::Deserializer<'de>,
265{
266 use serde::de::Error as _;
267
268 let label = <String as serde::Deserialize>::deserialize(deserializer)?;
269 if let Some(value) = known(&label) {
270 return Ok(value);
271 }
272 let code = parse_unknown_code(&label).ok_or_else(|| {
273 D::Error::custom(format!(
274 "invalid NNS neuron {classification} label {label:?}"
275 ))
276 })?;
277 Ok(unknown(code))
278}
279
280fn parse_unknown_code(label: &str) -> Option<i32> {
281 let code = label
282 .strip_prefix("unknown(")?
283 .strip_suffix(')')?
284 .parse::<i32>()
285 .ok()?;
286 (label == format!("unknown({code})")).then_some(code)
287}
288
289#[cfg(test)]
290mod tests {
291 use super::{NnsNeuronState, NnsNeuronType, NnsNeuronVisibility, NnsNeuronVote};
292
293 #[test]
294 fn required_classifications_preserve_codes_and_labels() {
295 for (code, state, label) in [
296 (0, NnsNeuronState::Unspecified, "unspecified"),
297 (1, NnsNeuronState::NotDissolving, "not-dissolving"),
298 (2, NnsNeuronState::Dissolving, "dissolving"),
299 (3, NnsNeuronState::Dissolved, "dissolved"),
300 (4, NnsNeuronState::Spawning, "spawning"),
301 (99, NnsNeuronState::Unknown(99), "unknown(99)"),
302 ] {
303 assert_eq!(NnsNeuronState::from_code(code), state);
304 assert_eq!(state.code(), code);
305 assert_eq!(state.to_string(), label);
306 assert_eq!(serde_json::to_value(state).expect("serialize state"), label);
307 }
308 for (code, vote, label) in [
309 (0, NnsNeuronVote::Unspecified, "unspecified"),
310 (1, NnsNeuronVote::Yes, "yes"),
311 (2, NnsNeuronVote::No, "no"),
312 (99, NnsNeuronVote::Unknown(99), "unknown(99)"),
313 ] {
314 assert_eq!(NnsNeuronVote::from_code(code), vote);
315 assert_eq!(vote.code(), code);
316 assert_eq!(vote.to_string(), label);
317 assert_eq!(serde_json::to_value(vote).expect("serialize vote"), label);
318 }
319 }
320
321 #[test]
322 fn optional_classifications_distinguish_absent_and_unknown_codes() {
323 for (code, visibility, label) in [
324 (None, NnsNeuronVisibility::Unknown, "unknown"),
325 (Some(0), NnsNeuronVisibility::Unspecified, "unspecified"),
326 (Some(1), NnsNeuronVisibility::Private, "private"),
327 (Some(2), NnsNeuronVisibility::Public, "public"),
328 (
329 Some(99),
330 NnsNeuronVisibility::UnknownCode(99),
331 "unknown(99)",
332 ),
333 ] {
334 assert_eq!(NnsNeuronVisibility::from_code(code), visibility);
335 assert_eq!(visibility.code(), code);
336 assert_eq!(visibility.to_string(), label);
337 assert_eq!(
338 serde_json::to_value(visibility).expect("serialize visibility"),
339 label
340 );
341 }
342 for (code, neuron_type, label) in [
343 (None, NnsNeuronType::Unknown, "unknown"),
344 (Some(0), NnsNeuronType::Unspecified, "unspecified"),
345 (Some(1), NnsNeuronType::Seed, "seed"),
346 (Some(2), NnsNeuronType::Ect, "ect"),
347 (Some(99), NnsNeuronType::UnknownCode(99), "unknown(99)"),
348 ] {
349 assert_eq!(NnsNeuronType::from_code(code), neuron_type);
350 assert_eq!(neuron_type.code(), code);
351 assert_eq!(neuron_type.to_string(), label);
352 assert_eq!(
353 serde_json::to_value(neuron_type).expect("serialize neuron type"),
354 label
355 );
356 }
357 }
358
359 #[test]
360 fn classifications_read_canonical_cache_labels_only() {
361 assert_eq!(
362 serde_json::from_str::<NnsNeuronState>("\"unknown(-9)\"")
363 .expect("deserialize unknown state"),
364 NnsNeuronState::Unknown(-9)
365 );
366 assert_eq!(
367 serde_json::from_str::<NnsNeuronVisibility>("\"unknown\"")
368 .expect("deserialize absent visibility"),
369 NnsNeuronVisibility::Unknown
370 );
371 assert_eq!(
372 serde_json::from_str::<NnsNeuronType>("\"unknown(9)\"")
373 .expect("deserialize unknown neuron type"),
374 NnsNeuronType::UnknownCode(9)
375 );
376 assert_eq!(
377 serde_json::from_str::<NnsNeuronVote>("\"yes\"").expect("deserialize known vote"),
378 NnsNeuronVote::Yes
379 );
380 assert!(serde_json::from_str::<NnsNeuronState>("\"unknown(+9)\"").is_err());
381 assert!(serde_json::from_str::<NnsNeuronVote>("\"maybe\"").is_err());
382 }
383}