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 #[cfg(feature = "host")]
71 impl<'de> serde::Deserialize<'de> for $name {
72 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73 where
74 D: serde::Deserializer<'de>,
75 {
76 deserialize_code_label(
77 deserializer,
78 $classification,
79 |label| match label {
80 $($label => Some(Self::$variant),)+
81 _ => None,
82 },
83 Self::$unknown,
84 )
85 }
86 }
87 };
88}
89
90macro_rules! native_optional_code_classification {
91 (
92 $classification:literal;
93 $(#[$enum_meta:meta])*
94 pub enum $name:ident {
95 $(#[$absent_meta:meta])*
96 $absent:ident,
97 $(
98 $(#[$variant_meta:meta])*
99 $variant:ident = $code:literal => $label:literal,
100 )+
101 ; unknown $unknown:ident(i32),
102 }
103 ) => {
104 $(#[$enum_meta])*
105 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
106 pub enum $name {
107 $(#[$absent_meta])*
108 $absent,
109 $(
110 $(#[$variant_meta])*
111 $variant,
112 )+
113 $unknown(i32),
115 }
116
117 impl $name {
118 #[must_use]
120 pub const fn from_code(code: Option<i32>) -> Self {
121 match code {
122 None => Self::$absent,
123 $(Some($code) => Self::$variant,)+
124 Some(code) => Self::$unknown(code),
125 }
126 }
127
128 #[must_use]
130 pub const fn code(self) -> Option<i32> {
131 match self {
132 Self::$absent => None,
133 $(Self::$variant => Some($code),)+
134 Self::$unknown(code) => Some(code),
135 }
136 }
137 }
138
139 impl fmt::Display for $name {
140 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
141 match self {
142 Self::$absent => formatter.write_str("unknown"),
143 $(Self::$variant => formatter.write_str($label),)+
144 Self::$unknown(code) => write!(formatter, "unknown({code})"),
145 }
146 }
147 }
148
149 impl serde::Serialize for $name {
150 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151 where
152 S: serde::Serializer,
153 {
154 serializer.collect_str(self)
155 }
156 }
157
158 #[cfg(feature = "host")]
159 impl<'de> serde::Deserialize<'de> for $name {
160 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161 where
162 D: serde::Deserializer<'de>,
163 {
164 deserialize_code_label(
165 deserializer,
166 $classification,
167 |label| match label {
168 "unknown" => Some(Self::$absent),
169 $($label => Some(Self::$variant),)+
170 _ => None,
171 },
172 Self::$unknown,
173 )
174 }
175 }
176 };
177}
178
179native_code_classification! {
180 "state";
181 pub enum NnsNeuronState {
187 Unspecified = 0 => "unspecified",
189 NotDissolving = 1 => "not-dissolving",
191 Dissolving = 2 => "dissolving",
193 Dissolved = 3 => "dissolved",
195 Spawning = 4 => "spawning",
197 ; unknown Unknown(i32),
198 }
199}
200
201native_optional_code_classification! {
202 "visibility";
203 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 {
229 Unknown,
231 Unspecified = 0 => "unspecified",
233 Seed = 1 => "seed",
235 Ect = 2 => "ect",
237 ; unknown UnknownCode(i32),
238 }
239}
240
241native_code_classification! {
242 "vote";
243 pub enum NnsNeuronVote {
249 Unspecified = 0 => "unspecified",
251 Yes = 1 => "yes",
253 No = 2 => "no",
255 ; unknown Unknown(i32),
256 }
257}
258
259#[cfg(feature = "host")]
260fn deserialize_code_label<'de, D, T>(
261 deserializer: D,
262 classification: &str,
263 known: fn(&str) -> Option<T>,
264 unknown: fn(i32) -> T,
265) -> Result<T, D::Error>
266where
267 D: serde::Deserializer<'de>,
268{
269 use serde::de::Error as _;
270
271 let label = <String as serde::Deserialize>::deserialize(deserializer)?;
272 if let Some(value) = known(&label) {
273 return Ok(value);
274 }
275 let code = parse_unknown_code(&label).ok_or_else(|| {
276 D::Error::custom(format!(
277 "invalid NNS neuron {classification} label {label:?}"
278 ))
279 })?;
280 Ok(unknown(code))
281}
282
283#[cfg(feature = "host")]
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 #[cfg(feature = "host")]
364 #[test]
365 fn classifications_read_canonical_cache_labels_only() {
366 assert_eq!(
367 serde_json::from_str::<NnsNeuronState>("\"unknown(-9)\"")
368 .expect("deserialize unknown state"),
369 NnsNeuronState::Unknown(-9)
370 );
371 assert_eq!(
372 serde_json::from_str::<NnsNeuronVisibility>("\"unknown\"")
373 .expect("deserialize absent visibility"),
374 NnsNeuronVisibility::Unknown
375 );
376 assert_eq!(
377 serde_json::from_str::<NnsNeuronType>("\"unknown(9)\"")
378 .expect("deserialize unknown neuron type"),
379 NnsNeuronType::UnknownCode(9)
380 );
381 assert_eq!(
382 serde_json::from_str::<NnsNeuronVote>("\"yes\"").expect("deserialize known vote"),
383 NnsNeuronVote::Yes
384 );
385 assert!(serde_json::from_str::<NnsNeuronState>("\"unknown(+9)\"").is_err());
386 assert!(serde_json::from_str::<NnsNeuronVote>("\"maybe\"").is_err());
387 }
388}