toasty_driver_integration_suite/tests/
embed_enum_string_discriminant.rs

1use crate::prelude::*;
2
3/// Tests basic CRUD with a unit enum using explicit string discriminants.
4#[driver_test(id(ID))]
5pub async fn string_discriminant_unit_enum(t: &mut Test) -> Result<()> {
6    #[derive(Debug, PartialEq, toasty::Embed)]
7    enum Status {
8        #[column(variant = "pending")]
9        Pending,
10        #[column(variant = "active")]
11        Active,
12        #[column(variant = "done")]
13        Done,
14    }
15
16    #[derive(Debug, toasty::Model)]
17    struct Task {
18        #[key]
19        #[auto]
20        id: ID,
21        title: String,
22        status: Status,
23    }
24
25    let mut db = t.setup_db(models!(Task, Status)).await;
26
27    let task = toasty::create!(Task {
28        title: "Ship it",
29        status: Status::Pending,
30    })
31    .exec(&mut db)
32    .await?;
33    assert_eq!(task.status, Status::Pending);
34
35    let found = Task::get_by_id(&mut db, &task.id).await?;
36    assert_eq!(found.status, Status::Pending);
37
38    // Update and re-read
39    let mut task = found;
40    task.update().status(Status::Active).exec(&mut db).await?;
41    let found = Task::get_by_id(&mut db, &task.id).await?;
42    assert_eq!(found.status, Status::Active);
43
44    Ok(())
45}
46
47/// Tests unit enum with default labels (variant ident used as string label).
48#[driver_test(id(ID))]
49pub async fn default_string_labels(t: &mut Test) -> Result<()> {
50    #[derive(Debug, PartialEq, toasty::Embed)]
51    enum Priority {
52        Low,
53        Medium,
54        High,
55    }
56
57    #[derive(Debug, toasty::Model)]
58    struct Task {
59        #[key]
60        #[auto]
61        id: ID,
62        title: String,
63        priority: Priority,
64    }
65
66    let mut db = t.setup_db(models!(Task, Priority)).await;
67
68    let task = toasty::create!(Task {
69        title: "Fix bug",
70        priority: Priority::High,
71    })
72    .exec(&mut db)
73    .await?;
74    assert_eq!(task.priority, Priority::High);
75
76    let found = Task::get_by_id(&mut db, &task.id).await?;
77    assert_eq!(found.priority, Priority::High);
78
79    Ok(())
80}
81
82/// Tests mixing explicit string labels with default labels.
83#[driver_test(id(ID))]
84pub async fn mixed_explicit_and_default_labels(t: &mut Test) -> Result<()> {
85    #[derive(Debug, PartialEq, toasty::Embed)]
86    enum Status {
87        #[column(variant = "waiting")]
88        Pending,
89        Active,
90        Done,
91    }
92
93    #[derive(Debug, toasty::Model)]
94    struct Task {
95        #[key]
96        #[auto]
97        id: ID,
98        status: Status,
99    }
100
101    let mut db = t.setup_db(models!(Task, Status)).await;
102
103    // "waiting" is the explicit label for Pending
104    let t1 = toasty::create!(Task {
105        status: Status::Pending
106    })
107    .exec(&mut db)
108    .await?;
109    assert_eq!(t1.status, Status::Pending);
110
111    // "Active" is the default label
112    let t2 = toasty::create!(Task {
113        status: Status::Active
114    })
115    .exec(&mut db)
116    .await?;
117
118    let found1 = Task::get_by_id(&mut db, &t1.id).await?;
119    let found2 = Task::get_by_id(&mut db, &t2.id).await?;
120    assert_eq!(found1.status, Status::Pending);
121    assert_eq!(found2.status, Status::Active);
122
123    Ok(())
124}
125
126/// Tests data-carrying enum with string discriminants.
127#[driver_test(id(ID))]
128pub async fn string_discriminant_data_enum(t: &mut Test) -> Result<()> {
129    #[derive(Debug, PartialEq, toasty::Embed)]
130    enum ContactMethod {
131        #[column(variant = "email")]
132        Email { address: String },
133        #[column(variant = "phone")]
134        Phone { number: String },
135    }
136
137    #[derive(Debug, toasty::Model)]
138    #[allow(dead_code)]
139    struct User {
140        #[key]
141        #[auto]
142        id: ID,
143        name: String,
144        contact: ContactMethod,
145    }
146
147    let mut db = t.setup_db(models!(User, ContactMethod)).await;
148
149    let user = toasty::create!(User {
150        name: "Alice",
151        contact: ContactMethod::Email {
152            address: "alice@example.com".into(),
153        },
154    })
155    .exec(&mut db)
156    .await?;
157
158    let found = User::get_by_id(&mut db, &user.id).await?;
159    assert_eq!(
160        found.contact,
161        ContactMethod::Email {
162            address: "alice@example.com".into()
163        }
164    );
165
166    // Update to a different variant
167    let mut user = found;
168    user.update()
169        .contact(ContactMethod::Phone {
170            number: "555-0100".into(),
171        })
172        .exec(&mut db)
173        .await?;
174
175    let found = User::get_by_id(&mut db, &user.id).await?;
176    assert_eq!(
177        found.contact,
178        ContactMethod::Phone {
179            number: "555-0100".into()
180        }
181    );
182
183    Ok(())
184}
185
186/// Tests data-carrying enum with default string labels (variant ident as discriminant).
187#[driver_test(id(ID))]
188pub async fn default_string_labels_data_enum(t: &mut Test) -> Result<()> {
189    #[derive(Debug, PartialEq, toasty::Embed)]
190    enum ContactMethod {
191        Email { address: String },
192        Phone { number: String },
193    }
194
195    #[derive(Debug, toasty::Model)]
196    #[allow(dead_code)]
197    struct User {
198        #[key]
199        #[auto]
200        id: ID,
201        name: String,
202        contact: ContactMethod,
203    }
204
205    let mut db = t.setup_db(models!(User, ContactMethod)).await;
206
207    let user = toasty::create!(User {
208        name: "Alice",
209        contact: ContactMethod::Email {
210            address: "alice@example.com".into(),
211        },
212    })
213    .exec(&mut db)
214    .await?;
215
216    let found = User::get_by_id(&mut db, &user.id).await?;
217    assert_eq!(
218        found.contact,
219        ContactMethod::Email {
220            address: "alice@example.com".into()
221        }
222    );
223
224    // Update to a different variant
225    let mut user = found;
226    user.update()
227        .contact(ContactMethod::Phone {
228            number: "555-0100".into(),
229        })
230        .exec(&mut db)
231        .await?;
232
233    let found = User::get_by_id(&mut db, &user.id).await?;
234    assert_eq!(
235        found.contact,
236        ContactMethod::Phone {
237            number: "555-0100".into()
238        }
239    );
240
241    Ok(())
242}
243
244/// Tests data-carrying enum mixing explicit string labels with defaults.
245#[driver_test(id(ID))]
246pub async fn mixed_string_labels_data_enum(t: &mut Test) -> Result<()> {
247    #[derive(Debug, PartialEq, toasty::Embed)]
248    enum ContactMethod {
249        #[column(variant = "mail")]
250        Email {
251            address: String,
252        },
253        Phone {
254            number: String,
255        },
256    }
257
258    #[derive(Debug, toasty::Model)]
259    #[allow(dead_code)]
260    struct User {
261        #[key]
262        #[auto]
263        id: ID,
264        name: String,
265        contact: ContactMethod,
266    }
267
268    let mut db = t.setup_db(models!(User, ContactMethod)).await;
269
270    // Create with the explicit-label variant
271    let u1 = toasty::create!(User {
272        name: "Alice",
273        contact: ContactMethod::Email {
274            address: "alice@example.com".into(),
275        },
276    })
277    .exec(&mut db)
278    .await?;
279
280    // Create with the default-label variant
281    let u2 = toasty::create!(User {
282        name: "Bob",
283        contact: ContactMethod::Phone {
284            number: "555-0200".into(),
285        },
286    })
287    .exec(&mut db)
288    .await?;
289
290    let found1 = User::get_by_id(&mut db, &u1.id).await?;
291    assert_eq!(
292        found1.contact,
293        ContactMethod::Email {
294            address: "alice@example.com".into()
295        }
296    );
297
298    let found2 = User::get_by_id(&mut db, &u2.id).await?;
299    assert_eq!(
300        found2.contact,
301        ContactMethod::Phone {
302            number: "555-0200".into()
303        }
304    );
305
306    // Update from explicit-label variant to default-label variant
307    let mut user = found1;
308    user.update()
309        .contact(ContactMethod::Phone {
310            number: "555-0300".into(),
311        })
312        .exec(&mut db)
313        .await?;
314
315    let found = User::get_by_id(&mut db, &user.id).await?;
316    assert_eq!(
317        found.contact,
318        ContactMethod::Phone {
319            number: "555-0300".into()
320        }
321    );
322
323    Ok(())
324}
325
326/// Tests filtering by variant with string discriminants.
327#[driver_test(requires(sql))]
328pub async fn filter_by_string_variant(t: &mut Test) -> Result<()> {
329    #[derive(Debug, PartialEq, toasty::Embed)]
330    enum Status {
331        #[column(variant = "pending")]
332        Pending,
333        #[column(variant = "active")]
334        Active,
335    }
336
337    #[derive(Debug, toasty::Model)]
338    #[allow(dead_code)]
339    struct Task {
340        #[key]
341        #[auto]
342        id: uuid::Uuid,
343        title: String,
344        status: Status,
345    }
346
347    let mut db = t.setup_db(models!(Task, Status)).await;
348
349    toasty::create!(Task {
350        title: "A",
351        status: Status::Pending
352    })
353    .exec(&mut db)
354    .await?;
355
356    toasty::create!(Task {
357        title: "B",
358        status: Status::Active
359    })
360    .exec(&mut db)
361    .await?;
362
363    let pending = Task::filter(Task::fields().status().is_pending())
364        .exec(&mut db)
365        .await?;
366    assert_eq!(pending.len(), 1);
367    assert_eq!(pending[0].title, "A");
368
369    Ok(())
370}
371
372/// Verifies the schema registers string discriminants with the correct type.
373#[driver_test]
374pub async fn string_discriminant_schema_registration(t: &mut Test) {
375    #[derive(Debug, PartialEq, toasty::Embed)]
376    enum Color {
377        #[column(variant = "red")]
378        Red,
379        #[column(variant = "green")]
380        Green,
381        #[column(variant = "blue")]
382        Blue,
383    }
384
385    let db = t.setup_db(models!(Color)).await;
386    let schema = db.schema();
387
388    let color_model = schema.app.model(Color::id()).as_embedded_enum_unwrap();
389    assert_eq!(color_model.discriminant.ty, toasty_core::stmt::Type::String);
390    assert_eq!(color_model.variants.len(), 3);
391    assert_eq!(
392        color_model.variants[0].discriminant,
393        toasty_core::stmt::Value::String("red".to_string())
394    );
395    assert_eq!(
396        color_model.variants[1].discriminant,
397        toasty_core::stmt::Value::String("green".to_string())
398    );
399    assert_eq!(
400        color_model.variants[2].discriminant,
401        toasty_core::stmt::Value::String("blue".to_string())
402    );
403}