toasty_driver_integration_suite/tests/
embed_enum_data.rs1use crate::prelude::*;
2
3#[driver_test(scenario(crate::scenarios::user_contact_info))]
6pub async fn data_carrying_enum_schema(t: &mut Test) {
7 let db = setup(t).await;
8 let schema = db.schema();
9
10 let contact_info = &schema.app.models[&ContactInfo::id()];
11 assert_struct!(contact_info, toasty::schema::app::Model::EmbeddedEnum({
12 name.upper_camel_case(): "ContactInfo",
13 variants: [
14 {
15 name.upper_camel_case(): "Email",
16 discriminant: toasty_core::stmt::Value::I64(1),
17 ..
18 },
19 {
20 name.upper_camel_case(): "Phone",
21 discriminant: toasty_core::stmt::Value::I64(2),
22 ..
23 },
24 ],
25 fields: [
26 { id.index: 0, name.app: Some("address") },
27 { id.index: 1, name.app: Some("number") },
28 ],
29 }));
30}
31
32#[driver_test(scenario(crate::scenarios::task_with_status))]
36pub async fn mixed_enum_schema(t: &mut Test) {
37 let db = setup(t).await;
38 let schema = db.schema();
39
40 let status = &schema.app.models[&Status::id()];
41 assert_struct!(status, toasty::schema::app::Model::EmbeddedEnum({
42 variants: [
43 {
44 name.upper_camel_case(): "Pending",
45 discriminant: toasty_core::stmt::Value::I64(1),
46 ..
47 },
48 {
49 name.upper_camel_case(): "Failed",
50 discriminant: toasty_core::stmt::Value::I64(2),
51 ..
52 },
53 {
54 name.upper_camel_case(): "Done",
55 discriminant: toasty_core::stmt::Value::I64(3),
56 ..
57 },
58 ],
59 fields: [
60 { id.index: 0, name.app: Some("reason") },
61 ],
62 }));
63}
64
65#[driver_test]
68pub async fn data_carrying_enum_db_schema(test: &mut Test) {
69 #[allow(dead_code)]
70 #[derive(toasty::Embed)]
71 enum ContactInfo {
72 #[column(variant = 1)]
73 Email { address: String },
74 #[column(variant = 2)]
75 Phone { number: String },
76 }
77
78 #[derive(toasty::Model)]
79 struct User {
80 #[key]
81 id: String,
82 #[allow(dead_code)]
83 contact: ContactInfo,
84 }
85
86 let db = test.setup_db(models!(User)).await;
87 let schema = db.schema();
88
89 assert_struct!(schema.db.tables, [
91 {
92 name: =~ r"users$",
93 columns: [
94 { name: "id" },
95 { name: "contact", nullable: false },
96 { name: "contact_address", nullable: true },
97 { name: "contact_number", nullable: true },
98 ],
99 },
100 ]);
101}
102
103#[driver_test(scenario(crate::scenarios::user_contact_info))]
106pub async fn data_variant_roundtrip(test: &mut Test) -> Result<()> {
107 let mut db = setup(test).await;
108
109 let alice = User::create()
110 .name("Alice")
111 .contact(ContactInfo::Email {
112 address: "alice@example.com".to_string(),
113 })
114 .exec(&mut db)
115 .await?;
116
117 let bob = User::create()
118 .name("Bob")
119 .contact(ContactInfo::Phone {
120 number: "555-1234".to_string(),
121 })
122 .exec(&mut db)
123 .await?;
124
125 let found_alice = User::get_by_id(&mut db, &alice.id).await?;
127 assert_eq!(
128 found_alice.contact,
129 ContactInfo::Email {
130 address: "alice@example.com".to_string()
131 }
132 );
133
134 let found_bob = User::get_by_id(&mut db, &bob.id).await?;
135 assert_eq!(
136 found_bob.contact,
137 ContactInfo::Phone {
138 number: "555-1234".to_string()
139 }
140 );
141
142 alice.delete().exec(&mut db).await?;
144 bob.delete().exec(&mut db).await?;
145 Ok(())
146}
147
148#[driver_test(scenario(crate::scenarios::task_with_status))]
151pub async fn mixed_enum_roundtrip(t: &mut Test) -> Result<()> {
152 let mut db = setup(t).await;
153
154 let pending = Task::create()
155 .title("Pending task")
156 .status(Status::Pending)
157 .exec(&mut db)
158 .await?;
159
160 let failed = Task::create()
161 .title("Failed task")
162 .status(Status::Failed {
163 reason: "out of memory".to_string(),
164 })
165 .exec(&mut db)
166 .await?;
167
168 let done = Task::create()
169 .title("Done task")
170 .status(Status::Done)
171 .exec(&mut db)
172 .await?;
173
174 let found_pending = Task::get_by_id(&mut db, &pending.id).await?;
175 assert_eq!(found_pending.status, Status::Pending);
176
177 let found_failed = Task::get_by_id(&mut db, &failed.id).await?;
178 assert_eq!(
179 found_failed.status,
180 Status::Failed {
181 reason: "out of memory".to_string()
182 }
183 );
184
185 let found_done = Task::get_by_id(&mut db, &done.id).await?;
186 assert_eq!(found_done.status, Status::Done);
187
188 Ok(())
189}
190
191#[driver_test(scenario(crate::scenarios::task_with_status))]
196pub async fn mixed_enum_update_data_to_unit_variant(t: &mut Test) -> Result<()> {
197 let mut db = setup(t).await;
198
199 let mut task = toasty::create!(Task {
200 title: "task",
201 status: Status::Failed {
202 reason: "boom".to_string(),
203 },
204 })
205 .exec(&mut db)
206 .await?;
207
208 task.update().status(Status::Pending).exec(&mut db).await?;
210 assert_eq!(
211 Task::get_by_id(&mut db, &task.id).await?.status,
212 Status::Pending
213 );
214
215 task.update()
217 .status(Status::Failed {
218 reason: "again".to_string(),
219 })
220 .exec(&mut db)
221 .await?;
222 assert_eq!(
223 Task::get_by_id(&mut db, &task.id).await?.status,
224 Status::Failed {
225 reason: "again".to_string()
226 }
227 );
228
229 Ok(())
230}
231
232#[driver_test]
237pub async fn enum_update_between_variants_of_different_width(test: &mut Test) -> Result<()> {
238 #[derive(Debug, PartialEq, toasty::Embed)]
239 enum Event {
240 #[column(variant = 1)]
241 Login { user: String },
242 #[column(variant = 2)]
243 Purchase { item: String, amount: i64 },
244 }
245
246 #[derive(Debug, toasty::Model)]
247 struct Log {
248 #[key]
249 #[auto]
250 id: uuid::Uuid,
251 event: Event,
252 }
253
254 let mut db = test.setup_db(models!(Log)).await;
255
256 let mut log = toasty::create!(Log {
257 event: Event::Purchase {
258 item: "book".to_string(),
259 amount: 42,
260 },
261 })
262 .exec(&mut db)
263 .await?;
264
265 log.update()
267 .event(Event::Login {
268 user: "alice".to_string(),
269 })
270 .exec(&mut db)
271 .await?;
272 assert_eq!(
273 Log::get_by_id(&mut db, &log.id).await?.event,
274 Event::Login {
275 user: "alice".to_string()
276 }
277 );
278
279 Ok(())
280}
281
282#[driver_test]
285pub async fn data_variant_with_uuid_field(test: &mut Test) -> Result<()> {
286 #[derive(Debug, PartialEq, toasty::Embed)]
287 enum OrderRef {
288 #[column(variant = 1)]
289 Internal { id: uuid::Uuid },
290 #[column(variant = 2)]
291 External { code: String },
292 }
293
294 #[derive(Debug, toasty::Model)]
295 struct Order {
296 #[key]
297 #[auto]
298 id: uuid::Uuid,
299 order_ref: OrderRef,
300 }
301
302 let mut db = test.setup_db(models!(Order)).await;
303
304 let internal_id = uuid::Uuid::new_v4();
305
306 let o1 = Order::create()
307 .order_ref(OrderRef::Internal { id: internal_id })
308 .exec(&mut db)
309 .await?;
310
311 let o2 = Order::create()
312 .order_ref(OrderRef::External {
313 code: "EXT-001".to_string(),
314 })
315 .exec(&mut db)
316 .await?;
317
318 let found_o1 = Order::get_by_id(&mut db, &o1.id).await?;
319 assert_eq!(found_o1.order_ref, OrderRef::Internal { id: internal_id });
320
321 let found_o2 = Order::get_by_id(&mut db, &o2.id).await?;
322 assert_eq!(
323 found_o2.order_ref,
324 OrderRef::External {
325 code: "EXT-001".to_string()
326 }
327 );
328
329 Ok(())
330}
331
332#[driver_test]
335pub async fn data_variant_with_jiff_timestamp(test: &mut Test) -> Result<()> {
336 #[derive(Debug, PartialEq, toasty::Embed)]
337 enum EventTime {
338 #[column(variant = 1)]
339 Scheduled { at: jiff::Timestamp },
340 #[column(variant = 2)]
341 Unscheduled,
342 }
343
344 #[derive(Debug, toasty::Model)]
345 struct Event {
346 #[key]
347 #[auto]
348 id: uuid::Uuid,
349 name: String,
350 time: EventTime,
351 }
352
353 let mut db = test.setup_db(models!(Event)).await;
354
355 let ts = jiff::Timestamp::from_second(1_700_000_000).unwrap();
356
357 let scheduled = Event::create()
358 .name("launch")
359 .time(EventTime::Scheduled { at: ts })
360 .exec(&mut db)
361 .await?;
362
363 let unscheduled = Event::create()
364 .name("tbd")
365 .time(EventTime::Unscheduled)
366 .exec(&mut db)
367 .await?;
368
369 let found_scheduled = Event::get_by_id(&mut db, &scheduled.id).await?;
370 assert_eq!(found_scheduled.time, EventTime::Scheduled { at: ts });
371
372 let found_unscheduled = Event::get_by_id(&mut db, &unscheduled.id).await?;
373 assert_eq!(found_unscheduled.time, EventTime::Unscheduled);
374
375 Ok(())
376}
377
378#[driver_test]
379pub async fn struct_in_data_variant(test: &mut Test) -> Result<()> {
380 #[derive(Debug, PartialEq, toasty::Embed)]
381 struct Address {
382 street: String,
383 city: String,
384 }
385
386 #[derive(Debug, PartialEq, toasty::Embed)]
387 enum Destination {
388 #[column(variant = 1)]
389 Digital { email: String },
390 #[column(variant = 2)]
391 Physical { address: Address },
392 }
393
394 #[derive(Debug, toasty::Model)]
395 struct Shipment {
396 #[key]
397 #[auto]
398 id: uuid::Uuid,
399 destination: Destination,
400 }
401
402 let mut db = test.setup_db(models!(Shipment)).await;
403
404 let digital = Shipment::create()
405 .destination(Destination::Digital {
406 email: "user@example.com".to_string(),
407 })
408 .exec(&mut db)
409 .await?;
410
411 let physical = Shipment::create()
412 .destination(Destination::Physical {
413 address: Address {
414 street: "123 Main St".to_string(),
415 city: "Seattle".to_string(),
416 },
417 })
418 .exec(&mut db)
419 .await?;
420
421 let found_digital = Shipment::get_by_id(&mut db, &digital.id).await?;
422 assert_eq!(
423 found_digital.destination,
424 Destination::Digital {
425 email: "user@example.com".to_string()
426 }
427 );
428
429 let found_physical = Shipment::get_by_id(&mut db, &physical.id).await?;
430 assert_eq!(
431 found_physical.destination,
432 Destination::Physical {
433 address: Address {
434 street: "123 Main St".to_string(),
435 city: "Seattle".to_string(),
436 },
437 }
438 );
439
440 Ok(())
441}
442
443#[driver_test]
446pub async fn enum_in_enum_roundtrip(test: &mut Test) -> Result<()> {
447 #[derive(Debug, PartialEq, toasty::Embed)]
448 enum Channel {
449 #[column(variant = 1)]
450 Email,
451 #[column(variant = 2)]
452 Sms,
453 }
454
455 #[derive(Debug, PartialEq, toasty::Embed)]
456 enum Notification {
457 #[column(variant = 1)]
458 Send { channel: Channel, message: String },
459 #[column(variant = 2)]
460 Suppress,
461 }
462
463 #[derive(Debug, toasty::Model)]
464 struct Alert {
465 #[key]
466 #[auto]
467 id: uuid::Uuid,
468 notification: Notification,
469 }
470
471 let mut db = test.setup_db(models!(Alert)).await;
472
473 let a1 = Alert::create()
474 .notification(Notification::Send {
475 channel: Channel::Email,
476 message: "hello".to_string(),
477 })
478 .exec(&mut db)
479 .await?;
480
481 let a2 = Alert::create()
482 .notification(Notification::Send {
483 channel: Channel::Sms,
484 message: "world".to_string(),
485 })
486 .exec(&mut db)
487 .await?;
488
489 let a3 = Alert::create()
490 .notification(Notification::Suppress)
491 .exec(&mut db)
492 .await?;
493
494 let found_a1 = Alert::get_by_id(&mut db, &a1.id).await?;
495 assert_eq!(
496 found_a1.notification,
497 Notification::Send {
498 channel: Channel::Email,
499 message: "hello".to_string(),
500 }
501 );
502
503 let found_a2 = Alert::get_by_id(&mut db, &a2.id).await?;
504 assert_eq!(
505 found_a2.notification,
506 Notification::Send {
507 channel: Channel::Sms,
508 message: "world".to_string(),
509 }
510 );
511
512 let found_a3 = Alert::get_by_id(&mut db, &a3.id).await?;
513 assert_eq!(found_a3.notification, Notification::Suppress);
514
515 Ok(())
516}
517
518#[driver_test]
521pub async fn global_field_indices(test: &mut Test) {
522 #[allow(dead_code)]
523 #[derive(toasty::Embed)]
524 enum Event {
525 #[column(variant = 1)]
526 Login { user_id: String, ip: String },
527 #[column(variant = 2)]
528 Purchase { item_id: String, amount: i64 },
529 }
530
531 #[derive(toasty::Model)]
532 #[allow(dead_code)]
533 struct Container {
534 #[key]
535 id: i64,
536 event: Event,
537 }
538
539 let db = test.setup_db(models!(Container)).await;
540 let schema = db.schema();
541
542 let event = &schema.app.models[&Event::id()];
543 assert_struct!(event, toasty::schema::app::Model::EmbeddedEnum({
544 fields: [
545 { id.index: 0, name.app: Some("user_id") },
546 { id.index: 1, name.app: Some("ip") },
547 { id.index: 2, name.app: Some("item_id") },
548 { id.index: 3, name.app: Some("amount") },
549 ],
550 }));
551}