dynamodb_expression/value/scalar.rs
1use core::fmt::{self, LowerExp, UpperExp};
2
3use aws_sdk_dynamodb::{primitives::Blob, types::AttributeValue};
4
5use super::base64;
6use super::Num;
7
8/// <https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes>
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub enum Scalar {
11 /// DynamoDB [string](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-S)
12 /// value
13 String(String),
14 /// DynamoDB [numeric](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-N)
15 /// value
16 Num(Num),
17 /// DynamoDB [boolean](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-BOOL)
18 /// value
19 Bool(bool),
20 /// DynamoDB [binary](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-B)
21 /// value
22 Binary(Vec<u8>),
23 /// DynamoDB [null](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-NULL)
24 /// value
25 Null,
26}
27
28impl Scalar {
29 /// Use when you need a [string][1] value for DynamoDB.
30 ///
31 /// See also: [`Value::new_string`]
32 ///
33 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-S
34 /// [`Value::new_string`]: crate::value::Value::new_string
35 pub fn new_string<T>(value: T) -> Self
36 where
37 T: Into<String>,
38 {
39 Self::String(value.into())
40 }
41
42 /// Use when you need a [numeric][1] value for DynamoDB.
43 ///
44 /// See also: [`Scalar::new_num_lower_exp`], [`Scalar::new_num_upper_exp`],
45 /// [`Value::new_num`], [`Num`]
46 ///
47 /// # Examples
48 ///
49 /// ```
50 /// use dynamodb_expression::Scalar;
51 /// # use pretty_assertions::assert_eq;
52 ///
53 /// let value = Scalar::new_num(2600);
54 /// assert_eq!("2600", value.to_string());
55 ///
56 /// let value = Scalar::new_num(2600.0);
57 /// assert_eq!("2600", value.to_string());
58 /// ```
59 ///
60 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-N
61 /// [`Value::new_num`]: crate::value::Value::new_num
62 pub fn new_num<N>(value: N) -> Self
63 where
64 N: ToString + num::Num,
65 {
66 Self::Num(Num::new(value))
67 }
68
69 /// Use when you need a [numeric][1] value for DynamoDB in exponent form
70 /// (with a lowercase `e`).
71 ///
72 /// See also: [`Scalar::new_num`], [`Scalar::new_num_upper_exp`],
73 /// [`Value::new_num_lower_exp`], [`Num`]
74 ///
75 /// # Examples
76 ///
77 /// ```
78 /// use dynamodb_expression::Scalar;
79 /// # use pretty_assertions::assert_eq;
80 ///
81 /// let value = Scalar::new_num_lower_exp(2600);
82 /// assert_eq!("2.6e3", value.to_string());
83 ///
84 /// let value = Scalar::new_num_lower_exp(2600.0);
85 /// assert_eq!("2.6e3", value.to_string());
86 /// ```
87 ///
88 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-N
89 /// [`Value::new_num_lower_exp`]: crate::value::Value::new_num_lower_exp
90 pub fn new_num_lower_exp<N>(value: N) -> Self
91 where
92 N: LowerExp + num::Num,
93 {
94 Self::Num(Num::new_lower_exp(value))
95 }
96
97 /// Use when you need a [numeric][1] value for DynamoDB in exponent form
98 /// (with an uppercase `e`).
99 ///
100 /// See also: [`Scalar::new_num`], [`Scalar::new_num_lower_exp`],
101 /// [`Value::new_num_upper_exp`], [`Num`]
102 ///
103 /// # Examples
104 ///
105 /// ```
106 /// use dynamodb_expression::Scalar;
107 /// # use pretty_assertions::assert_eq;
108 ///
109 /// let value = Scalar::new_num_upper_exp(2600);
110 /// assert_eq!("2.6E3", value.to_string());
111 ///
112 /// let value = Scalar::new_num_upper_exp(2600.0);
113 /// assert_eq!("2.6E3", value.to_string());
114 /// ```
115 ///
116 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-N
117 /// [`Value::new_num_upper_exp`]: crate::value::Value::new_num_upper_exp
118 pub fn new_num_upper_exp<N>(value: N) -> Self
119 where
120 N: UpperExp + num::Num,
121 {
122 Self::Num(Num::new_upper_exp(value))
123 }
124
125 /// Use when you need a [boolean][1] value for DynamoDB.
126 ///
127 /// See also: [`Value::new_bool`]
128 ///
129 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-BOOL
130 /// [`Value::new_bool`]: crate::value::Value::new_bool
131 pub fn new_bool(b: bool) -> Self {
132 Self::Bool(b)
133 }
134
135 /// Use when you need a [binary][1] value for DynamoDB.
136 ///
137 /// See also: [`Value::new_binary`]
138 ///
139 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-B
140 /// [`Value::new_binary`]: crate::value::Value::new_binary
141 pub fn new_binary<B>(binary: B) -> Self
142 where
143 B: Into<Vec<u8>>,
144 {
145 Self::Binary(binary.into())
146 }
147
148 /// Use when you need a [null][1] value for DynamoDB.
149 ///
150 /// See also: [`Value::new_null`]
151 ///
152 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-NULL
153 /// [`Value::new_null`]: crate::value::Value::new_null
154 pub fn new_null() -> Self {
155 Self::Null
156 }
157
158 // Intentionally not using `impl From<Scalar> for AttributeValue` because
159 // I don't want to make this a public API people rely on. The purpose of this
160 // crate is not to make creating `AttributeValues` easier. They should try
161 // `serde_dynamo`.
162 pub(super) fn into_attribute_value(self) -> AttributeValue {
163 match self {
164 Scalar::String(s) => AttributeValue::S(s),
165 Scalar::Num(n) => n.into_attribute_value(),
166 Scalar::Bool(b) => AttributeValue::Bool(b),
167 Scalar::Binary(b) => AttributeValue::B(Blob::new(b)),
168 Scalar::Null => AttributeValue::Null(true),
169 }
170 }
171}
172
173impl fmt::Display for Scalar {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 match self {
176 Self::String(s) => serde_json::to_string(s).unwrap().fmt(f),
177 Self::Num(n) => n.fmt(f),
178 Self::Bool(b) => serde_json::Value::Bool(*b).to_string().fmt(f),
179 Self::Binary(b) => serde_json::Value::String(base64(b)).to_string().fmt(f),
180
181 // TODO: I'm pretty sure this isn't right.
182 // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html#DDB-Type-AttributeValue-NULL
183 Self::Null => f.write_str("NULL"),
184 }
185 }
186}
187
188impl From<String> for Scalar {
189 fn from(value: String) -> Self {
190 Self::String(value)
191 }
192}
193
194impl From<&String> for Scalar {
195 fn from(value: &String) -> Self {
196 Self::String(value.to_owned())
197 }
198}
199
200impl From<&str> for Scalar {
201 fn from(value: &str) -> Self {
202 Self::String(value.to_owned())
203 }
204}
205
206impl From<&&str> for Scalar {
207 fn from(value: &&str) -> Self {
208 Self::String((*value).to_owned())
209 }
210}
211
212impl From<Num> for Scalar {
213 fn from(value: Num) -> Self {
214 Self::Num(value)
215 }
216}
217
218impl From<bool> for Scalar {
219 fn from(value: bool) -> Self {
220 Self::Bool(value)
221 }
222}
223
224impl From<Vec<u8>> for Scalar {
225 fn from(value: Vec<u8>) -> Self {
226 Self::Binary(value)
227 }
228}
229
230impl<const N: usize> From<[u8; N]> for Scalar {
231 fn from(value: [u8; N]) -> Self {
232 Self::Binary(value.into())
233 }
234}
235
236impl<const N: usize> From<&[u8; N]> for Scalar {
237 fn from(value: &[u8; N]) -> Self {
238 Self::Binary(value.into())
239 }
240}
241
242impl From<&[u8]> for Scalar {
243 fn from(value: &[u8]) -> Self {
244 Self::Binary(value.into())
245 }
246}
247
248impl From<()> for Scalar {
249 fn from(_: ()) -> Self {
250 Self::Null
251 }
252}
253
254impl FromIterator<u8> for Scalar {
255 fn from_iter<T>(iter: T) -> Self
256 where
257 T: IntoIterator<Item = u8>,
258 {
259 Self::Binary(iter.into_iter().collect())
260 }
261}
262
263#[cfg(test)]
264mod test {
265 use pretty_assertions::assert_eq;
266
267 use crate::Num;
268
269 use super::Scalar;
270
271 #[test]
272 fn string() {
273 let fish: &str = "fish";
274
275 let actual = Scalar::new_string(fish);
276 assert_eq!("\"fish\"", actual.to_string());
277
278 // &str
279 let actual = Scalar::from(fish);
280 assert_eq!("\"fish\"", actual.to_string());
281
282 // &&str
283 let phish: &&str = &fish;
284 let actual = Scalar::from(phish);
285 assert_eq!("\"fish\"", actual.to_string());
286
287 // &String
288 let phish: String = fish.into();
289 let actual = Scalar::from(&phish);
290 assert_eq!("\"fish\"", actual.to_string());
291
292 // String
293 let actual = Scalar::from(phish);
294 assert_eq!("\"fish\"", actual.to_string());
295 }
296
297 #[test]
298 fn numeric() {
299 let actual = Scalar::new_num(42);
300 assert_eq!("42", actual.to_string());
301
302 let actual = Scalar::from(Num::new(42));
303 assert_eq!("42", actual.to_string());
304 }
305
306 #[test]
307 fn boolean() {
308 assert_eq!("true", Scalar::new_bool(true).to_string());
309 assert_eq!("false", Scalar::new_bool(false).to_string());
310
311 assert_eq!("true", Scalar::from(true).to_string());
312 assert_eq!("false", Scalar::from(false).to_string());
313 }
314
315 #[test]
316 fn binary_vec() {
317 let bytes: Vec<u8> = b"fish".into();
318
319 let actual = Scalar::new_binary(bytes.clone());
320 assert_eq!(r#""ZmlzaA==""#, actual.to_string());
321
322 let actual = Scalar::from(bytes);
323 assert_eq!(r#""ZmlzaA==""#, actual.to_string());
324 }
325
326 #[test]
327 fn binary_array() {
328 let bytes: [u8; 4] = b"fish".to_owned();
329
330 let actual = Scalar::new_binary(bytes);
331 assert_eq!(r#""ZmlzaA==""#, actual.to_string());
332
333 let actual = Scalar::from(bytes);
334 assert_eq!(r#""ZmlzaA==""#, actual.to_string());
335 }
336
337 #[test]
338 fn binary_array_ref() {
339 let bytes: &[u8; 4] = b"fish";
340
341 #[allow(clippy::needless_borrows_for_generic_args)]
342 let actual = Scalar::new_binary(bytes);
343 assert_eq!(r#""ZmlzaA==""#, actual.to_string());
344
345 let actual = Scalar::from(bytes);
346 assert_eq!(r#""ZmlzaA==""#, actual.to_string());
347 }
348
349 #[test]
350 fn binary_slice() {
351 let bytes: &[u8] = &b"fish"[..];
352
353 let actual = Scalar::new_binary(bytes);
354 assert_eq!(r#""ZmlzaA==""#, actual.to_string());
355
356 let actual = Scalar::from(bytes);
357 assert_eq!(r#""ZmlzaA==""#, actual.to_string());
358 }
359
360 #[test]
361 fn null() {
362 assert_eq!("NULL", Scalar::new_null().to_string());
363 assert_eq!("NULL", Scalar::from(()).to_string());
364 }
365}