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}