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