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