1use std::borrow::Cow;
2use std::mem::discriminant;
3
4use serde::{Deserialize, Deserializer, Serializer};
5
6use crate::{Cast, ColumnReader, ColumnSpec, Error, Result, SqlValue};
7
8pub trait Variants: Copy + 'static {
9 const TYPE: &'static str;
10 const PATH: &'static str = Self::TYPE;
12 const VARIANTS: &'static [(&'static str, Self)];
13 const TRANSITIONS: &'static [(Self, Self)] = &[];
14
15 #[must_use]
16 fn name(self) -> &'static str {
17 Self::VARIANTS
18 .iter()
19 .find(|(_, variant)| same(*variant, self))
20 .map(|(name, _)| *name)
21 .expect("`#[derive(Enum)]` lists every variant")
22 }
23
24 fn to(self, next: Self) -> Result<Self> {
25 let allowed = Self::TRANSITIONS
26 .iter()
27 .any(|(source, target)| same(*source, self) && same(*target, next));
28 if allowed {
29 crate::moves::record(Self::PATH, self.name(), next.name());
30 return Ok(next);
31 }
32 Err(crate::moves::refused(
33 snake_case(Self::TYPE),
34 self.name(),
35 next.name(),
36 ))
37 }
38}
39
40fn snake_case(name: &str) -> String {
41 let mut snake = String::with_capacity(name.len() + 4);
42 for (index, character) in name.chars().enumerate() {
43 if character.is_uppercase() && index > 0 {
44 snake.push('_');
45 }
46 snake.extend(character.to_lowercase());
47 }
48 snake
49}
50
51pub const COLUMNS: &[ColumnSpec] = String::COLUMNS;
52
53fn same<E: Variants>(left: E, right: E) -> bool {
54 discriminant(&left) == discriminant(&right)
55}
56
57#[must_use]
58pub fn parse<E: Variants>(name: &str) -> Option<E> {
59 E::VARIANTS
60 .iter()
61 .find(|(candidate, _)| *candidate == name)
62 .map(|(_, variant)| *variant)
63}
64
65pub fn encode<E: Variants>(value: E, out: &mut Vec<SqlValue>) {
66 out.push(SqlValue::Text(value.name().to_owned()));
67}
68
69pub fn decode<E: Variants>(row: &mut dyn ColumnReader) -> Result<E> {
70 let stored = row.text()?;
71 parse(stored)
72 .ok_or_else(|| Error::internal_message(format!("`{stored}` is not a stored {}", E::TYPE)))
73}
74
75pub fn serialize<E: Variants, S: Serializer>(value: E, serializer: S) -> Result<S::Ok, S::Error> {
76 serializer.serialize_str(value.name())
77}
78
79pub fn deserialize<'de, E: Variants, D: Deserializer<'de>>(deserializer: D) -> Result<E, D::Error> {
80 let given = Cow::<str>::deserialize(deserializer)?;
81 parse(&given).ok_or_else(|| {
82 let accepted: Vec<&str> = E::VARIANTS.iter().map(|(name, _)| *name).collect();
83 serde::de::Error::custom(format!(
84 "`{given}` is not a {}, expected one of {}",
85 E::TYPE,
86 accepted.join(", ")
87 ))
88 })
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[derive(Clone, Copy, Debug, PartialEq)]
96 enum Light {
97 Green,
98 Red,
99 }
100
101 impl Variants for Light {
102 const TYPE: &'static str = "Light";
103 const VARIANTS: &'static [(&'static str, Self)] =
104 &[("GREEN", Self::Green), ("RED", Self::Red)];
105 const TRANSITIONS: &'static [(Self, Self)] = &[(Self::Green, Self::Red)];
106 }
107
108 #[test]
109 fn names_and_parses_variants() {
110 assert_eq!(Light::Red.name(), "RED");
111 assert_eq!(parse::<Light>("GREEN"), Some(Light::Green));
112 assert_eq!(parse::<Light>("BLUE"), None);
113 }
114
115 #[test]
116 fn decodes_a_stored_name_and_refuses_an_unknown_one() {
117 let values = [
118 SqlValue::Text("RED".to_owned()),
119 SqlValue::Text("BLUE".to_owned()),
120 ];
121 let mut row = crate::RowReader::new(&values);
122
123 assert_eq!(decode::<Light>(&mut row).unwrap(), Light::Red);
124 assert!(matches!(
125 decode::<Light>(&mut row),
126 Err(Error::Internal { .. })
127 ));
128 }
129
130 #[test]
131 fn allows_only_listed_transitions() {
132 assert_eq!(Light::Green.to(Light::Red).unwrap(), Light::Red);
133 let Err(Error::Invalid(refused)) = Light::Red.to(Light::Green) else {
134 panic!("an unlisted move is a field error");
135 };
136 assert_eq!(
137 refused,
138 [
139 crate::FieldError::new("light", "validation.move", "cannot move from RED to GREEN")
140 .with("from", "RED")
141 .with("to", "GREEN")
142 ]
143 );
144 }
145}