Skip to main content

cratestack_sqlx/
delegate.rs

1use crate::sqlx;
2
3use cratestack_core::{CoolContext, CoolError};
4
5use crate::{
6    CreateModelInput, CreateRecord, DeleteRecord, Filter, FilterExpr, FindMany, FindUnique,
7    ModelDescriptor, OrderClause, SqlxRuntime, UpdateModelInput, UpdateRecord, UpdateRecordSet,
8    UpsertModelInput, UpsertRecord,
9};
10
11#[derive(Debug, Clone, Copy)]
12pub struct ModelDelegate<'a, M: 'static, PK: 'static> {
13    runtime: &'a SqlxRuntime,
14    descriptor: &'static ModelDescriptor<M, PK>,
15}
16
17impl<'a, M: 'static, PK: 'static> ModelDelegate<'a, M, PK> {
18    pub fn new(runtime: &'a SqlxRuntime, descriptor: &'static ModelDescriptor<M, PK>) -> Self {
19        Self {
20            runtime,
21            descriptor,
22        }
23    }
24
25    pub fn descriptor(&self) -> &'static ModelDescriptor<M, PK> {
26        self.descriptor
27    }
28
29    pub fn bind(self, ctx: CoolContext) -> ScopedModelDelegate<'a, M, PK> {
30        ScopedModelDelegate {
31            delegate: self,
32            ctx,
33        }
34    }
35
36    pub fn find_many(&self) -> FindMany<'a, M, PK> {
37        FindMany {
38            runtime: self.runtime,
39            descriptor: self.descriptor,
40            filters: Vec::new(),
41            order_by: Vec::new(),
42            limit: None,
43            offset: None,
44        }
45    }
46
47    pub fn find_unique(&self, id: PK) -> FindUnique<'a, M, PK> {
48        FindUnique {
49            runtime: self.runtime,
50            descriptor: self.descriptor,
51            id,
52        }
53    }
54
55    pub fn create<I>(&self, input: I) -> CreateRecord<'a, M, PK, I> {
56        CreateRecord {
57            runtime: self.runtime,
58            descriptor: self.descriptor,
59            input,
60        }
61    }
62
63    /// Insert-or-update on primary-key conflict. Available only on models
64    /// whose `@id` field is client-supplied (no `@default(...)`); attempting
65    /// to call this on a model with a server-generated PK is a compile error.
66    pub fn upsert<I>(&self, input: I) -> UpsertRecord<'a, M, PK, I> {
67        UpsertRecord {
68            runtime: self.runtime,
69            descriptor: self.descriptor,
70            input,
71        }
72    }
73
74    pub fn update(&self, id: PK) -> UpdateRecord<'a, M, PK> {
75        UpdateRecord {
76            runtime: self.runtime,
77            descriptor: self.descriptor,
78            id,
79        }
80    }
81
82    pub fn delete(&self, id: PK) -> DeleteRecord<'a, M, PK> {
83        DeleteRecord {
84            runtime: self.runtime,
85            descriptor: self.descriptor,
86            id,
87        }
88    }
89
90    pub async fn authorize_detail(&self, id: PK, ctx: &CoolContext) -> Result<(), CoolError>
91    where
92        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
93    {
94        crate::query::authorize_record_action(
95            self.runtime,
96            self.descriptor,
97            id,
98            self.descriptor.detail_allow_policies,
99            self.descriptor.detail_deny_policies,
100            ctx,
101            "detail",
102        )
103        .await
104    }
105
106    pub async fn authorize_update(&self, id: PK, ctx: &CoolContext) -> Result<(), CoolError>
107    where
108        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
109    {
110        crate::query::authorize_record_action(
111            self.runtime,
112            self.descriptor,
113            id,
114            self.descriptor.update_allow_policies,
115            self.descriptor.update_deny_policies,
116            ctx,
117            "update",
118        )
119        .await
120    }
121
122    pub async fn authorize_delete(&self, id: PK, ctx: &CoolContext) -> Result<(), CoolError>
123    where
124        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
125    {
126        crate::query::authorize_record_action(
127            self.runtime,
128            self.descriptor,
129            id,
130            self.descriptor.delete_allow_policies,
131            self.descriptor.delete_deny_policies,
132            ctx,
133            "delete",
134        )
135        .await
136    }
137}
138
139#[derive(Debug, Clone)]
140pub struct ScopedModelDelegate<'a, M: 'static, PK: 'static> {
141    delegate: ModelDelegate<'a, M, PK>,
142    ctx: CoolContext,
143}
144
145impl<'a, M: 'static, PK: 'static> ScopedModelDelegate<'a, M, PK> {
146    pub fn descriptor(&self) -> &'static ModelDescriptor<M, PK> {
147        self.delegate.descriptor()
148    }
149
150    pub fn context(&self) -> &CoolContext {
151        &self.ctx
152    }
153
154    pub fn find_many(&self) -> ScopedFindMany<'a, M, PK> {
155        ScopedFindMany {
156            request: self.delegate.find_many(),
157            ctx: self.ctx.clone(),
158        }
159    }
160
161    pub fn find_unique(&self, id: PK) -> ScopedFindUnique<'a, M, PK> {
162        ScopedFindUnique {
163            request: self.delegate.find_unique(id),
164            ctx: self.ctx.clone(),
165        }
166    }
167
168    pub fn create<I>(&self, input: I) -> ScopedCreateRecord<'a, M, PK, I> {
169        ScopedCreateRecord {
170            request: self.delegate.create(input),
171            ctx: self.ctx.clone(),
172        }
173    }
174
175    pub fn upsert<I>(&self, input: I) -> ScopedUpsertRecord<'a, M, PK, I> {
176        ScopedUpsertRecord {
177            request: self.delegate.upsert(input),
178            ctx: self.ctx.clone(),
179        }
180    }
181
182    pub fn update(&self, id: PK) -> ScopedUpdateRecord<'a, M, PK> {
183        ScopedUpdateRecord {
184            request: self.delegate.update(id),
185            ctx: self.ctx.clone(),
186        }
187    }
188
189    pub fn delete(&self, id: PK) -> ScopedDeleteRecord<'a, M, PK> {
190        ScopedDeleteRecord {
191            request: self.delegate.delete(id),
192            ctx: self.ctx.clone(),
193        }
194    }
195}
196
197#[derive(Debug, Clone)]
198pub struct ScopedFindMany<'a, M: 'static, PK: 'static> {
199    request: FindMany<'a, M, PK>,
200    ctx: CoolContext,
201}
202
203impl<'a, M: 'static, PK: 'static> ScopedFindMany<'a, M, PK> {
204    pub fn where_(mut self, filter: Filter) -> Self {
205        self.request = self.request.where_(filter);
206        self
207    }
208
209    pub fn where_expr(mut self, filter: FilterExpr) -> Self {
210        self.request = self.request.where_expr(filter);
211        self
212    }
213
214    pub fn where_any(mut self, filters: impl IntoIterator<Item = FilterExpr>) -> Self {
215        self.request = self.request.where_any(filters);
216        self
217    }
218
219    pub fn order_by(mut self, clause: OrderClause) -> Self {
220        self.request = self.request.order_by(clause);
221        self
222    }
223
224    pub fn limit(mut self, limit: i64) -> Self {
225        self.request = self.request.limit(limit);
226        self
227    }
228
229    pub fn offset(mut self, offset: i64) -> Self {
230        self.request = self.request.offset(offset);
231        self
232    }
233
234    pub fn preview_sql(&self) -> String {
235        self.request.preview_sql()
236    }
237
238    pub fn preview_scoped_sql(&self) -> String {
239        self.request.preview_scoped_sql(&self.ctx)
240    }
241
242    pub async fn run(self) -> Result<Vec<M>, CoolError>
243    where
244        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow>,
245    {
246        self.request.run(&self.ctx).await
247    }
248}
249
250#[derive(Debug, Clone)]
251pub struct ScopedFindUnique<'a, M: 'static, PK: 'static> {
252    request: FindUnique<'a, M, PK>,
253    ctx: CoolContext,
254}
255
256impl<'a, M: 'static, PK: 'static> ScopedFindUnique<'a, M, PK> {
257    pub fn preview_sql(&self) -> String {
258        self.request.preview_sql()
259    }
260
261    pub fn preview_scoped_sql(&self) -> String {
262        self.request.preview_scoped_sql(&self.ctx)
263    }
264
265    pub async fn run(self) -> Result<Option<M>, CoolError>
266    where
267        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow>,
268        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
269    {
270        self.request.run(&self.ctx).await
271    }
272}
273
274#[derive(Debug, Clone)]
275pub struct ScopedCreateRecord<'a, M: 'static, PK: 'static, I> {
276    request: CreateRecord<'a, M, PK, I>,
277    ctx: CoolContext,
278}
279
280impl<'a, M: 'static, PK: 'static, I> ScopedCreateRecord<'a, M, PK, I>
281where
282    I: CreateModelInput<M>,
283{
284    pub fn preview_sql(&self) -> String {
285        self.request.preview_sql()
286    }
287
288    pub async fn run(self) -> Result<M, CoolError>
289    where
290        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
291    {
292        self.request.run(&self.ctx).await
293    }
294}
295
296#[derive(Debug, Clone)]
297pub struct ScopedUpsertRecord<'a, M: 'static, PK: 'static, I> {
298    request: UpsertRecord<'a, M, PK, I>,
299    ctx: CoolContext,
300}
301
302impl<'a, M: 'static, PK: 'static, I> ScopedUpsertRecord<'a, M, PK, I>
303where
304    I: UpsertModelInput<M>,
305{
306    pub fn preview_sql(&self) -> String {
307        self.request.preview_sql()
308    }
309
310    pub async fn run(self) -> Result<M, CoolError>
311    where
312        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
313        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
314    {
315        self.request.run(&self.ctx).await
316    }
317}
318
319#[derive(Debug, Clone)]
320pub struct ScopedUpdateRecord<'a, M: 'static, PK: 'static> {
321    request: UpdateRecord<'a, M, PK>,
322    ctx: CoolContext,
323}
324
325impl<'a, M: 'static, PK: 'static> ScopedUpdateRecord<'a, M, PK> {
326    pub fn set<I>(self, input: I) -> ScopedUpdateRecordSet<'a, M, PK, I> {
327        ScopedUpdateRecordSet {
328            request: self.request.set(input),
329            ctx: self.ctx,
330        }
331    }
332}
333
334#[derive(Debug, Clone)]
335pub struct ScopedUpdateRecordSet<'a, M: 'static, PK: 'static, I> {
336    request: UpdateRecordSet<'a, M, PK, I>,
337    ctx: CoolContext,
338}
339
340impl<'a, M: 'static, PK: 'static, I> ScopedUpdateRecordSet<'a, M, PK, I>
341where
342    I: UpdateModelInput<M>,
343{
344    pub fn preview_sql(&self) -> String {
345        self.request.preview_sql()
346    }
347
348    pub async fn run(self) -> Result<M, CoolError>
349    where
350        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
351        PK: Send + Clone + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
352    {
353        self.request.run(&self.ctx).await
354    }
355
356    /// Attach an expected version for optimistic locking. See
357    /// [`UpdateRecordSet::if_match`].
358    pub fn if_match(mut self, expected: i64) -> Self {
359        self.request = self.request.if_match(expected);
360        self
361    }
362}
363
364#[derive(Debug, Clone)]
365pub struct ScopedDeleteRecord<'a, M: 'static, PK: 'static> {
366    request: DeleteRecord<'a, M, PK>,
367    ctx: CoolContext,
368}
369
370impl<'a, M: 'static, PK: 'static> ScopedDeleteRecord<'a, M, PK> {
371    pub fn preview_sql(&self) -> String {
372        self.request.preview_sql()
373    }
374
375    pub async fn run(self) -> Result<M, CoolError>
376    where
377        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
378        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
379    {
380        self.request.run(&self.ctx).await
381    }
382}