dynamodb_expression/value/set/
mod.rs1mod binary_set;
2mod num_set;
3mod string_set;
4
5pub use binary_set::BinarySet;
6pub use num_set::NumSet;
7pub use string_set::StringSet;
8
9use core::fmt;
10
11use aws_sdk_dynamodb::types::AttributeValue;
12
13use super::base64;
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub enum Set {
20 StringSet(StringSet),
21 NumSet(NumSet),
22 BinarySet(BinarySet),
23}
24
25impl Set {
26 pub fn new_string_set<T>(string_set: T) -> Self
30 where
31 T: Into<StringSet>,
32 {
33 string_set.into().into()
34 }
35
36 pub fn new_num_set<T>(num_set: T) -> Self
40 where
41 T: Into<NumSet>,
42 {
43 num_set.into().into()
44 }
45
46 pub fn new_binary_set<T>(binary_set: T) -> Self
50 where
51 T: Into<BinarySet>,
52 {
53 binary_set.into().into()
54 }
55
56 pub(super) fn into_attribute_value(self) -> AttributeValue {
61 match self {
62 Set::StringSet(set) => set.into_attribute_value(),
63 Set::NumSet(set) => set.into_attribute_value(),
64 Set::BinarySet(set) => set.into_attribute_value(),
65 }
66 }
67}
68
69impl fmt::Display for Set {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Set::StringSet(set) => set.fmt(f),
73 Set::NumSet(set) => set.fmt(f),
74 Set::BinarySet(set) => set.fmt(f),
75 }
76 }
77}
78
79impl From<StringSet> for Set {
80 fn from(string_set: StringSet) -> Self {
81 Self::StringSet(string_set)
82 }
83}
84
85impl From<NumSet> for Set {
86 fn from(num_set: NumSet) -> Self {
87 Self::NumSet(num_set)
88 }
89}
90
91impl From<BinarySet> for Set {
92 fn from(binary_set: BinarySet) -> Self {
93 Self::BinarySet(binary_set)
94 }
95}
96
97#[cfg(test)]
98mod test {
99 use std::{cell::RefCell, iter::FusedIterator};
100
101 use itertools::Itertools;
102 use pretty_assertions::assert_eq;
103
104 use crate::{
105 value::{base64, Set},
106 Num,
107 };
108
109 #[test]
110 fn string_set_display() {
111 let set = Set::new_string_set(["foo", "bar", "!@#$%^&*()-=_+\"'{}[]\\|;:<>,./?`~"]);
112 assert_eq!(
113 r#"["!@#$%^&*()-=_+\"'{}[]\\|;:<>,./?`~", "bar", "foo"]"#,
114 set.to_string()
115 );
116
117 let deserialized: Vec<String> =
118 serde_json::from_str(&set.to_string()).expect("Must be valid JSON");
119 assert_eq!(
120 vec!["!@#$%^&*()-=_+\"'{}[]\\|;:<>,./?`~", "bar", "foo"],
121 deserialized
122 );
123 }
124
125 #[test]
126 #[allow(clippy::approx_constant)]
127 fn num_set_display() {
128 let set = Set::new_num_set([-1, 0, 1, 42]);
129 assert_eq!("[-1, 0, 1, 42]", set.to_string());
130
131 let deserialized: Vec<i32> =
132 serde_json::from_str(&set.to_string()).expect("Must be valid JSON");
133 assert_eq!(vec![-1, 0, 1, 42], deserialized);
134
135 let set = Set::new_num_set([
136 Num::new_lower_exp(f32::MIN),
137 Num::new(0.0),
138 Num::new(3.14),
139 Num::new(1000),
140 Num::new_upper_exp(f32::MAX),
141 ]);
142 assert_eq!(
143 "[\
144 -3.4028235e38, \
145 0, \
146 1000, \
147 3.14, \
148 3.4028235E38\
149 ]",
150 set.to_string()
151 );
152
153 let deserialized: Vec<f32> =
154 serde_json::from_str(&set.to_string()).expect("Must be valid JSON");
155 assert_eq!(vec![f32::MIN, 0.0, 1000.0, 3.14, f32::MAX], deserialized);
156 }
157
158 #[test]
159 fn binary_set_display() {
160 let set = Set::new_binary_set([" > ", " ? "]);
164 assert_eq!(r#"["ICA+IA==", "ICA/IA=="]"#, set.to_string());
165
166 let deserialized: Vec<String> =
167 serde_json::from_str(&set.to_string()).expect("Must be valid JSON");
168 assert_eq!(vec!["ICA+IA==", "ICA/IA=="], deserialized);
169 }
170
171 #[test]
172 #[ignore = "Just used to find more base64 for JSON encoding testing"]
173 fn find_tricky_base64() {
174 fn charset(
176 ) -> impl Iterator<Item = char> + ExactSizeIterator + DoubleEndedIterator + FusedIterator + Clone
177 {
178 (32..127).map(char::from_u32).map(Option::unwrap)
179 }
180
181 let specials = RefCell::new(['+', '/'].into_iter().peekable());
184 [charset(), charset(), charset(), charset()]
185 .into_iter()
186 .multi_cartesian_product()
187 .take_while(|_| specials.borrow_mut().peek().is_some())
188 .map(String::from_iter)
189 .enumerate() .map(|(i, raw)| {
191 let encoded = base64(&raw);
192 (i, raw, encoded)
193 })
194 .filter(|(_i, _raw, encoded)| {
195 if encoded.contains(specials.borrow_mut().peek().cloned().unwrap()) {
196 specials.borrow_mut().next();
197 true
198 } else {
199 false
200 }
201 })
202 .for_each(|(index, raw, encoded)| {
203 println!(
204 "The encoded version of iteration {index}, {raw:?}, \
205 includes special characters: {encoded}"
206 )
207 });
208 }
209}