dynamodb_expression/update/set/
list_append.rs

1use core::fmt::{self, Write};
2
3use crate::{
4    path::Path,
5    update::Update,
6    value::{List, ValueOrRef},
7};
8
9/// Represents an update expression to [append elements to a list][1].
10///
11/// Prefer [`Path::list_append`] over this.
12///
13/// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.UpdatingListElements
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct ListAppend {
16    /// The field to set the newly combined list to
17    pub(crate) dst: Path,
18
19    /// The field to get the current list from
20    pub(crate) src: Option<Path>,
21
22    /// The value(s) to add to the list
23    pub(crate) list: ValueOrRef,
24
25    /// Whether to add the new values to the beginning or end of the source list
26    after: bool,
27}
28
29impl ListAppend {
30    pub fn builder<T>(dst: T) -> Builder
31    where
32        T: Into<Path>,
33    {
34        Builder {
35            dst: dst.into(),
36            src: None,
37            after: true,
38        }
39    }
40
41    /// Add an additional [`Update`] statement to this expression.
42    ///
43    /// ```
44    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
45    /// use dynamodb_expression::{Num, Path};
46    /// # use pretty_assertions::assert_eq;
47    ///
48    /// let set = "foo"
49    ///     .parse::<Path>()?
50    ///     .list_append()
51    ///     .list([7, 8, 9].map(Num::new))
52    ///     .and("bar".parse::<Path>()?.set("a value"));
53    /// assert_eq!(r#"SET foo = list_append(foo, [7, 8, 9]), bar = "a value""#, set.to_string());
54    /// #
55    /// # Ok(())
56    /// # }
57    /// ```
58    pub fn and<T>(self, other: T) -> Update
59    where
60        T: Into<Update>,
61    {
62        Update::from(self).and(other)
63    }
64}
65
66impl fmt::Display for ListAppend {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        self.dst.fmt(f)?;
69        f.write_str(" = list_append(")?;
70
71        // If no source field is specified, default to using the destination.
72        let src = self.src.as_ref().unwrap_or(&self.dst);
73
74        let (first, second): (&dyn fmt::Display, &dyn fmt::Display) = if self.after {
75            (src, &self.list)
76        } else {
77            (&self.list, src)
78        };
79
80        first.fmt(f)?;
81        f.write_str(", ")?;
82        second.fmt(f)?;
83        f.write_char(')')
84    }
85}
86
87/// Builds an [`ListAppend`] instance.
88///
89/// Prefer [`Path::list_append`] over this.
90#[must_use = "Consume this `Builder` by using its `.list()` method"]
91#[derive(Debug, Clone)]
92pub struct Builder {
93    dst: Path,
94    src: Option<Path>,
95    after: bool,
96}
97
98impl Builder {
99    /// Sets the source [`Path`] to read the initial list from.
100    ///
101    /// Defaults to the [`Path`] the combined list is being assigned to.
102    ///
103    /// ```
104    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
105    /// # use dynamodb_expression::{Num, Path};
106    /// # use pretty_assertions::assert_eq;
107    /// #
108    /// let list_append = "foo"
109    ///     .parse::<Path>()?
110    ///     .list_append()
111    ///     .src("bar".parse::<Path>()?)
112    ///     .list([1, 2, 3].map(Num::new));
113    /// assert_eq!("foo = list_append(bar, [1, 2, 3])", list_append.to_string());
114    /// #
115    /// # Ok(())
116    /// # }
117    /// ```
118    ///
119    /// Compare with what happens without specifying a source [`Path`]:
120    ///
121    /// ```
122    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
123    /// # use dynamodb_expression::{Num, Path};
124    /// # use pretty_assertions::assert_eq;
125    /// #
126    /// let list_append = "foo"
127    ///     .parse::<Path>()?
128    ///     .list_append()
129    ///     .list([1, 2, 3].map(Num::new));
130    /// assert_eq!("foo = list_append(foo, [1, 2, 3])", list_append.to_string());
131    /// #
132    /// # Ok(())
133    /// # }
134    /// ```
135    pub fn src<T>(mut self, src: T) -> Self
136    where
137        T: Into<Path>,
138    {
139        self.src = Some(src.into());
140
141        self
142    }
143
144    /// The new values will be appended to the end of the existing values.
145    ///
146    /// This is the default.
147    ///
148    /// ```
149    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
150    /// # use dynamodb_expression::{Num, Path};
151    /// # use pretty_assertions::assert_eq;
152    /// #
153    /// let list_append = "foo".parse::<Path>()?.list_append().after().list([1, 2, 3].map(Num::new));
154    /// assert_eq!("foo = list_append(foo, [1, 2, 3])", list_append.to_string());
155    /// #
156    /// # Ok(())
157    /// # }
158    /// ```
159    ///
160    /// Compare with when [`before`] is used:
161    ///
162    /// ```
163    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
164    /// # use dynamodb_expression::{Num, Path};
165    /// # use pretty_assertions::assert_eq;
166    /// #
167    /// let list_append = "foo".parse::<Path>()?.list_append().before().list([1, 2, 3].map(Num::new));
168    /// assert_eq!("foo = list_append([1, 2, 3], foo)", list_append.to_string());
169    /// #
170    /// # Ok(())
171    /// # }
172    /// ```
173    ///
174    /// The default, with the same behavior as `after`:
175    ///
176    /// ```
177    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
178    /// # use dynamodb_expression::{Num, Path};
179    /// # use pretty_assertions::assert_eq;
180    /// #
181    /// let list_append = "foo".parse::<Path>()?.list_append().list([1, 2, 3].map(Num::new));
182    /// assert_eq!("foo = list_append(foo, [1, 2, 3])", list_append.to_string());
183    /// #
184    /// # Ok(())
185    /// # }
186    /// ```
187    ///
188    /// [`before`]: crate::update::set::list_append::Builder::before
189    pub fn after(mut self) -> Self {
190        self.after = true;
191
192        self
193    }
194
195    /// The new values will be placed before the existing values.
196    ///
197    /// Defaults to appending new values [`after`] existing values.
198    ///
199    /// ```
200    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
201    /// # use dynamodb_expression::{Num, Path};
202    /// # use pretty_assertions::assert_eq;
203    /// #
204    /// let list_append = "foo".parse::<Path>()?.list_append().before().list([1, 2, 3].map(Num::new));
205    /// assert_eq!("foo = list_append([1, 2, 3], foo)", list_append.to_string());
206    /// #
207    /// # Ok(())
208    /// # }
209    /// ```
210    ///
211    /// Compare with when [`after`] is used:
212    ///
213    /// ```
214    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
215    /// # use dynamodb_expression::{Num, Path};
216    /// # use pretty_assertions::assert_eq;
217    /// #
218    /// let list_append = "foo".parse::<Path>()?.list_append().after().list([1, 2, 3].map(Num::new));
219    /// assert_eq!("foo = list_append(foo, [1, 2, 3])", list_append.to_string());
220    /// #
221    /// # Ok(())
222    /// # }
223    /// ```
224    ///
225    /// The default, with the same behavior as [`after`]:
226    ///
227    /// ```
228    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
229    /// # use dynamodb_expression::{Num, Path};
230    /// # use pretty_assertions::assert_eq;
231    /// #
232    /// let list_append = "foo".parse::<Path>()?.list_append().list([1, 2, 3].map(Num::new));
233    /// assert_eq!("foo = list_append(foo, [1, 2, 3])", list_append.to_string());
234    /// #
235    /// # Ok(())
236    /// # }
237    /// ```
238    ///
239    /// [`after`]: crate::update::set::list_append::Builder::after
240    pub fn before(mut self) -> Self {
241        self.after = false;
242
243        self
244    }
245
246    /// Sets the new value(s) to concatenate with the specified field.
247    ///
248    /// Consumes this [`Builder`] and creates a [`ListAppend`] instance.
249    pub fn list<T>(self, list: T) -> ListAppend
250    where
251        T: Into<List>,
252    {
253        let Self { dst, src, after } = self;
254
255        ListAppend {
256            dst,
257            src,
258            after,
259            list: list.into().into(),
260        }
261    }
262}
263
264#[cfg(test)]
265mod test {
266    use pretty_assertions::assert_eq;
267
268    use crate::{
269        update::{Assign, Set, SetAction},
270        Num, Path,
271    };
272
273    use super::ListAppend;
274
275    #[test]
276    fn display() -> Result<(), Box<dyn std::error::Error>> {
277        let append = ListAppend::builder("foo".parse::<Path>()?)
278            .src("bar".parse::<Path>()?)
279            .after()
280            .list(["a", "b"]);
281        assert_eq!(r#"foo = list_append(bar, ["a", "b"])"#, append.to_string());
282
283        let append = ListAppend::builder("foo".parse::<Path>()?)
284            .src("bar".parse::<Path>()?)
285            .list(["a", "b"]);
286        assert_eq!(r#"foo = list_append(bar, ["a", "b"])"#, append.to_string());
287
288        let append = ListAppend::builder("foo".parse::<Path>()?)
289            .src("bar".parse::<Path>()?)
290            .before()
291            .list(["a", "b"]);
292        assert_eq!(r#"foo = list_append(["a", "b"], bar)"#, append.to_string());
293
294        let append = ListAppend::builder("foo".parse::<Path>()?)
295            .after()
296            .list(["a", "b"]);
297        assert_eq!(r#"foo = list_append(foo, ["a", "b"])"#, append.to_string());
298
299        let append = ListAppend::builder("foo".parse::<Path>()?).list(["a", "b"]);
300        assert_eq!(r#"foo = list_append(foo, ["a", "b"])"#, append.to_string());
301
302        let append = ListAppend::builder("foo".parse::<Path>()?)
303            .before()
304            .list(["a", "b"]);
305        assert_eq!(r#"foo = list_append(["a", "b"], foo)"#, append.to_string());
306
307        Ok(())
308    }
309
310    #[test]
311    fn and() -> Result<(), Box<dyn std::error::Error>> {
312        let list_append = "foo".parse::<Path>()?.list_append().list(["d", "e", "f"]);
313        let assign: Assign = "bar".parse::<Path>()?.set(Num::new(8));
314
315        // Should be able to concatenate anything that can be turned into a SetAction.
316
317        let combined = list_append.clone().and(assign.clone());
318        assert_eq!(
319            r#"SET foo = list_append(foo, ["d", "e", "f"]), bar = 8"#,
320            combined.to_string()
321        );
322
323        // Should be able to concatenate a SetAction instance.
324
325        let combined = list_append.clone().and(SetAction::from(assign.clone()));
326        assert_eq!(
327            r#"SET foo = list_append(foo, ["d", "e", "f"]), bar = 8"#,
328            combined.to_string()
329        );
330
331        // Should be able to concatenate a Set instance
332
333        let set: Set = [
334            SetAction::from(assign),
335            SetAction::from("baz".parse::<Path>()?.math().add(1)),
336        ]
337        .into_iter()
338        .collect();
339        let combined = list_append.clone().and(set);
340        assert_eq!(
341            r#"SET foo = list_append(foo, ["d", "e", "f"]), bar = 8, baz = baz + 1"#,
342            combined.to_string()
343        );
344
345        // Should be able to concatenate a Remove instance
346
347        let combined = list_append.clone().and("quux".parse::<Path>()?.remove());
348        assert_eq!(
349            r#"SET foo = list_append(foo, ["d", "e", "f"]) REMOVE quux"#,
350            combined.to_string()
351        );
352
353        // Should be able to concatenate a SetRemove instance
354
355        let combined = list_append.and("quux".parse::<Path>()?.remove());
356        assert_eq!(
357            r#"SET foo = list_append(foo, ["d", "e", "f"]) REMOVE quux"#,
358            combined.to_string()
359        );
360
361        Ok(())
362    }
363}