1use std::borrow::Cow;
4
5use vitaminc_protected::NonEmpty;
6
7use crate::ContextPiece;
8
9pub trait IntoContext<'a> {
39 fn into_context(self) -> ContextPiece<'a>;
41}
42
43impl<'a> IntoContext<'a> for ContextPiece<'a> {
45 fn into_context(self) -> ContextPiece<'a> {
46 self
47 }
48}
49
50impl<'a, T> IntoContext<'a> for NonEmpty<T>
53where
54 T: IntoContext<'a>,
55{
56 fn into_context(self) -> ContextPiece<'a> {
57 self.into_inner().into_context()
58 }
59}
60
61impl<'a> IntoContext<'a> for () {
63 fn into_context(self) -> ContextPiece<'a> {
64 ContextPiece::Unit
65 }
66}
67
68impl<'a> IntoContext<'a> for &'a str {
69 fn into_context(self) -> ContextPiece<'a> {
70 ContextPiece::Text(Cow::Borrowed(self))
71 }
72}
73
74impl<'a> IntoContext<'a> for String {
75 fn into_context(self) -> ContextPiece<'a> {
76 ContextPiece::Text(Cow::Owned(self))
77 }
78}
79
80impl<'a> IntoContext<'a> for &'a [u8] {
81 fn into_context(self) -> ContextPiece<'a> {
82 ContextPiece::Bytes(Cow::Borrowed(self))
83 }
84}
85
86impl<'a> IntoContext<'a> for Vec<u8> {
87 fn into_context(self) -> ContextPiece<'a> {
88 ContextPiece::Bytes(Cow::Owned(self))
89 }
90}
91
92impl<'a, const N: usize> IntoContext<'a> for [u8; N] {
93 fn into_context(self) -> ContextPiece<'a> {
94 ContextPiece::Bytes(Cow::Owned(self.to_vec()))
95 }
96}
97
98impl<'a, const N: usize> IntoContext<'a> for &'a [u8; N] {
99 fn into_context(self) -> ContextPiece<'a> {
100 ContextPiece::Bytes(Cow::Borrowed(self.as_slice()))
101 }
102}
103
104impl<'a> IntoContext<'a> for Cow<'a, [u8]> {
105 fn into_context(self) -> ContextPiece<'a> {
106 ContextPiece::Bytes(self)
107 }
108}
109
110macro_rules! integer_context {
111 ($($ty:ty => $variant:ident),+ $(,)?) => {$(
112 impl<'a> IntoContext<'a> for $ty {
115 fn into_context(self) -> ContextPiece<'a> {
116 ContextPiece::$variant(self)
117 }
118 }
119 )+};
120}
121
122integer_context!(
123 u8 => U8, u16 => U16, u32 => U32, u64 => U64, u128 => U128,
124 i8 => I8, i16 => I16, i32 => I32, i64 => I64, i128 => I128,
125);
126
127impl<'a, T> IntoContext<'a> for Option<T>
130where
131 T: IntoContext<'a>,
132{
133 fn into_context(self) -> ContextPiece<'a> {
134 ContextPiece::List(self.into_iter().map(T::into_context).collect())
135 }
136}
137
138impl<'a, A, B> IntoContext<'a> for (A, B)
140where
141 A: IntoContext<'a>,
142 B: IntoContext<'a>,
143{
144 fn into_context(self) -> ContextPiece<'a> {
145 let (a, b) = self;
146 ContextPiece::List(vec![a.into_context(), b.into_context()])
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 #![allow(clippy::unwrap_used)]
153
154 use quickcheck_macros::quickcheck;
155
156 use super::*;
157 use crate::Context;
158
159 fn encoded<'a>(value: impl IntoContext<'a>) -> Context<'a> {
160 value.into_context().encode()
161 }
162
163 mod given_a_text_context {
164 use super::*;
165
166 #[test]
167 fn is_a_text_leaf_however_it_is_owned() {
168 assert_eq!("a".into_context(), ContextPiece::Text(Cow::Borrowed("a")));
169 assert_eq!(
170 String::from("a").into_context(),
171 ContextPiece::Text(Cow::Owned(String::from("a")))
172 );
173 assert_eq!(encoded("a"), encoded(String::from("a")));
174 }
175 }
176
177 mod given_a_bytes_context {
178 use super::*;
179
180 #[test]
181 fn every_byte_container_is_the_same_bytes_leaf() {
182 let expected = encoded(b"abc".as_slice());
183 assert_eq!(encoded(*b"abc"), expected, "an owned array");
184 assert_eq!(encoded(b"abc"), expected, "a borrowed array");
185 assert_eq!(encoded(b"abc".to_vec()), expected, "a vector");
186 assert_eq!(
187 encoded(Cow::<[u8]>::Borrowed(b"abc")),
188 expected,
189 "a borrowed cow"
190 );
191 assert_eq!(
192 encoded(Cow::<[u8]>::Owned(b"abc".to_vec())),
193 expected,
194 "an owned cow"
195 );
196 }
197
198 #[test]
199 fn text_and_bytes_are_different_contexts() {
200 assert_eq!("ab".into_context().leaves().count(), 1);
201 assert_ne!(encoded("ab"), encoded(b"ab".as_slice()));
202 }
203 }
204
205 mod given_an_integer_context {
206 use super::*;
207
208 #[test]
209 fn keeps_its_type() {
210 assert_eq!(7u32.into_context(), ContextPiece::U32(7));
211 assert_eq!((-7i16).into_context(), ContextPiece::I16(-7));
212 }
213
214 #[test]
215 fn width_and_signedness_are_part_of_the_context() {
216 assert_ne!(encoded(7u32), encoded(7u64), "different widths");
217 assert_ne!(encoded(7u32), encoded(7i32), "different signedness");
218 assert_ne!(encoded(1u16), encoded([1u8, 0]), "not its raw bytes");
219 assert_ne!(
220 encoded(0u64),
221 encoded(Option::<u64>::None),
222 "`0u64` is not `None`"
223 );
224 }
225 }
226
227 mod given_the_unit_context {
228 use super::*;
229
230 #[test]
231 fn is_the_empty_context() {
232 assert_eq!(().into_context(), ContextPiece::Unit);
233 assert_eq!(encoded(()), Context::empty());
234 }
235
236 #[test]
237 fn differs_from_none_and_from_empty_text() {
238 assert_ne!(encoded(()), encoded(Option::<&str>::None));
239 assert_ne!(encoded(()), encoded(""));
240 }
241 }
242
243 mod given_an_option_context {
244 use super::*;
245
246 #[quickcheck]
247 fn some_is_the_one_element_list(bytes: Vec<u8>) -> bool {
248 let inner = encoded(bytes.clone());
249 let some = encoded(Some(bytes));
250 some == Context::pae(&[inner.as_bytes()]) && some != inner
251 }
252
253 #[test]
254 fn none_is_the_empty_list() {
255 assert_eq!(None::<&str>.into_context(), ContextPiece::List(vec![]));
256 assert_eq!(encoded(None::<&str>), Context::pae(&[]));
257 assert_eq!(encoded(None::<&str>).as_bytes(), &[0u8; 8]);
258 }
259
260 #[test]
261 fn none_differs_from_some_of_an_empty_value() {
262 assert_ne!(encoded(None::<&str>), encoded(Some("")));
263 }
264
265 #[test]
266 fn some_does_not_carry_the_value_side_domain() {
267 assert_ne!(encoded(Some("value")), encoded("value").for_option_some());
270 }
271 }
272
273 mod given_a_pair_context {
274 use super::*;
275
276 #[test]
277 fn is_the_two_element_list_of_typed_parts() {
278 assert_eq!(
279 ("users/email", 7u64).into_context(),
280 ContextPiece::List(vec![
281 ContextPiece::Text(Cow::Borrowed("users/email")),
282 ContextPiece::U64(7),
283 ])
284 );
285 let left = encoded("left");
286 let right = encoded(7u16);
287 assert_eq!(
288 encoded(("left", 7u16)),
289 Context::pae(&[left.as_bytes(), right.as_bytes()])
290 );
291 }
292
293 #[test]
294 fn is_injective_across_its_boundary() {
295 assert_ne!(encoded(("ab", "cd")), encoded(("a", "bcd")));
296 assert_ne!(encoded(("foobar", ())), encoded(("foo", "bar")));
297 }
298
299 #[test]
300 fn unit_inside_a_pair_is_a_zero_length_part() {
301 assert_eq!(
302 encoded(("x", ())),
303 Context::pae(&[encoded("x").as_bytes(), b""])
304 );
305 assert_ne!(encoded(("x", ())), encoded("x"));
306 }
307 }
308
309 mod given_a_proven_context {
310 use super::*;
311
312 #[test]
313 fn non_empty_is_transparent() {
314 assert_eq!(
315 NonEmpty::new("users/email").unwrap().into_context(),
316 "users/email".into_context()
317 );
318 assert_eq!(
319 encoded(Some(NonEmpty::new("users/email").unwrap())),
320 encoded(Some("users/email"))
321 );
322 assert_eq!(
323 encoded(NonEmpty::new(("users", "email")).unwrap()),
324 encoded(("users", "email"))
325 );
326 assert_eq!(
327 encoded(vitaminc_protected::nonempty!("users/email").with(42u64)),
328 encoded(("users/email", 42u64)),
329 "a pair extended from a proven head is the bare pair"
330 );
331 }
332 }
333
334 mod given_an_encoded_context {
335 use super::*;
336
337 #[test]
338 fn is_the_encoded_leaf() {
339 let stored = encoded(("a", 1u8));
340 assert_eq!(
341 stored.clone().into_context(),
342 ContextPiece::Encoded(Cow::Borrowed(stored.as_bytes())).into_owned()
343 );
344 assert_eq!(encoded(stored.clone()), stored);
345 }
346
347 #[test]
348 fn inside_a_composite_is_written_verbatim() {
349 let derived = Context::empty().for_map_entry("name");
352 assert_eq!(
353 encoded((derived.clone(), 7u8)),
354 Context::pae(&[derived.as_bytes(), encoded(7u8).as_bytes()])
355 );
356 }
357 }
358}