dynamodb_expression/condition/
mod.rs

1//! Types related to conditions for [DynamoDB condition and filter expressions][1].
2//!
3//! [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html
4
5mod and;
6mod attribute_exists;
7mod attribute_not_exists;
8pub mod attribute_type;
9mod begins_with;
10mod between;
11mod comparison;
12mod contains;
13mod in_;
14mod not;
15mod or;
16mod parenthetical;
17
18pub use and::And;
19pub use attribute_exists::AttributeExists;
20pub use attribute_not_exists::AttributeNotExists;
21pub use attribute_type::AttributeType;
22pub use begins_with::BeginsWith;
23pub use between::Between;
24pub use comparison::{
25    equal, greater_than, greater_than_or_equal, less_than, less_than_or_equal, not_equal,
26    Comparator, Comparison,
27};
28pub use contains::Contains;
29pub use in_::In;
30pub use not::Not;
31pub use or::Or;
32pub use parenthetical::Parenthetical;
33
34use core::{fmt, ops};
35
36/// Represents a logical condition in a [DynamoDB expression][1].
37///
38/// You will usually create these using the methods on [`Path`].
39///
40/// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Syntax
41/// [`Path`]: crate::path::Path
42#[must_use = "Use in a DynamoDB expression with \
43    `Expression::builder().with_condition(condition)` or \
44    `Expression::builder().with_filter(condition)`"]
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum Condition {
47    AttributeExists(AttributeExists),
48    AttributeNotExists(AttributeNotExists),
49    AttributeType(AttributeType),
50    BeginsWith(BeginsWith),
51    Between(Between),
52    Contains(Contains),
53    In(In),
54    Not(Not),
55    And(And),
56    Or(Or),
57    Comparison(Comparison),
58    Parenthetical(Parenthetical),
59}
60
61impl Condition {
62    /// A [DynamoDB logical `AND`][1] condition.
63    ///
64    /// See also: [`And`]
65    ///
66    /// ```
67    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
68    /// use dynamodb_expression::Path;
69    /// # use pretty_assertions::assert_eq;
70    ///
71    /// let a = "a".parse::<Path>()?;
72    /// let b = "b".parse::<Path>()?;
73    /// let c = "c".parse::<Path>()?;
74    /// let d = "d".parse::<Path>()?;
75    ///
76    /// let condition = a.greater_than(b).and(c.less_than(d));
77    /// assert_eq!("a > b AND c < d", condition.to_string());
78    /// #
79    /// # Ok(())
80    /// # }
81    /// ```
82    ///
83    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.LogicalEvaluations
84    pub fn and<R>(self, right: R) -> Self
85    where
86        R: Into<Condition>,
87    {
88        Self::And(And {
89            left: self.into(),
90            right: right.into().into(),
91        })
92    }
93
94    /// A [DynamoDB logical `OR`][1] condition.
95    ///
96    /// See also: [`Or`]
97    ///
98    /// ```
99    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
100    /// use dynamodb_expression::Path;
101    /// # use pretty_assertions::assert_eq;
102    ///
103    /// let a = "a".parse::<Path>()?;
104    /// let b = "b".parse::<Path>()?;
105    /// let c = "c".parse::<Path>()?;
106    /// let d = "d".parse::<Path>()?;
107    ///
108    /// let condition = a.greater_than(b).or(c.less_than(d));
109    /// assert_eq!("a > b OR c < d", condition.to_string());
110    /// #
111    /// # Ok(())
112    /// # }
113    /// ```
114    ///
115    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.LogicalEvaluations
116    pub fn or<R>(self, right: R) -> Self
117    where
118        R: Into<Condition>,
119    {
120        Self::Or(Or {
121            left: self.into(),
122            right: right.into().into(),
123        })
124    }
125
126    /// A [DynamoDB logical `NOT`][1] condition.
127    ///
128    /// See also: [`Not`]
129    ///
130    /// ```
131    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
132    /// use dynamodb_expression::Path;
133    /// # use pretty_assertions::assert_eq;
134    ///
135    /// let a = "a".parse::<Path>()?;
136    /// let b = "b".parse::<Path>()?;
137    ///
138    /// let condition = a.greater_than(b).not();
139    /// assert_eq!("NOT a > b", condition.to_string());
140    /// #
141    /// # Ok(())
142    /// # }
143    /// ```
144    ///
145    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.LogicalEvaluations
146    #[allow(clippy::should_implement_trait)]
147    pub fn not(self) -> Self {
148        Self::Not(Not::from(self))
149    }
150
151    /// Wraps a condition in [parentheses][1].
152    ///
153    /// See also: [`Parenthetical`]
154    ///
155    /// ```
156    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
157    /// use dynamodb_expression::Path;
158    /// # use pretty_assertions::assert_eq;
159    ///
160    /// let a = "a".parse::<Path>()?;
161    /// let b = "b".parse::<Path>()?;
162    /// let c = "c".parse::<Path>()?;
163    /// let d = "d".parse::<Path>()?;
164    ///
165    /// let condition = a.greater_than(b).parenthesize().and(c.less_than(d).parenthesize());
166    /// assert_eq!("(a > b) AND (c < d)", condition.to_string());
167    /// #
168    /// # Ok(())
169    /// # }
170    /// ```
171    ///
172    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Parentheses
173    pub fn parenthesize(self) -> Self {
174        Self::Parenthetical(Parenthetical::from(self))
175    }
176}
177
178impl ops::Not for Condition {
179    type Output = Condition;
180
181    /// A [DynamoDB logical `NOT`][1] condition.
182    ///
183    /// See also: [`Condition::not`], [`Not`]
184    ///
185    /// ```
186    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
187    /// use dynamodb_expression::Path;
188    /// # use pretty_assertions::assert_eq;
189    ///
190    /// let a = "a".parse::<Path>()?;
191    /// let b = "b".parse::<Path>()?;
192    ///
193    /// let condition = !a.greater_than(b);
194    /// assert_eq!("NOT a > b", condition.to_string());
195    /// #
196    /// # Ok(())
197    /// # }
198    /// ```
199    ///
200    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.LogicalEvaluations
201    fn not(self) -> Self::Output {
202        Condition::not(self)
203    }
204}
205
206impl fmt::Display for Condition {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        match self {
209            Condition::AttributeExists(condition) => condition.fmt(f),
210            Condition::AttributeNotExists(condition) => condition.fmt(f),
211            Condition::AttributeType(condition) => condition.fmt(f),
212            Condition::BeginsWith(condition) => condition.fmt(f),
213            Condition::Between(condition) => condition.fmt(f),
214            Condition::Contains(condition) => condition.fmt(f),
215            Condition::In(condition) => condition.fmt(f),
216            Condition::Not(condition) => condition.fmt(f),
217            Condition::And(condition) => condition.fmt(f),
218            Condition::Or(condition) => condition.fmt(f),
219            Condition::Comparison(condition) => condition.fmt(f),
220            Condition::Parenthetical(condition) => condition.fmt(f),
221        }
222    }
223}
224
225impl From<AttributeExists> for Condition {
226    fn from(condition: AttributeExists) -> Self {
227        Self::AttributeExists(condition)
228    }
229}
230
231impl From<AttributeNotExists> for Condition {
232    fn from(condition: AttributeNotExists) -> Self {
233        Self::AttributeNotExists(condition)
234    }
235}
236
237impl From<AttributeType> for Condition {
238    fn from(condition: AttributeType) -> Self {
239        Self::AttributeType(condition)
240    }
241}
242
243impl From<BeginsWith> for Condition {
244    fn from(condition: BeginsWith) -> Self {
245        Self::BeginsWith(condition)
246    }
247}
248
249impl From<Between> for Condition {
250    fn from(condition: Between) -> Self {
251        Self::Between(condition)
252    }
253}
254
255impl From<Contains> for Condition {
256    fn from(condition: Contains) -> Self {
257        Self::Contains(condition)
258    }
259}
260
261impl From<In> for Condition {
262    fn from(condition: In) -> Self {
263        Self::In(condition)
264    }
265}
266
267impl From<Not> for Condition {
268    fn from(condition: Not) -> Self {
269        Self::Not(condition)
270    }
271}
272
273impl From<And> for Condition {
274    fn from(condition: And) -> Self {
275        Self::And(condition)
276    }
277}
278
279impl From<Or> for Condition {
280    fn from(condition: Or) -> Self {
281        Self::Or(condition)
282    }
283}
284
285impl From<Comparison> for Condition {
286    fn from(condition: Comparison) -> Self {
287        Self::Comparison(condition)
288    }
289}
290
291impl From<Parenthetical> for Condition {
292    fn from(condition: Parenthetical) -> Self {
293        Self::Parenthetical(condition)
294    }
295}
296
297// As of v0.29, `aws_sdk_dynamodb` wants an `Into<String>` to be passed to the
298// `.filter_expression()` methods on its `*Input` types. So, we'll implement
299// that to make this nicer to work with.
300impl From<Condition> for String {
301    fn from(condition: Condition) -> Self {
302        // TODO: Is there a more efficient way when all of these require formatting?
303        condition.to_string()
304    }
305}
306
307#[cfg(test)]
308pub(crate) mod test {
309    use pretty_assertions::assert_eq;
310
311    use crate::path::Path;
312
313    use super::{
314        comparison::{greater_than, less_than},
315        Condition,
316    };
317
318    /// `a > b`
319    pub fn cmp_a_gt_b() -> Condition {
320        Condition::Comparison(greater_than(
321            "a".parse::<Path>().unwrap(),
322            "b".parse::<Path>().unwrap(),
323        ))
324    }
325
326    /// `c < d`
327    pub fn cmp_c_lt_d() -> Condition {
328        Condition::Comparison(less_than(
329            "c".parse::<Path>().unwrap(),
330            "d".parse::<Path>().unwrap(),
331        ))
332    }
333
334    #[test]
335    fn display() {
336        assert_eq!("a > b", cmp_a_gt_b().to_string());
337        assert_eq!("c < d", cmp_c_lt_d().to_string());
338    }
339
340    #[test]
341    fn and() {
342        use crate::Path;
343        use pretty_assertions::assert_eq;
344
345        let a = "a".parse::<Path>().unwrap();
346        let b = "b".parse::<Path>().unwrap();
347        let c = "c".parse::<Path>().unwrap();
348        let d = "d".parse::<Path>().unwrap();
349
350        let condition = a.greater_than(b).and(c.less_than(d));
351        assert_eq!("a > b AND c < d", condition.to_string());
352    }
353
354    #[test]
355    fn or() {
356        use crate::Path;
357        use pretty_assertions::assert_eq;
358
359        let a = "a".parse::<Path>().unwrap();
360        let b = "b".parse::<Path>().unwrap();
361        let c = "c".parse::<Path>().unwrap();
362        let d = "d".parse::<Path>().unwrap();
363
364        let condition = a.greater_than(b).or(c.less_than(d));
365        assert_eq!("a > b OR c < d", condition.to_string());
366    }
367
368    #[test]
369    fn not() {
370        use crate::Path;
371        use pretty_assertions::assert_eq;
372
373        let a = "a".parse::<Path>().unwrap();
374        let b = "b".parse::<Path>().unwrap();
375
376        let condition = a.greater_than(b).not();
377        assert_eq!("NOT a > b", condition.to_string());
378    }
379
380    #[test]
381    fn not_operator() {
382        use crate::Path;
383        use pretty_assertions::assert_eq;
384
385        let a = "a".parse::<Path>().unwrap();
386        let b = "b".parse::<Path>().unwrap();
387
388        let condition = !a.greater_than(b);
389        assert_eq!("NOT a > b", condition.to_string());
390    }
391}