Skip to main content

toasty_driver_integration_suite/tests/
embed_enum_rename_all.rs

1use crate::prelude::*;
2
3use toasty::Db;
4use toasty_core::schema::db;
5
6/// The (driver-independent) storage type of a database column.
7fn column_storage_ty(db: &Db, table: &str, column: &str) -> db::Type {
8    let schema = db.schema();
9    let table = schema
10        .db
11        .tables
12        .iter()
13        .find(|t| t.name == table || t.name.ends_with(table))
14        .unwrap_or_else(|| panic!("table '{table}' not in schema"));
15    table
16        .columns
17        .iter()
18        .find(|c| c.name == column)
19        .unwrap_or_else(|| panic!("column '{column}' not in table"))
20        .storage_ty
21        .clone()
22}
23
24/// The (type name, variant labels) of a native enum column, in declaration
25/// order. Panics if the column is not a native enum type.
26fn native_enum(db: &Db, table: &str, column: &str) -> (String, Vec<String>) {
27    match column_storage_ty(db, table, column) {
28        db::Type::Enum(e) => (
29            e.name.expect("a native enum type has a name"),
30            e.variants.into_iter().map(|v| v.name).collect(),
31        ),
32        other => panic!("expected a native enum column, got {other:?}"),
33    }
34}
35
36/// `#[column(rename_all = ...)]` derives each variant's default label; an
37/// explicit per-variant `#[column(variant = ...)]` still wins.
38#[driver_test]
39pub async fn rename_all_derives_labels(t: &mut Test) {
40    #[derive(Debug, toasty::Embed)]
41    #[column(rename_all = "PascalCase")]
42    enum Pascal {
43        Customer,
44        PreferredSupplier,
45    }
46
47    #[derive(Debug, toasty::Embed)]
48    #[column(rename_all = "SCREAMING_SNAKE_CASE")]
49    enum Screaming {
50        Customer,
51        PreferredSupplier,
52    }
53
54    #[derive(Debug, toasty::Embed)]
55    #[column(rename_all = "PascalCase")]
56    enum Overridden {
57        #[column(variant = "vip")]
58        PreferredSupplier,
59        Customer,
60    }
61
62    #[derive(Debug, toasty::Model)]
63    #[allow(dead_code)]
64    struct Contact {
65        #[key]
66        #[auto]
67        id: uuid::Uuid,
68        pascal: Pascal,
69        screaming: Screaming,
70        overridden: Overridden,
71    }
72
73    let db = t.setup_db(models!(Contact)).await;
74
75    assert_eq!(
76        native_enum(&db, "contacts", "pascal").1,
77        ["Customer", "PreferredSupplier"]
78    );
79    assert_eq!(
80        native_enum(&db, "contacts", "screaming").1,
81        ["CUSTOMER", "PREFERRED_SUPPLIER"]
82    );
83    assert_eq!(
84        native_enum(&db, "contacts", "overridden").1,
85        ["vip", "Customer"]
86    );
87}
88
89/// The renamed labels flow into every string storage mapping: native enum
90/// (default, explicit `type = enum`, and named `type = enum("...")`) and the
91/// plain `type = text` column. `rename_all` never affects the enum type name.
92#[driver_test]
93pub async fn rename_all_across_storage_mappings(t: &mut Test) {
94    #[derive(Debug, toasty::Embed)]
95    #[column(rename_all = "PascalCase")]
96    enum NativeDefault {
97        Customer,
98        Supplier,
99    }
100
101    #[derive(Debug, toasty::Embed)]
102    #[column(rename_all = "PascalCase", type = enum)]
103    enum NativeExplicit {
104        Customer,
105        Supplier,
106    }
107
108    #[derive(Debug, toasty::Embed)]
109    #[column(rename_all = "PascalCase", type = enum("party_kind"))]
110    enum NativeNamed {
111        Customer,
112        Supplier,
113    }
114
115    #[derive(Debug, toasty::Embed)]
116    #[column(rename_all = "PascalCase", type = text)]
117    enum TextMapped {
118        Customer,
119        Supplier,
120    }
121
122    #[derive(Debug, toasty::Model)]
123    #[allow(dead_code)]
124    struct Contact {
125        #[key]
126        #[auto]
127        id: uuid::Uuid,
128        native_default: NativeDefault,
129        native_explicit: NativeExplicit,
130        native_named: NativeNamed,
131        text_mapped: TextMapped,
132    }
133
134    let db = t.setup_db(models!(Contact)).await;
135    let renamed = ["Customer".to_string(), "Supplier".to_string()];
136
137    // Default native enum: type name derived from the ident in snake_case.
138    let (name, labels) = native_enum(&db, "contacts", "native_default");
139    assert!(name.ends_with("native_default"), "unexpected name: {name}");
140    assert_eq!(labels, renamed);
141
142    // Explicit `type = enum`: identical native representation.
143    assert_eq!(native_enum(&db, "contacts", "native_explicit").1, renamed);
144
145    // Named `type = enum("party_kind")`: custom type name, renamed labels.
146    let (name, labels) = native_enum(&db, "contacts", "native_named");
147    assert!(name.ends_with("party_kind"), "unexpected name: {name}");
148    assert_eq!(labels, renamed);
149
150    // Plain text mapping: a TEXT column, not a native enum type.
151    assert_eq!(
152        column_storage_ty(&db, "contacts", "text_mapped"),
153        db::Type::Text
154    );
155}
156
157/// End-to-end: a renamed enum round-trips through the database (create + read).
158#[driver_test]
159pub async fn rename_all_round_trip(t: &mut Test) -> Result<()> {
160    #[derive(Debug, PartialEq, toasty::Embed)]
161    #[column(rename_all = "PascalCase")]
162    enum PartyKind {
163        Customer,
164        Supplier,
165    }
166
167    #[derive(Debug, toasty::Model)]
168    struct Contact {
169        #[key]
170        #[auto]
171        id: uuid::Uuid,
172        kind: PartyKind,
173    }
174
175    let mut db = t.setup_db(models!(Contact)).await;
176
177    let contact = toasty::create!(Contact {
178        kind: PartyKind::Supplier,
179    })
180    .exec(&mut db)
181    .await?;
182    let found = Contact::get_by_id(&mut db, &contact.id).await?;
183    assert_eq!(found.kind, PartyKind::Supplier);
184
185    Ok(())
186}