Skip to main content

max / makenotwork

3.9 KB · 98 lines History Blame Raw
1 //! The one macro every enum here is built with.
2 //!
3 //! `macro_rules!` is textually scoped, so a sibling cannot see it by being a
4 //! sibling: it reaches them through the `pub(super) use` at the bottom of this
5 //! file, and each sibling imports it by name.
6
7 /// Generate `Display`, `FromStr`, and sqlx `Type`/`Encode`/`Decode` impls
8 /// for a simple enum ↔ string mapping. The sqlx impls delegate to `String`
9 /// so the enum is compatible with any text-like column (TEXT, VARCHAR, etc.).
10 macro_rules! impl_str_enum {
11 ($enum_name:ident { $($variant:ident => $str:literal),+ $(,)? }) => {
12 impl std::fmt::Display for $enum_name {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 let s = match self {
15 $( Self::$variant => $str, )+
16 };
17 f.write_str(s)
18 }
19 }
20
21 impl std::str::FromStr for $enum_name {
22 type Err = String;
23
24 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
25 match s {
26 $( $str => Ok(Self::$variant), )+
27 other => Err(format!("invalid {}: {other}", stringify!($enum_name))),
28 }
29 }
30 }
31
32 // sqlx Type: delegate to String so it's compatible with TEXT/VARCHAR.
33 impl sqlx::Type<sqlx::Postgres> for $enum_name {
34 fn type_info() -> sqlx::postgres::PgTypeInfo {
35 <String as sqlx::Type<sqlx::Postgres>>::type_info()
36 }
37
38 fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
39 <String as sqlx::Type<sqlx::Postgres>>::compatible(ty)
40 }
41 }
42
43 // sqlx Encode: write the Display string.
44 impl sqlx::Encode<'_, sqlx::Postgres> for $enum_name {
45 fn encode_by_ref(
46 &self,
47 buf: &mut sqlx::postgres::PgArgumentBuffer,
48 ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
49 <String as sqlx::Encode<'_, sqlx::Postgres>>::encode(self.to_string(), buf)
50 }
51 }
52
53 // sqlx Decode: parse the string value via FromStr.
54 impl sqlx::Decode<'_, sqlx::Postgres> for $enum_name {
55 fn decode(
56 value: sqlx::postgres::PgValueRef<'_>,
57 ) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
58 let s = <String as sqlx::Decode<'_, sqlx::Postgres>>::decode(value)?;
59 Ok(s.parse::<Self>()?)
60 }
61 }
62
63 // Allow comparison with string slices (useful in Askama templates).
64 impl PartialEq<&str> for $enum_name {
65 fn eq(&self, other: &&str) -> bool {
66 let s: &str = match self {
67 $( Self::$variant => $str, )+
68 };
69 s == *other
70 }
71 }
72
73 impl PartialEq<str> for $enum_name {
74 fn eq(&self, other: &str) -> bool {
75 let s: &str = match self {
76 $( Self::$variant => $str, )+
77 };
78 s == other
79 }
80 }
81
82 impl $enum_name {
83 /// Every wire/DB string this enum maps to, the single source of
84 /// truth for the variant set. For each enum *registered* in the
85 /// enum-drift integration test (`tests/workflows/enum_drift.rs`),
86 /// this set is asserted equal to the Postgres `CHECK (... IN (...))`
87 /// list on its backing column, so a variant added here without
88 /// widening the DB constraint (or vice versa) fails at test time
89 /// rather than at the first read of a poisoned row. Coverage is that
90 /// registry, not every enum automatically: add a `(enum, table,
91 /// column)` row there when a new CHECK-constrained column lands.
92 pub const VARIANTS: &'static [&'static str] = &[$($str),+];
93 }
94 };
95 }
96
97 pub(super) use impl_str_enum;
98