dynamodb_expression/condition/
not.rs

1use core::fmt;
2
3use crate::condition::Condition;
4
5/// Represents a [DynamoDB logical `NOT`][1] condition.
6///
7/// See also: [`Condition::not`]
8///
9/// ```
10/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
11/// use dynamodb_expression::Path;
12/// # use pretty_assertions::assert_eq;
13///
14/// let a = "a".parse::<Path>()?;
15/// let b = "b".parse::<Path>()?;
16///
17/// let condition = a.greater_than(b).not();
18/// assert_eq!("NOT a > b", condition.to_string());
19/// #
20/// # Ok(())
21/// # }
22/// ```
23///
24/// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.LogicalEvaluations
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct Not {
27    pub(crate) condition: Box<Condition>,
28}
29
30impl Not {
31    // /// Normalizes pairs of `NOT` statements by removing them. E.g.,
32    // /// `NOT NOT a < b` becomes `a < b`.
33    // /// `NOT (NOT a < b)` becomes `a < b`.
34    // pub fn normalize(self) -> Expression {
35    //     // `NOT inner`
36
37    //     if let Expression::Logical(Logical::Not(Self(inner))) = *self.0 {
38    //         // `NOT NOT inner`
39    //         inner.normalize()
40    //     } else if let Expression::Parenthetical(parens) = *self.0 {
41    //         // `NOT (inner)`
42
43    //         // Flatten nested paren statements to turn `NOT (((inner)))` into `NOT (inner)`
44    //         let Parenthetical(inner) = parens.flatten();
45
46    //         if let Expression::Logical(Logical::Not(Self(inner))) = *inner {
47    //             // `NOT (NOT inner)`
48    //             inner.normalize()
49    //         } else {
50    //             // `NOT (inner)
51    //             //
52    //             // Put it back in the parentheses.
53    //             let inner = inner.normalize().parenthesize();
54
55    //             // Put it back in `NOT`
56    //             Self::from(inner).into()
57    //         }
58    //     } else {
59    //         Expression::Logical(Logical::Not(self))
60    //     }
61    // }
62}
63
64impl<T> From<T> for Not
65where
66    T: Into<Box<Condition>>,
67{
68    fn from(condition: T) -> Self {
69        Self {
70            condition: condition.into(),
71        }
72    }
73}
74
75impl fmt::Display for Not {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.write_str("NOT ")?;
78        self.condition.fmt(f)
79    }
80}
81
82#[cfg(test)]
83mod test {
84    use std::io::{self, Write};
85
86    use pretty_assertions::assert_str_eq;
87
88    use crate::condition::{test::cmp_a_gt_b, Condition};
89
90    use super::Not;
91
92    #[test]
93    fn display() {
94        assert_str_eq!("NOT a > b", (!cmp_a_gt_b()).to_string());
95    }
96
97    #[test]
98    fn not_expression() {
99        let expr = cmp_a_gt_b();
100
101        for i in 0..3 {
102            let mut wrapped = !expr.clone();
103            for _ in 0..i {
104                wrapped = !wrapped;
105            }
106
107            print!("{i}: {wrapped}");
108            io::stdout().lock().flush().unwrap();
109
110            assert_str_eq!(
111                match i {
112                    0 => format!("NOT {expr}"),
113                    1 => format!("NOT NOT {expr}"),
114                    2 => format!("NOT NOT NOT {expr}"),
115                    _ => unreachable!(),
116                },
117                wrapped.to_string(),
118            );
119
120            // let normalized = wrapped.normalize();
121            // println!(" → {normalized}");
122            // assert_str_eq!(
123            //     if i % 2 == 1 { "a > b" } else { "NOT a > b" },
124            //     normalized.to_string(),
125            //     "Pairs of `NOT`s cancel each other out."
126            // );
127        }
128    }
129
130    #[test]
131    fn not_parens() {
132        let expr = cmp_a_gt_b();
133
134        for i in 0..3 {
135            let mut wrapped = Not::from(expr.clone());
136            for _ in 0..i {
137                wrapped = Not::from(Condition::Not(wrapped).parenthesize().parenthesize());
138            }
139
140            print!("{i}: {wrapped}");
141            io::stdout().lock().flush().unwrap();
142
143            let (expected_wrapped, expected_normalized) = match i {
144                0 => {
145                    let expr = format!("NOT {expr}");
146                    (expr.clone(), expr)
147                }
148                1 => (format!("NOT ((NOT {expr}))"), expr.to_string()),
149                2 => (
150                    format!("NOT ((NOT ((NOT {expr}))))"),
151                    format!("(NOT {expr})"),
152                ),
153                _ => unreachable!(),
154            };
155
156            assert_str_eq!(expected_wrapped, wrapped.to_string());
157
158            _ = expected_normalized;
159            // let normalized = wrapped.normalize();
160            // println!(" → {normalized}");
161            // assert_str_eq!(
162            //     expected_normalized,
163            //     normalized.to_string(),
164            //     "Pairs of `NOT`s cancel each other out."
165            // );
166        }
167    }
168
169    #[test]
170    fn normalize_variants() {
171        let wrapped = cmp_a_gt_b()
172            .parenthesize()
173            .parenthesize()
174            .parenthesize()
175            .not()
176            .parenthesize()
177            .parenthesize()
178            .parenthesize();
179
180        println!("{wrapped}");
181
182        assert_str_eq!("(((NOT (((a > b))))))", wrapped.to_string());
183
184        // let normalized = wrapped.clone().normalize();
185        // println!("{normalized}");
186
187        // assert_str_eq!(
188        //     cmp_a_gt_b().parenthesize().not().parenthesize().to_string(),
189        //     normalized.to_string()
190        // );
191
192        // ----
193
194        let wrapped = cmp_a_gt_b()
195            .parenthesize()
196            .parenthesize()
197            .parenthesize()
198            .not()
199            .not();
200
201        println!("{wrapped}");
202
203        assert_str_eq!("NOT NOT (((a > b)))", wrapped.to_string());
204
205        // let normalized = wrapped.clone().normalize();
206
207        // println!("{normalized}");
208
209        // assert_str_eq!(
210        //     cmp_a_gt_b().parenthesize().to_string(),
211        //     normalized.to_string(),
212        //     "`NOT NOT` should be normalized away"
213        // );
214
215        // ----
216
217        let wrapped = cmp_a_gt_b()
218            .parenthesize()
219            .parenthesize()
220            .parenthesize()
221            .not()
222            .parenthesize()
223            .not();
224
225        println!("{wrapped}");
226
227        assert_str_eq!("NOT (NOT (((a > b))))", wrapped.to_string());
228
229        // let normalized = wrapped.clone().normalize();
230
231        // println!("{normalized}");
232
233        // assert_str_eq!(
234        //     cmp_a_gt_b().parenthesize().to_string(),
235        //     normalized.to_string(),
236        //     "`NOT (NOT` should be normalized away"
237        // );
238
239        // ----
240
241        let wrapped = !!!(cmp_a_gt_b().parenthesize().parenthesize().parenthesize());
242
243        println!("{wrapped}");
244
245        assert_str_eq!("NOT NOT NOT (((a > b)))", wrapped.to_string());
246
247        // let normalized = wrapped.clone().normalize();
248
249        // println!("{normalized}");
250
251        // assert_str_eq!(
252        //     (!cmp_a_gt_b().parenthesize()).to_string(),
253        //     normalized.to_string(),
254        //     "`NOT NOT NOT` should be normalized to `NOT`"
255        // );
256    }
257}
258
259#[cfg(test)]
260mod examples {
261    // This is really for formatting of the doc tests
262    #[test]
263    fn not() -> Result<(), Box<dyn std::error::Error>> {
264        use crate::Path;
265        use pretty_assertions::assert_eq;
266
267        let a = "a".parse::<Path>()?;
268        let b = "b".parse::<Path>()?;
269
270        let condition = a.greater_than(b).not();
271        assert_eq!("NOT a > b", condition.to_string());
272
273        Ok(())
274    }
275}