1use std::mem::discriminant;
2
3use serde::{Deserializer, Serializer};
4
5use crate::case::snake_case;
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
40pub const COLUMNS: &[ColumnSpec] = String::COLUMNS;
41
42fn same<E: Variants>(left: E, right: E) -> bool {
43 discriminant(&left) == discriminant(&right)
44}
45
46#[must_use]
47pub fn parse<E: Variants>(name: &str) -> Option<E> {
48 E::VARIANTS
49 .iter()
50 .find(|(candidate, _)| *candidate == name)
51 .map(|(_, variant)| *variant)
52}
53
54pub fn encode<E: Variants>(value: E, out: &mut Vec<SqlValue>) {
55 out.push(SqlValue::Text(value.name().to_owned()));
56}
57
58pub fn decode<E: Variants>(row: &mut dyn ColumnReader) -> Result<E> {
59 let stored = row.text()?;
60 parse(stored)
61 .ok_or_else(|| Error::internal_message(format!("`{stored}` is not a stored {}", E::TYPE)))
62}
63
64pub fn serialize<E: Variants, S: Serializer>(value: E, serializer: S) -> Result<S::Ok, S::Error> {
65 serializer.serialize_str(value.name())
66}
67
68pub fn deserialize<'de, E: Variants, D: Deserializer<'de>>(deserializer: D) -> Result<E, D::Error> {
69 crate::deserialize_parsed(deserializer, "a variant name", parse_given)
70}
71
72fn parse_given<E: Variants>(given: &str) -> Result<E, String> {
73 parse(given).ok_or_else(|| {
74 let accepted: Vec<&str> = E::VARIANTS.iter().map(|(name, _)| *name).collect();
75 format!(
76 "`{given}` is not a {}, expected one of {}",
77 E::TYPE,
78 accepted.join(", ")
79 )
80 })
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[derive(Clone, Copy, Debug, PartialEq)]
88 enum Light {
89 Green,
90 Red,
91 }
92
93 impl Variants for Light {
94 const TYPE: &'static str = "Light";
95 const VARIANTS: &'static [(&'static str, Self)] =
96 &[("GREEN", Self::Green), ("RED", Self::Red)];
97 const TRANSITIONS: &'static [(Self, Self)] = &[(Self::Green, Self::Red)];
98 }
99
100 #[test]
101 fn names_and_parses_variants() {
102 assert_eq!(Light::Red.name(), "RED");
103 assert_eq!(parse::<Light>("GREEN"), Some(Light::Green));
104 assert_eq!(parse::<Light>("BLUE"), None);
105 }
106
107 #[test]
108 fn decodes_a_stored_name_and_refuses_an_unknown_one() {
109 let values = [
110 SqlValue::Text("RED".to_owned()),
111 SqlValue::Text("BLUE".to_owned()),
112 ];
113 let mut row = crate::RowReader::new(&values);
114
115 assert_eq!(decode::<Light>(&mut row).unwrap(), Light::Red);
116 assert!(matches!(
117 decode::<Light>(&mut row),
118 Err(Error::Internal { .. })
119 ));
120 }
121
122 #[test]
123 fn deserializes_a_borrowed_or_escaped_name_and_names_the_accepted_ones() {
124 let read = |json: &str| {
125 deserialize::<Light, _>(&mut serde_json::Deserializer::from_str(json))
126 .map_err(|error| error.to_string())
127 };
128
129 assert_eq!(read("\"RED\""), Ok(Light::Red));
130 assert_eq!(read("\"R\\u0045D\""), Ok(Light::Red));
131 let refusal = read("\"BLUE\"").unwrap_err();
132 assert!(
133 refusal.starts_with("`BLUE` is not a Light, expected one of GREEN, RED"),
134 "{refusal}"
135 );
136 }
137
138 #[test]
139 fn allows_only_listed_transitions() {
140 assert_eq!(Light::Green.to(Light::Red).unwrap(), Light::Red);
141 let Err(Error::Invalid(refused)) = Light::Red.to(Light::Green) else {
142 panic!("an unlisted move is a field error");
143 };
144 assert_eq!(
145 refused,
146 [
147 crate::FieldError::new("light", "validation.move", "cannot move from RED to GREEN")
148 .with("from", "RED")
149 .with("to", "GREEN")
150 ]
151 );
152 }
153}