Skip to main content

toasty_driver_integration_suite/tests/
embed_enum_variant_patch.rs

1//! `stmt::patch` may step through embedded structs and may replace an
2//! embedded enum as a whole, but it may not enter an enum variant. These
3//! tests pin that boundary: a path into a variant rejects the whole update
4//! with `unsupported_feature` before any write, while the supported forms
5//! on the same model keep working.
6
7use crate::prelude::*;
8use toasty::stmt;
9
10#[derive(Debug, PartialEq, toasty::Embed)]
11enum State {
12    Active { count: i64 },
13    Inactive,
14}
15
16#[derive(Debug, PartialEq, toasty::Embed)]
17enum Nested {
18    Wrapped { state: State },
19    Empty,
20}
21
22/// `state` sits after `unrelated`, so the variant-local index of
23/// `Active.count` (0) names `unrelated` when the variant root is dropped.
24#[derive(Debug, PartialEq, toasty::Embed)]
25struct Details {
26    unrelated: i64,
27    state: State,
28    nested: Nested,
29}
30
31#[derive(Debug, toasty::Model)]
32struct Item {
33    #[key]
34    #[auto]
35    id: uuid::Uuid,
36    name: String,
37    details: Details,
38}
39
40fn initial_details() -> Details {
41    Details {
42        unrelated: 1,
43        state: State::Active { count: 5 },
44        nested: Nested::Wrapped {
45            state: State::Active { count: 7 },
46        },
47    }
48}
49
50async fn setup(t: &mut Test) -> Result<(toasty::Db, Item)> {
51    let mut db = t.setup_db(models!(Item)).await;
52    let item = toasty::create!(Item {
53        name: "before",
54        details: initial_details(),
55    })
56    .exec(&mut db)
57    .await?;
58    Ok((db, item))
59}
60
61/// Executes an update carrying `patch` on `details` beside an ordinary
62/// assignment to `name`, and checks that the statement is rejected as a
63/// whole: `unsupported_feature`, no driver operation, and every field of
64/// the row unchanged.
65async fn assert_rejected(
66    t: &Test,
67    db: &mut toasty::Db,
68    item: &Item,
69    patch: stmt::Assignment<Details>,
70) -> Result<()> {
71    t.log().clear();
72    let err = assert_err!(
73        Item::filter_by_id(item.id)
74            .update()
75            .name("after")
76            .details(patch)
77            .exec(db)
78            .await
79    );
80    assert!(err.is_unsupported_feature(), "{err}");
81    assert!(t.log().is_empty());
82
83    let found = Item::get_by_id(db, item.id).await?;
84    assert_eq!(found.name, "before");
85    assert_eq!(found.details, initial_details());
86    Ok(())
87}
88
89#[driver_test]
90pub async fn reject_set_through_variant(t: &mut Test) -> Result<()> {
91    let (mut db, item) = setup(t).await?;
92    let patch = stmt::patch(Details::fields().state().active().count(), 9);
93    assert_rejected(t, &mut db, &item, patch).await
94}
95
96#[driver_test]
97pub async fn reject_increment_through_variant(t: &mut Test) -> Result<()> {
98    let (mut db, item) = setup(t).await?;
99    let patch = stmt::patch(
100        Details::fields().state().active().count(),
101        stmt::increment(),
102    );
103    assert_rejected(t, &mut db, &item, patch).await
104}
105
106#[driver_test]
107pub async fn reject_patch_through_nested_variants(t: &mut Test) -> Result<()> {
108    let (mut db, item) = setup(t).await?;
109    let count = || {
110        Details::fields()
111            .nested()
112            .wrapped()
113            .state()
114            .active()
115            .count()
116    };
117
118    assert_rejected(t, &mut db, &item, stmt::patch(count(), 9)).await?;
119    assert_rejected(t, &mut db, &item, stmt::patch(count(), stmt::increment())).await
120}
121
122/// The supported forms on the same model: patching a struct field beside
123/// the enum, and replacing an enum nested in the struct as a whole.
124#[driver_test]
125pub async fn patch_beside_variant_boundary(t: &mut Test) -> Result<()> {
126    let (mut db, mut item) = setup(t).await?;
127
128    item.update()
129        .details(stmt::apply([
130            stmt::patch(Details::fields().unrelated(), stmt::increment()),
131            stmt::patch(Details::fields().state().into(), State::Inactive),
132        ]))
133        .exec(&mut db)
134        .await?;
135    assert_eq!(
136        Item::get_by_id(&mut db, item.id).await?.details,
137        Details {
138            unrelated: 2,
139            state: State::Inactive,
140            nested: Nested::Wrapped {
141                state: State::Active { count: 7 },
142            },
143        }
144    );
145
146    item.update()
147        .details(stmt::patch(
148            Details::fields().nested().into(),
149            Nested::Wrapped {
150                state: State::Active { count: 8 },
151            },
152        ))
153        .exec(&mut db)
154        .await?;
155    assert_eq!(
156        Item::get_by_id(&mut db, item.id).await?.details,
157        Details {
158            unrelated: 2,
159            state: State::Inactive,
160            nested: Nested::Wrapped {
161                state: State::Active { count: 8 },
162            },
163        }
164    );
165
166    Ok(())
167}