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}