dynamodb_expression/update/set/
math.rs1use core::fmt::{self, Write};
2
3use crate::{
4 path::Path,
5 update::Update,
6 value::{Num, ValueOrRef},
7};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Math {
16 pub(crate) dst: Path,
17 pub(crate) src: Option<Path>,
18 op: MathOp,
19 pub(crate) num: ValueOrRef,
20}
21
22impl Math {
29 pub fn builder<T>(dst: T) -> Builder
31 where
32 T: Into<Path>,
33 {
34 Builder {
35 dst: dst.into(),
36 src: None,
37 }
38 }
39
40 pub fn and<T>(self, other: T) -> Update
58 where
59 T: Into<Update>,
60 {
61 Update::from(self).and(other)
62 }
63}
64
65impl fmt::Display for Math {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 self.dst.fmt(f)?;
68 f.write_str(" = ")?;
69 self.src.as_ref().unwrap_or(&self.dst).fmt(f)?;
71 f.write_char(' ')?;
72 self.op.fmt(f)?;
73 f.write_char(' ')?;
74 self.num.fmt(f)
75 }
76}
77
78#[derive(Clone, Copy, PartialEq, Eq)]
79enum MathOp {
80 Add,
81 Sub,
82}
83
84impl fmt::Debug for MathOp {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 fmt::Display::fmt(self, f)
87 }
88}
89
90impl fmt::Display for MathOp {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 f.write_char(match self {
93 MathOp::Add => '+',
94 MathOp::Sub => '-',
95 })
96 }
97}
98
99#[must_use = "Consume this `Builder` by using its `.add()` or `.sub()` methods"]
101#[derive(Debug, Clone)]
102pub struct Builder {
103 dst: Path,
104 src: Option<Path>,
105}
106
107impl Builder {
108 pub fn src<T>(mut self, src: T) -> Self
111 where
112 T: Into<Path>,
113 {
114 self.src = Some(src.into());
115
116 self
117 }
118
119 #[rustversion::attr(before(1.81), allow(clippy::should_implement_trait))]
121 #[rustversion::attr(
122 since(1.81),
123 expect(
124 clippy::should_implement_trait,
125 reason = "This is for the DynamoDB `+` operator, not the Rust `+` operator."
126 )
127 )]
128 pub fn add<T>(self, num: T) -> Math
129 where
130 T: Into<Num>,
131 {
132 self.with_op(MathOp::Add, num)
133 }
134
135 #[rustversion::attr(before(1.81), allow(clippy::should_implement_trait))]
137 #[rustversion::attr(
138 since(1.81),
139 expect(
140 clippy::should_implement_trait,
141 reason = "This is for the DynamoDB `-` operator, not the Rust `-` operator."
142 )
143 )]
144 pub fn sub<T>(self, num: T) -> Math
145 where
146 T: Into<Num>,
147 {
148 self.with_op(MathOp::Sub, num)
149 }
150
151 fn with_op<T>(self, op: MathOp, num: T) -> Math
152 where
153 T: Into<Num>,
154 {
155 let Self { dst, src } = self;
156
157 Math {
158 dst,
159 src,
160 op,
161 num: num.into().into(),
162 }
163 }
164}
165
166#[cfg(test)]
167mod test {
168 use pretty_assertions::assert_eq;
169
170 use crate::{
171 update::{Assign, Set, SetAction},
172 Num, Path,
173 };
174
175 use super::Math;
176
177 #[test]
178 fn and() -> Result<(), Box<dyn std::error::Error>> {
179 let math: Math = "foo".parse::<Path>()?.math().add(1);
180 let assign: Assign = "bar".parse::<Path>()?.set(Num::new(8));
181
182 let combined = math.clone().and(assign.clone());
185 assert_eq!(r#"SET foo = foo + 1, bar = 8"#, combined.to_string());
186
187 let combined = math.clone().and(SetAction::from(assign.clone()));
190 assert_eq!(r#"SET foo = foo + 1, bar = 8"#, combined.to_string());
191
192 let set: Set = [
195 SetAction::from(assign),
196 SetAction::from("baz".parse::<Path>()?.list_append().list(["d", "e", "f"])),
197 ]
198 .into_iter()
199 .collect();
200 let combined = math.clone().and(set);
201 assert_eq!(
202 r#"SET foo = foo + 1, bar = 8, baz = list_append(baz, ["d", "e", "f"])"#,
203 combined.to_string()
204 );
205
206 let combined = math.clone().and("quux".parse::<Path>()?.remove());
209 assert_eq!(r#"SET foo = foo + 1 REMOVE quux"#, combined.to_string());
210
211 let combined = math.and("quux".parse::<Path>()?.remove());
214 assert_eq!(r#"SET foo = foo + 1 REMOVE quux"#, combined.to_string());
215
216 Ok(())
217 }
218}