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}