Skip to main content

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    #[rustversion::attr(before(1.81), allow(clippy::should_implement_trait))]
147    #[rustversion::attr(
148        since(1.81),
149        expect(
150            clippy::should_implement_trait,
151            reason = "This is for the DynamoDB `NOT` operator, not the Rust `!` operator."
152        )
153    )]
154    pub fn not(self) -> Self {
155        Self::Not(Not::from(self))
156    }
157
158    /// Wraps a condition in [parentheses][1].
159    ///
160    /// See also: [`Parenthetical`]
161    ///
162    /// ```
163    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
164    /// use dynamodb_expression::Path;
165    /// # use pretty_assertions::assert_eq;
166    ///
167    /// let a = "a".parse::<Path>()?;
168    /// let b = "b".parse::<Path>()?;
169    /// let c = "c".parse::<Path>()?;
170    /// let d = "d".parse::<Path>()?;
171    ///
172    /// let condition = a.greater_than(b).parenthesize().and(c.less_than(d).parenthesize());
173    /// assert_eq!("(a > b) AND (c < d)", condition.to_string());
174    /// #
175    /// # Ok(())
176    /// # }
177    /// ```
178    ///
179    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Parentheses
180    pub fn parenthesize(self) -> Self {
181        Self::Parenthetical(Parenthetical::from(self))
182    }
183}
184
185impl ops::Not for Condition {
186    type Output = Condition;
187
188    /// A [DynamoDB logical `NOT`][1] condition.
189    ///
190    /// See also: [`Condition::not`], [`Not`]
191    ///
192    /// ```
193    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
194    /// use dynamodb_expression::Path;
195    /// # use pretty_assertions::assert_eq;
196    ///
197    /// let a = "a".parse::<Path>()?;
198    /// let b = "b".parse::<Path>()?;
199    ///
200    /// let condition = !a.greater_than(b);
201    /// assert_eq!("NOT a > b", condition.to_string());
202    /// #
203    /// # Ok(())
204    /// # }
205    /// ```
206    ///
207    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.LogicalEvaluations
208    fn not(self) -> Self::Output {
209        Condition::not(self)
210    }
211}
212
213impl fmt::Display for Condition {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        match self {
216            Condition::AttributeExists(condition) => condition.fmt(f),
217            Condition::AttributeNotExists(condition) => condition.fmt(f),
218            Condition::AttributeType(condition) => condition.fmt(f),
219            Condition::BeginsWith(condition) => condition.fmt(f),
220            Condition::Between(condition) => condition.fmt(f),
221            Condition::Contains(condition) => condition.fmt(f),
222            Condition::In(condition) => condition.fmt(f),
223            Condition::Not(condition) => condition.fmt(f),
224            Condition::And(condition) => condition.fmt(f),
225            Condition::Or(condition) => condition.fmt(f),
226            Condition::Comparison(condition) => condition.fmt(f),
227            Condition::Parenthetical(condition) => condition.fmt(f),
228        }
229    }
230}
231
232impl From<AttributeExists> for Condition {
233    fn from(condition: AttributeExists) -> Self {
234        Self::AttributeExists(condition)
235    }
236}
237
238impl From<AttributeNotExists> for Condition {
239    fn from(condition: AttributeNotExists) -> Self {
240        Self::AttributeNotExists(condition)
241    }
242}
243
244impl From<AttributeType> for Condition {
245    fn from(condition: AttributeType) -> Self {
246        Self::AttributeType(condition)
247    }
248}
249
250impl From<BeginsWith> for Condition {
251    fn from(condition: BeginsWith) -> Self {
252        Self::BeginsWith(condition)
253    }
254}
255
256impl From<Between> for Condition {
257    fn from(condition: Between) -> Self {
258        Self::Between(condition)
259    }
260}
261
262impl From<Contains> for Condition {
263    fn from(condition: Contains) -> Self {
264        Self::Contains(condition)
265    }
266}
267
268impl From<In> for Condition {
269    fn from(condition: In) -> Self {
270        Self::In(condition)
271    }
272}
273
274impl From<Not> for Condition {
275    fn from(condition: Not) -> Self {
276        Self::Not(condition)
277    }
278}
279
280impl From<And> for Condition {
281    fn from(condition: And) -> Self {
282        Self::And(condition)
283    }
284}
285
286impl From<Or> for Condition {
287    fn from(condition: Or) -> Self {
288        Self::Or(condition)
289    }
290}
291
292impl From<Comparison> for Condition {
293    fn from(condition: Comparison) -> Self {
294        Self::Comparison(condition)
295    }
296}
297
298impl From<Parenthetical> for Condition {
299    fn from(condition: Parenthetical) -> Self {
300        Self::Parenthetical(condition)
301    }
302}
303
304// As of v0.29, `aws_sdk_dynamodb` wants an `Into<String>` to be passed to the
305// `.filter_expression()` methods on its `*Input` types. So, we'll implement
306// that to make this nicer to work with.
307impl From<Condition> for String {
308    fn from(condition: Condition) -> Self {
309        // TODO: Is there a more efficient way when all of these require formatting?
310        condition.to_string()
311    }
312}
313
314#[cfg(test)]
315pub(crate) mod test {
316    use pretty_assertions::assert_eq;
317
318    use crate::path::Path;
319
320    use super::{
321        comparison::{greater_than, less_than},
322        Condition,
323    };
324
325    /// `a > b`
326    pub fn cmp_a_gt_b() -> Condition {
327        Condition::Comparison(greater_than(
328            "a".parse::<Path>().unwrap(),
329            "b".parse::<Path>().unwrap(),
330        ))
331    }
332
333    /// `c < d`
334    pub fn cmp_c_lt_d() -> Condition {
335        Condition::Comparison(less_than(
336            "c".parse::<Path>().unwrap(),
337            "d".parse::<Path>().unwrap(),
338        ))
339    }
340
341    #[test]
342    fn display() {
343        assert_eq!("a > b", cmp_a_gt_b().to_string());
344        assert_eq!("c < d", cmp_c_lt_d().to_string());
345    }
346
347    #[test]
348    fn and() {
349        use crate::Path;
350        use pretty_assertions::assert_eq;
351
352        let a = "a".parse::<Path>().unwrap();
353        let b = "b".parse::<Path>().unwrap();
354        let c = "c".parse::<Path>().unwrap();
355        let d = "d".parse::<Path>().unwrap();
356
357        let condition = a.greater_than(b).and(c.less_than(d));
358        assert_eq!("a > b AND c < d", condition.to_string());
359    }
360
361    #[test]
362    fn or() {
363        use crate::Path;
364        use pretty_assertions::assert_eq;
365
366        let a = "a".parse::<Path>().unwrap();
367        let b = "b".parse::<Path>().unwrap();
368        let c = "c".parse::<Path>().unwrap();
369        let d = "d".parse::<Path>().unwrap();
370
371        let condition = a.greater_than(b).or(c.less_than(d));
372        assert_eq!("a > b OR c < d", condition.to_string());
373    }
374
375    #[test]
376    fn not() {
377        use crate::Path;
378        use pretty_assertions::assert_eq;
379
380        let a = "a".parse::<Path>().unwrap();
381        let b = "b".parse::<Path>().unwrap();
382
383        let condition = a.greater_than(b).not();
384        assert_eq!("NOT a > b", condition.to_string());
385    }
386
387    #[test]
388    fn not_operator() {
389        use crate::Path;
390        use pretty_assertions::assert_eq;
391
392        let a = "a".parse::<Path>().unwrap();
393        let b = "b".parse::<Path>().unwrap();
394
395        let condition = !a.greater_than(b);
396        assert_eq!("NOT a > b", condition.to_string());
397    }
398}