dynamodb_expression/update/set/if_not_exists.rs
1use core::fmt::{self, Write};
2
3use crate::{
4 path::Path,
5 update::Update,
6 value::{Value, ValueOrRef},
7};
8
9/// Represents an update expression to [set an attribute if it doesn't exist][1].
10///
11/// Prefer [`Path::if_not_exists`] over this.
12///
13/// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.PreventingAttributeOverwrites
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct IfNotExists {
16 pub(crate) dst: Path,
17 pub(crate) src: Option<Path>,
18 pub(crate) value: ValueOrRef,
19}
20
21impl IfNotExists {
22 /// Prefer [`Path::if_not_exists`] over this.
23 pub fn builder<T>(dst: T) -> Builder
24 where
25 T: Into<Path>,
26 {
27 Builder {
28 dst: dst.into(),
29 src: None,
30 }
31 }
32
33 /// Add an additional [`Update`] statement to this expression.
34 ///
35 /// ```
36 /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
37 /// use dynamodb_expression::{Num, Path, update::Set};
38 /// # use pretty_assertions::assert_eq;
39 ///
40 /// let set = "foo"
41 /// .parse::<Path>()?
42 /// .if_not_exists()
43 /// .set(Num::new(7))
44 /// .and("bar".parse::<Path>()?.set("a value"));
45 /// assert_eq!(r#"SET foo = if_not_exists(foo, 7), bar = "a value""#, set.to_string());
46 /// #
47 /// # Ok(())
48 /// # }
49 /// ```
50 pub fn and<T>(self, action: T) -> Update
51 where
52 T: Into<Update>,
53 {
54 Update::from(self).and(action)
55 }
56}
57
58impl fmt::Display for IfNotExists {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 self.dst.fmt(f)?;
61 f.write_str(" = if_not_exists(")?;
62 // If no source field is specified, default to using the destination.
63 self.src.as_ref().unwrap_or(&self.dst).fmt(f)?;
64 f.write_str(", ")?;
65 self.value.fmt(f)?;
66 f.write_char(')')
67 }
68}
69
70/// Builds an [`IfNotExists`] instance. Create an instance of this by using [`IfNotExists::builder`].
71///
72/// Prefer [`Path::if_not_exists`] over this.
73#[must_use = "Consume this `Builder` by using its `.value()` method"]
74#[derive(Debug, Clone)]
75pub struct Builder {
76 dst: Path,
77 src: Option<Path>,
78}
79
80impl Builder {
81 /// Sets the source [`Path`] to check for existence.
82 ///
83 /// Defaults to the destination [`Path`].
84 ///
85 /// ```
86 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
87 /// # use dynamodb_expression::{Num, Path};
88 /// # use pretty_assertions::assert_eq;
89 /// #
90 /// let if_not_exists = "foo"
91 /// .parse::<Path>()?
92 /// .if_not_exists()
93 /// .src("bar".parse::<Path>()?)
94 /// .set(Num::new(42));
95 /// assert_eq!("foo = if_not_exists(bar, 42)", if_not_exists.to_string());
96 /// #
97 /// # Ok(())
98 /// # }
99 /// ```
100 ///
101 /// Compare with the default, where the destination [`Path`] is used:
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 if_not_exists = "foo"
109 /// .parse::<Path>()?
110 /// .if_not_exists()
111 /// .set(Num::new(42));
112 /// assert_eq!("foo = if_not_exists(foo, 42)", if_not_exists.to_string());
113 /// #
114 /// # Ok(())
115 /// # }
116 /// ```
117 pub fn src<T>(mut self, src: T) -> Self
118 where
119 T: Into<Path>,
120 {
121 self.src = Some(src.into());
122
123 self
124 }
125
126 /// The value to conditionally set.
127 ///
128 /// Consumes this [`Builder`] and creates an [`IfNotExists`] instance.
129 ///
130 /// See also: [`Path::if_not_exists`]
131 #[deprecated(since = "0.2.0-beta.6", note = "Use `.set(value)` instead")]
132 pub fn assign<T>(self, value: T) -> IfNotExists
133 where
134 T: Into<Value>,
135 {
136 self.set(value)
137 }
138
139 /// The value to conditionally set.
140 ///
141 /// Consumes this [`Builder`] and creates an [`IfNotExists`] instance.
142 ///
143 /// See also: [`Path::if_not_exists`]
144 pub fn set<T>(self, value: T) -> IfNotExists
145 where
146 T: Into<Value>,
147 {
148 let Self { dst, src } = self;
149
150 IfNotExists {
151 dst,
152 src,
153 value: value.into().into(),
154 }
155 }
156}
157
158#[cfg(test)]
159mod test {
160 use std::error::Error;
161
162 use pretty_assertions::assert_eq;
163
164 use crate::{
165 update::{Assign, Set, SetAction},
166 Num, Path,
167 };
168
169 use super::IfNotExists;
170
171 #[test]
172 fn and() -> Result<(), Box<dyn Error>> {
173 let if_not_exists: IfNotExists = "foo".parse::<Path>()?.if_not_exists().set("a value");
174 let assign: Assign = "bar".parse::<Path>()?.set(Num::new(8));
175
176 // Should be able to concatenate anything that can be turned into a SetAction.
177
178 let combined = if_not_exists.clone().and(assign.clone());
179 assert_eq!(
180 r#"SET foo = if_not_exists(foo, "a value"), bar = 8"#,
181 combined.to_string()
182 );
183
184 // Should be able to concatenate a SetAction instance.
185
186 let combined = if_not_exists.clone().and(SetAction::from(assign.clone()));
187 assert_eq!(
188 r#"SET foo = if_not_exists(foo, "a value"), bar = 8"#,
189 combined.to_string()
190 );
191
192 // Should be able to concatenate a Set instance
193
194 let set: Set = [
195 SetAction::from(assign),
196 SetAction::from("baz".parse::<Path>()?.math().add(1)),
197 ]
198 .into_iter()
199 .collect();
200 let combined = if_not_exists.clone().and(set);
201 assert_eq!(
202 r#"SET foo = if_not_exists(foo, "a value"), bar = 8, baz = baz + 1"#,
203 combined.to_string()
204 );
205
206 // Should be able to concatenate a Remove instance
207
208 let combined = if_not_exists.clone().and("quux".parse::<Path>()?.remove());
209 assert_eq!(
210 r#"SET foo = if_not_exists(foo, "a value") REMOVE quux"#,
211 combined.to_string()
212 );
213
214 // Should be able to concatenate a SetRemove instance
215
216 let combined = if_not_exists.and("quux".parse::<Path>()?.remove());
217 assert_eq!(
218 r#"SET foo = if_not_exists(foo, "a value") REMOVE quux"#,
219 combined.to_string()
220 );
221
222 Ok(())
223 }
224}