dynamodb_facade/operations/update.rs
1use std::future::{Future, IntoFuture};
2use std::pin::Pin;
3
4use super::*;
5
6use aws_sdk_dynamodb::operation::update_item::builders::UpdateItemFluentBuilder;
7
8/// Builder for a DynamoDB `UpdateItem` request.
9///
10/// Constructed via [`DynamoDBItemOp::update`] / [`DynamoDBItemOp::update_by_id`]
11/// (typed, with a concrete `T`) or [`UpdateItemRequest::new`] (stand-alone,
12/// raw output). The builder provides:
13///
14/// - **Output format** — the result can be deserialized into `T`.
15/// Call [`.raw()`][UpdateItemRequest::raw] to receive an untyped [`Item<TD>`]
16/// instead (one-way).
17/// - **Return value** — by default nothing is returned. Call
18/// [`.return_old()`][UpdateItemRequest::return_old],
19/// [`.return_new()`][UpdateItemRequest::return_new], or
20/// [`.return_none()`][UpdateItemRequest::return_none] to choose whether
21/// DynamoDB returns the pre- or post-update item.
22/// [`.return_new()`][UpdateItemRequest::return_new] returns `T` /
23/// `Item<TD>` directly (DynamoDB's `ALL_NEW` always provides the updated
24/// item). [`.return_old()`][UpdateItemRequest::return_old] returns
25/// `Option<T>` / `Option<Item<TD>>` because the item may not have existed
26/// before the update (DynamoDB's `UpdateItem` is an upsert). Note:
27/// [`DynamoDBItemOp::update_by_id`] starts with return-new by default.
28/// - **Condition** — optionally add a guard expression via
29/// [`.condition()`][UpdateItemRequest::condition],
30/// [`.exists()`][UpdateItemRequest::exists], or
31/// [`.not_exists()`][UpdateItemRequest::not_exists]. DynamoDB accepts a
32/// single condition expression per request, so this can only be called once.
33///
34/// The builder implements [`IntoFuture`], so it can
35/// be `.await`ed directly.
36///
37/// # Errors
38///
39/// Returns [`Err`] if the DynamoDB request fails, if a condition check
40/// fails, or if deserialization of the returned attributes fails.
41///
42/// # Examples
43///
44/// ```no_run
45/// # use dynamodb_facade::test_fixtures::*;
46/// use dynamodb_facade::{DynamoDBItemOp, Condition, KeyId, Update};
47///
48/// # async fn example(cclient: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
49/// # let client = cclient.clone();
50/// // Simple update by ID (returns the updated item by default)
51/// let updated /* : User */ = User::update_by_id(
52/// client,
53/// KeyId::pk("user-1"),
54/// Update::set("role", "instructor"),
55/// )
56/// .await?;
57///
58/// # let client = cclient.clone();
59/// // Update guarded by existence
60/// let updated /* : User */ = User::update_by_id(
61/// client,
62/// KeyId::pk("user-1"),
63/// Update::set("role", "instructor"),
64/// )
65/// .exists()
66/// .await?;
67///
68/// # let client = cclient.clone();
69/// // Update with a custom condition
70/// let updated /* : User */ = User::update_by_id(
71/// client,
72/// KeyId::pk("user-1"),
73/// Update::set("role", "instructor"),
74/// )
75/// .condition(Condition::eq("role", "student"))
76/// .await?;
77///
78/// # let client = cclient.clone();
79/// // Update without returning the item
80/// User::update_by_id(
81/// client,
82/// KeyId::pk("user-1"),
83/// Update::set("name", "Bob"),
84/// )
85/// .exists()
86/// .return_none()
87/// .await?;
88/// # Ok(())
89/// # }
90/// ```
91#[must_use = "builder does nothing until awaited or executed"]
92pub struct UpdateItemRequest<
93 TD: TableDefinition,
94 T = (),
95 O: OutputFormat = Raw,
96 R: ReturnValue = ReturnNothing,
97 C: ConditionState = NoCondition,
98> {
99 builder: UpdateItemFluentBuilder,
100 _marker: PhantomData<(TD, T, O, R, C)>,
101}
102
103// -- Common methods (all states) --------------------------------------------
104
105impl<TD: TableDefinition, T, O: OutputFormat, R: ReturnValue, C: ConditionState>
106 UpdateItemRequest<TD, T, O, R, C>
107{
108 /// Consumes the builder and returns the underlying SDK
109 /// [`UpdateItemFluentBuilder`].
110 ///
111 /// Use this escape hatch when you need to set options not exposed by this
112 /// facade, or when integrating with code that expects the raw SDK builder.
113 ///
114 /// # Examples
115 ///
116 /// ```no_run
117 /// # use dynamodb_facade::test_fixtures::*;
118 /// use dynamodb_facade::{DynamoDBItemOp, Update};
119 ///
120 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
121 /// let sdk_builder = sample_user()
122 /// .update(client, Update::set("role", "instructor"))
123 /// .into_inner();
124 /// // configure sdk_builder further, then call .send().await
125 /// # Ok(())
126 /// # }
127 /// ```
128 pub fn into_inner(self) -> UpdateItemFluentBuilder {
129 self.builder
130 }
131}
132
133// -- Stand-alone constructor (ReturnNothing, any C, T = (), O = Raw)
134
135impl<TD: TableDefinition> UpdateItemRequest<TD> {
136 /// Creates a stand-alone `UpdateItemRequest` with raw output (`T = ()`, `O = Raw`).
137 ///
138 /// Use this when you already have a [`Key<TD>`] and an [`Update`] expression
139 /// and do not need typed deserialization of the returned item. For typed
140 /// access, prefer [`DynamoDBItemOp::update`] or
141 /// [`DynamoDBItemOp::update_by_id`] instead.
142 ///
143 /// # Examples
144 ///
145 /// ```no_run
146 /// # use dynamodb_facade::test_fixtures::*;
147 /// use dynamodb_facade::{UpdateItemRequest, Update};
148 ///
149 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
150 /// let key = sample_user_item().into_key_only();
151 /// UpdateItemRequest::<PlatformTable>::new(client, key, Update::set("role", "instructor"))
152 /// .await?;
153 /// # Ok(())
154 /// # }
155 /// ```
156 pub fn new(client: aws_sdk_dynamodb::Client, key: Key<TD>, update: Update<'_>) -> Self {
157 Self::_new(client, key, update)
158 }
159}
160
161// -- Constructor (any R, any O, any C) ------------------------------
162
163impl<TD: TableDefinition, T, O: OutputFormat, R: ReturnValue, C: ConditionState>
164 UpdateItemRequest<TD, T, O, R, C>
165{
166 /// Creates a new `UpdateItemRequest` targeting the given key and applying `update`.
167 pub(super) fn _new(client: aws_sdk_dynamodb::Client, key: Key<TD>, update: Update<'_>) -> Self {
168 let table_name = TD::table_name();
169 tracing::debug!(table_name, ?key, %update, "UpdateItem");
170 Self {
171 builder: update.apply(
172 client
173 .update_item()
174 .table_name(table_name)
175 .set_key(Some(key.into_inner())),
176 ),
177 _marker: PhantomData,
178 }
179 }
180}
181
182// -- Return-value transitions (preserve O, C) -------------------------------
183
184// From ReturnNothing
185impl<TD: TableDefinition, T, O: OutputFormat, C: ConditionState>
186 UpdateItemRequest<TD, T, O, ReturnNothing, C>
187{
188 /// Requests that DynamoDB return the item's attributes before the update.
189 ///
190 /// When executed, [`execute`][UpdateItemRequest::execute] returns
191 /// `Option<T>` (typed) or `Option<Item<TD>>` (raw) containing the
192 /// pre-update state, or `None` if no item existed at the target key
193 /// prior to the update.
194 ///
195 /// # Examples
196 ///
197 /// ```no_run
198 /// # use dynamodb_facade::test_fixtures::*;
199 /// use dynamodb_facade::{DynamoDBItemOp, Update};
200 ///
201 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
202 /// let before /* : Option<User> */ = sample_user()
203 /// .update(client, Update::set("role", "instructor"))
204 /// .exists()
205 /// .return_old()
206 /// .await?;
207 /// # Ok(())
208 /// # }
209 /// ```
210 pub fn return_old(self) -> UpdateItemRequest<TD, T, O, Return<Old>, C> {
211 tracing::debug!("UpdateItem return_old");
212 UpdateItemRequest {
213 builder: self.builder,
214 _marker: PhantomData,
215 }
216 }
217
218 /// Requests that DynamoDB return the item's attributes after the update.
219 ///
220 /// When executed, [`execute`][UpdateItemRequest::execute] returns `T`
221 /// (typed) or [`Item<TD>`] (raw) containing the post-update state.
222 ///
223 /// # Examples
224 ///
225 /// ```no_run
226 /// # use dynamodb_facade::test_fixtures::*;
227 /// use dynamodb_facade::{DynamoDBItemOp, Update};
228 ///
229 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
230 /// let after /* : User */ = sample_user()
231 /// .update(client, Update::set("role", "instructor"))
232 /// .exists()
233 /// .return_new()
234 /// .await?;
235 /// # Ok(())
236 /// # }
237 /// ```
238 pub fn return_new(self) -> UpdateItemRequest<TD, T, O, Return<New>, C> {
239 tracing::debug!("UpdateItem return_new");
240 UpdateItemRequest {
241 builder: self.builder,
242 _marker: PhantomData,
243 }
244 }
245}
246
247// From ReturnItem<New>
248impl<TD: TableDefinition, T, O: OutputFormat, C: ConditionState>
249 UpdateItemRequest<TD, T, O, Return<New>, C>
250{
251 /// Switches from returning the post-update item to returning the
252 /// pre-update item.
253 ///
254 /// The [`execute`][UpdateItemRequest::execute] return type changes
255 /// from `T` / `Item<TD>` to `Option<T>` / `Option<Item<TD>>`,
256 /// because the old item may not exist if the update created it
257 /// (DynamoDB's `UpdateItem` is an upsert).
258 ///
259 /// # Examples
260 ///
261 /// ```no_run
262 /// # use dynamodb_facade::test_fixtures::*;
263 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
264 ///
265 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
266 /// // update_by_id defaults to Return<New>; switch to Return<Old>
267 /// let before /* : Option<User> */ = User::update_by_id(
268 /// client,
269 /// KeyId::pk("user-1"),
270 /// Update::set("role", "instructor"),
271 /// )
272 /// .exists()
273 /// .return_old()
274 /// .await?;
275 /// # Ok(())
276 /// # }
277 /// ```
278 pub fn return_old(self) -> UpdateItemRequest<TD, T, O, Return<Old>, C> {
279 tracing::debug!("UpdateItem return_old");
280 UpdateItemRequest {
281 builder: self.builder,
282 _marker: PhantomData,
283 }
284 }
285
286 /// Reverts the return-value setting so that nothing is returned.
287 ///
288 /// After this call, [`execute`][UpdateItemRequest::execute] returns `()`.
289 ///
290 /// # Examples
291 ///
292 /// ```no_run
293 /// # use dynamodb_facade::test_fixtures::*;
294 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
295 ///
296 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
297 /// // update_by_id defaults to Return<New>; opt out
298 /// User::update_by_id(
299 /// client,
300 /// KeyId::pk("user-1"),
301 /// Update::set("role", "instructor"),
302 /// )
303 /// .exists()
304 /// .return_none()
305 /// .await?;
306 /// # Ok(())
307 /// # }
308 /// ```
309 pub fn return_none(self) -> UpdateItemRequest<TD, T, O, ReturnNothing, C> {
310 tracing::debug!("UpdateItem return_none");
311 UpdateItemRequest {
312 builder: self.builder,
313 _marker: PhantomData,
314 }
315 }
316}
317
318// From ReturnItem<Old>
319impl<TD: TableDefinition, T, O: OutputFormat, C: ConditionState>
320 UpdateItemRequest<TD, T, O, Return<Old>, C>
321{
322 /// Switches from returning the pre-update item to returning the
323 /// post-update item.
324 ///
325 /// The [`execute`][UpdateItemRequest::execute] return type changes
326 /// from `Option<T>` / `Option<Item<TD>>` to `T` / `Item<TD>`,
327 /// because DynamoDB's `ALL_NEW` return mode always includes the full
328 /// item after the update.
329 ///
330 /// # Examples
331 ///
332 /// ```no_run
333 /// # use dynamodb_facade::test_fixtures::*;
334 /// use dynamodb_facade::{DynamoDBItemOp, Update};
335 ///
336 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
337 /// let after /* : User */ = sample_user()
338 /// .update(client, Update::set("role", "instructor"))
339 /// .exists()
340 /// .return_old()
341 /// .return_new()
342 /// .await?;
343 /// # Ok(())
344 /// # }
345 /// ```
346 pub fn return_new(self) -> UpdateItemRequest<TD, T, O, Return<New>, C> {
347 tracing::debug!("UpdateItem return_new");
348 UpdateItemRequest {
349 builder: self.builder,
350 _marker: PhantomData,
351 }
352 }
353
354 /// Reverts the return-value setting so that nothing is returned.
355 ///
356 /// After this call, [`execute`][UpdateItemRequest::execute] returns `()`.
357 ///
358 /// # Examples
359 ///
360 /// ```no_run
361 /// # use dynamodb_facade::test_fixtures::*;
362 /// use dynamodb_facade::{DynamoDBItemOp, Update};
363 ///
364 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
365 /// sample_user()
366 /// .update(client, Update::set("role", "instructor"))
367 /// .exists()
368 /// .return_old()
369 /// .return_none()
370 /// .await?;
371 /// # Ok(())
372 /// # }
373 /// ```
374 pub fn return_none(self) -> UpdateItemRequest<TD, T, O, ReturnNothing, C> {
375 tracing::debug!("UpdateItem return_none");
376 UpdateItemRequest {
377 builder: self.builder,
378 _marker: PhantomData,
379 }
380 }
381}
382
383// -- Condition (NoCondition only) -------------------------------------------
384
385impl<TD: TableDefinition, T, O: OutputFormat, R: ReturnValue>
386 UpdateItemRequest<TD, T, O, R, NoCondition>
387{
388 /// Adds a condition expression that must be satisfied for the update to succeed.
389 ///
390 /// DynamoDB accepts a single condition expression per request, so this
391 /// method can only be called once. If the condition fails at runtime,
392 /// DynamoDB returns a `ConditionalCheckFailedException`.
393 ///
394 /// For the common item exists/not_exists cases, prefer
395 /// the [`.exists()`][UpdateItemRequest::exists] and
396 /// [`.not_exists()`][UpdateItemRequest::not_exists] shorthands.
397 ///
398 /// # Examples
399 ///
400 /// ```no_run
401 /// # use dynamodb_facade::test_fixtures::*;
402 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update, Condition};
403 ///
404 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
405 /// // Update role only if the current role is not "student"
406 /// User::update_by_id(
407 /// client,
408 /// KeyId::pk("user-1"),
409 /// Update::set("role", "instructor"),
410 /// )
411 /// .condition(Condition::ne("role", "student"))
412 /// .await?;
413 /// # Ok(())
414 /// # }
415 /// ```
416 pub fn condition(
417 mut self,
418 condition: Condition<'_>,
419 ) -> UpdateItemRequest<TD, T, O, R, AlreadyHasCondition> {
420 tracing::debug!(%condition, "UpdateItem condition");
421 self.builder = condition.apply(self.builder);
422 UpdateItemRequest {
423 builder: self.builder,
424 _marker: PhantomData,
425 }
426 }
427}
428
429impl<TD: TableDefinition, T: DynamoDBItem<TD>, O: OutputFormat, R: ReturnValue>
430 UpdateItemRequest<TD, T, O, R, NoCondition>
431{
432 /// Adds an `attribute_exists(<PK>)` condition, requiring the item to exist before updating.
433 ///
434 /// The update fails with `ConditionalCheckFailedException` if the item does not exist.
435 ///
436 /// # Examples
437 ///
438 /// ```no_run
439 /// # use dynamodb_facade::test_fixtures::*;
440 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
441 ///
442 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
443 /// User::update_by_id(
444 /// client,
445 /// KeyId::pk("user-1"),
446 /// Update::set("name", "Bob"),
447 /// )
448 /// .exists()
449 /// .await?;
450 /// # Ok(())
451 /// # }
452 /// ```
453 pub fn exists(self) -> UpdateItemRequest<TD, T, O, R, AlreadyHasCondition> {
454 self.condition(T::exists())
455 }
456
457 /// Adds an `attribute_not_exists(<PK>)` condition, requiring the item to not yet exist.
458 ///
459 /// Useful for upsert-style operations where you want to initialize an item only
460 /// if it does not already exist.
461 ///
462 /// # Examples
463 ///
464 /// ```no_run
465 /// # use dynamodb_facade::test_fixtures::*;
466 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
467 ///
468 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
469 /// User::update_by_id(
470 /// client,
471 /// KeyId::pk("user-1"),
472 /// Update::set("role", "student"),
473 /// )
474 /// .not_exists()
475 /// .await?;
476 /// # Ok(())
477 /// # }
478 /// ```
479 pub fn not_exists(self) -> UpdateItemRequest<TD, T, O, R, AlreadyHasCondition> {
480 self.condition(T::not_exists())
481 }
482}
483
484// -- Output format transition (preserve R, C) -------------------------------
485
486impl<TD: TableDefinition, T, R: ReturnValue, C: ConditionState>
487 UpdateItemRequest<TD, T, Typed, R, C>
488{
489 /// Switches the output format from `Typed` to `Raw`.
490 ///
491 /// After calling `.raw()`, [`execute`][UpdateItemRequest::execute] returns
492 /// [`Item<TD>`] instead of `T` when a return value is requested.
493 /// This transition is one-way.
494 ///
495 /// # Examples
496 ///
497 /// ```no_run
498 /// # use dynamodb_facade::test_fixtures::*;
499 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
500 ///
501 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
502 /// let raw_new = User::update_by_id(
503 /// client,
504 /// KeyId::pk("user-1"),
505 /// Update::set("role", "instructor"),
506 /// )
507 /// .exists()
508 /// .raw()
509 /// .await?;
510 /// // raw_new: Item<PlatformTable>
511 /// # Ok(())
512 /// # }
513 /// ```
514 pub fn raw(self) -> UpdateItemRequest<TD, T, Raw, R, C> {
515 UpdateItemRequest {
516 builder: self.builder,
517 _marker: PhantomData,
518 }
519 }
520}
521
522// -- Terminal: ReturnNothing (any O, any C) ---------------------------------
523
524impl<TD: TableDefinition, T, O: OutputFormat, C: ConditionState>
525 UpdateItemRequest<TD, T, O, ReturnNothing, C>
526{
527 /// Sends the `UpdateItem` request, returning nothing on success.
528 ///
529 /// This method is also available implicitly via `.await`.
530 ///
531 /// # Errors
532 ///
533 /// Returns [`Err`] if the DynamoDB request fails or if a condition
534 /// expression is set and the check fails
535 /// (`ConditionalCheckFailedException`).
536 ///
537 /// # Examples
538 ///
539 /// ```no_run
540 /// # use dynamodb_facade::test_fixtures::*;
541 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
542 ///
543 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
544 /// User::update_by_id(
545 /// client,
546 /// KeyId::pk("user-1"),
547 /// Update::set("name", "Bob"),
548 /// )
549 /// .exists()
550 /// .return_none()
551 /// .execute()
552 /// .await?;
553 /// # Ok(())
554 /// # }
555 /// ```
556 #[tracing::instrument(level = "debug", skip(self), name = "update_execute")]
557 pub fn execute(self) -> impl Future<Output = Result<()>> + Send + 'static {
558 let builder = self.builder;
559 async move {
560 builder.return_values(SDKReturnValue::None).send().await?;
561 Ok(())
562 }
563 }
564}
565
566impl<TD: TableDefinition, T, O: OutputFormat, C: ConditionState> IntoFuture
567 for UpdateItemRequest<TD, T, O, ReturnNothing, C>
568{
569 type Output = Result<()>;
570 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
571
572 fn into_future(self) -> Self::IntoFuture {
573 Box::pin(self.execute())
574 }
575}
576
577// -- Terminal: ReturnItem<Old> + Typed (any C) -------------------------------
578
579impl<TD: TableDefinition, T: DynamoDBItem<TD> + DeserializeOwned, C: ConditionState>
580 UpdateItemRequest<TD, T, Typed, Return<Old>, C>
581{
582 /// Sends the `UpdateItem` request and returns the pre-update item
583 /// deserialized as `Option<T>`.
584 ///
585 /// Returns `Some(T)` containing the item's state **before** the update
586 /// was applied, or `None` if no item existed at the target key prior to
587 /// the update (DynamoDB's `UpdateItem` is an upsert — it creates the
588 /// item if absent).
589 ///
590 /// This method is also available implicitly via `.await`.
591 ///
592 /// # Errors
593 ///
594 /// Returns [`Err`] if the DynamoDB request fails, if a condition check
595 /// fails, or if deserialization of the returned attributes fails.
596 ///
597 /// # Examples
598 ///
599 /// ```no_run
600 /// # use dynamodb_facade::test_fixtures::*;
601 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
602 ///
603 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
604 /// let before /* : Option<User> */ = User::update_by_id(
605 /// client,
606 /// KeyId::pk("user-1"),
607 /// Update::set("role", "instructor"),
608 /// )
609 /// .exists()
610 /// .return_old()
611 /// .execute()
612 /// .await?;
613 /// # Ok(())
614 /// # }
615 /// ```
616 #[tracing::instrument(level = "debug", skip(self), name = "update_execute_old")]
617 pub fn execute(self) -> impl Future<Output = Result<Option<T>>> + Send + 'static {
618 let builder = self.builder;
619 async move {
620 let out = builder.return_values(Old::return_value()).send().await?;
621
622 out.attributes
623 .map(Item::from_dynamodb_response)
624 .map(T::try_from_item)
625 .transpose()
626 }
627 }
628}
629
630impl<TD: TableDefinition, T: DynamoDBItem<TD> + DeserializeOwned, C: ConditionState> IntoFuture
631 for UpdateItemRequest<TD, T, Typed, Return<Old>, C>
632{
633 type Output = Result<Option<T>>;
634 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
635
636 fn into_future(self) -> Self::IntoFuture {
637 Box::pin(self.execute())
638 }
639}
640
641// -- Terminal: ReturnItem<New> + Typed (any C) -------------------------------
642
643impl<TD: TableDefinition, T: DynamoDBItem<TD> + DeserializeOwned, C: ConditionState>
644 UpdateItemRequest<TD, T, Typed, Return<New>, C>
645{
646 /// Sends the `UpdateItem` request and returns the post-update item
647 /// deserialized as `T`.
648 ///
649 /// Because DynamoDB's `ALL_NEW` return mode always includes the full
650 /// item after the update, this method returns `T` directly (not
651 /// `Option<T>`).
652 ///
653 /// This method is also available implicitly via `.await`.
654 ///
655 /// # Panics
656 ///
657 /// Panics if DynamoDB does not return attributes in the response. This
658 /// should not happen when `ALL_NEW` is requested, but could indicate a
659 /// bug in the SDK or an unexpected API change.
660 ///
661 /// # Errors
662 ///
663 /// Returns [`Err`] if the DynamoDB request fails, if a condition check
664 /// fails, or if deserialization of the returned attributes fails.
665 ///
666 /// # Examples
667 ///
668 /// ```no_run
669 /// # use dynamodb_facade::test_fixtures::*;
670 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
671 ///
672 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
673 /// let updated /* : User */ = User::update_by_id(
674 /// client,
675 /// KeyId::pk("user-1"),
676 /// Update::set("role", "instructor"),
677 /// )
678 /// .exists()
679 /// .execute()
680 /// .await?;
681 /// # Ok(())
682 /// # }
683 /// ```
684 #[tracing::instrument(level = "debug", skip(self), name = "update_execute_new")]
685 pub fn execute(self) -> impl Future<Output = Result<T>> + Send + 'static {
686 let builder = self.builder;
687 async move {
688 let out = builder.return_values(New::return_value()).send().await?;
689
690 out.attributes
691 .map(Item::from_dynamodb_response)
692 .map(T::try_from_item)
693 .expect("asked to return something")
694 }
695 }
696}
697
698impl<TD: TableDefinition, T: DynamoDBItem<TD> + DeserializeOwned, C: ConditionState> IntoFuture
699 for UpdateItemRequest<TD, T, Typed, Return<New>, C>
700{
701 type Output = Result<T>;
702 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
703
704 fn into_future(self) -> Self::IntoFuture {
705 Box::pin(self.execute())
706 }
707}
708
709// -- Terminal: ReturnItem<Old> + Raw (any C) ---------------------------------
710
711impl<TD: TableDefinition, T, C: ConditionState> UpdateItemRequest<TD, T, Raw, Return<Old>, C> {
712 /// Sends the `UpdateItem` request and returns the pre-update raw item
713 /// map as `Option<Item<TD>>`.
714 ///
715 /// Returns `Some(Item<TD>)` containing the item's state **before** the
716 /// update was applied, or `None` if no item existed at the target key
717 /// prior to the update (DynamoDB's `UpdateItem` is an upsert — it
718 /// creates the item if absent).
719 ///
720 /// This method is also available implicitly via `.await`.
721 ///
722 /// # Errors
723 ///
724 /// Returns [`Err`] if the DynamoDB request fails or if a condition check
725 /// fails.
726 ///
727 /// # Examples
728 ///
729 /// ```no_run
730 /// # use dynamodb_facade::test_fixtures::*;
731 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
732 ///
733 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
734 /// let raw /* : Option<dynamodb_facade::Item<PlatformTable>> */ = User::update_by_id(
735 /// client,
736 /// KeyId::pk("user-1"),
737 /// Update::set("role", "instructor"),
738 /// )
739 /// .exists()
740 /// .return_old()
741 /// .raw()
742 /// .execute()
743 /// .await?;
744 /// # Ok(())
745 /// # }
746 /// ```
747 #[tracing::instrument(level = "debug", skip(self), name = "update_execute_old_raw")]
748 pub fn execute(self) -> impl Future<Output = Result<Option<Item<TD>>>> + Send + 'static {
749 let builder = self.builder;
750 async move {
751 let out = builder.return_values(Old::return_value()).send().await?;
752
753 Ok(out.attributes.map(Item::from_dynamodb_response))
754 }
755 }
756}
757
758impl<TD: TableDefinition, T, C: ConditionState> IntoFuture
759 for UpdateItemRequest<TD, T, Raw, Return<Old>, C>
760{
761 type Output = Result<Option<Item<TD>>>;
762 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
763
764 fn into_future(self) -> Self::IntoFuture {
765 Box::pin(self.execute())
766 }
767}
768
769// -- Terminal: ReturnItem<New> + Raw (any C) ---------------------------------
770
771impl<TD: TableDefinition, T, C: ConditionState> UpdateItemRequest<TD, T, Raw, Return<New>, C> {
772 /// Sends the `UpdateItem` request and returns the post-update raw item
773 /// map as `Item<TD>`.
774 ///
775 /// Because DynamoDB's `ALL_NEW` return mode always includes the full
776 /// item after the update, this method returns `Item<TD>` directly (not
777 /// `Option<Item<TD>>`).
778 ///
779 /// This method is also available implicitly via `.await`.
780 ///
781 /// # Panics
782 ///
783 /// Panics if DynamoDB does not return attributes in the response. This
784 /// should not happen when `ALL_NEW` is requested, but could indicate a
785 /// bug in the SDK or an unexpected API change.
786 ///
787 /// # Errors
788 ///
789 /// Returns [`Err`] if the DynamoDB request fails or if a condition check
790 /// fails.
791 ///
792 /// # Examples
793 ///
794 /// ```no_run
795 /// # use dynamodb_facade::test_fixtures::*;
796 /// use dynamodb_facade::{DynamoDBItemOp, KeyId, Update};
797 ///
798 /// # async fn example(client: aws_sdk_dynamodb::Client) -> dynamodb_facade::Result<()> {
799 /// let raw /* : dynamodb_facade::Item<PlatformTable> */ = User::update_by_id(
800 /// client,
801 /// KeyId::pk("user-1"),
802 /// Update::set("role", "instructor"),
803 /// )
804 /// .exists()
805 /// .raw()
806 /// .execute()
807 /// .await?;
808 /// assert!(raw.get("role").is_some());
809 /// # Ok(())
810 /// # }
811 /// ```
812 #[tracing::instrument(level = "debug", skip(self), name = "update_execute_new_raw")]
813 pub fn execute(self) -> impl Future<Output = Result<Item<TD>>> + Send + 'static {
814 let builder = self.builder;
815 async move {
816 let out = builder.return_values(New::return_value()).send().await?;
817
818 Ok(out
819 .attributes
820 .map(Item::from_dynamodb_response)
821 .expect("asked to return something"))
822 }
823 }
824}
825
826impl<TD: TableDefinition, T, C: ConditionState> IntoFuture
827 for UpdateItemRequest<TD, T, Raw, Return<New>, C>
828{
829 type Output = Result<Item<TD>>;
830 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
831
832 fn into_future(self) -> Self::IntoFuture {
833 Box::pin(self.execute())
834 }
835}