dynamodb_expression/path/mod.rs
1//! Types related to [DynamoDB document paths][1]. For more, see [`Path`].
2//!
3//! [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Attributes.html#Expressions.Attributes.NestedElements.DocumentPathExamples
4
5mod element;
6mod name;
7
8pub use self::{
9 element::{Element, IndexedField, Indexes},
10 name::Name,
11};
12
13use core::{
14 fmt::{self, Write},
15 ops,
16 str::FromStr,
17};
18
19use itertools::Itertools;
20
21use crate::{
22 condition::{
23 attribute_type::Type, equal, greater_than, greater_than_or_equal, less_than,
24 less_than_or_equal, not_equal, AttributeType, BeginsWith, Between, Condition, Contains, In,
25 },
26 key::Key,
27 operand::{Operand, Size},
28 update::{
29 if_not_exists::Builder as IfNotExistsBuilder, list_append::Builder as ListAppendBuilder,
30 math::Builder as MathBuilder, Add, AddValue, Assign, Delete, IfNotExists, ListAppend, Math,
31 Remove,
32 },
33 value::{self, StringOrRef, Value},
34};
35
36/// Represents a DynamoDB [document path][1]. For example, `foo[3][7].bar[2].baz`.
37///
38/// You can use the many methods on [`Path`] for building [DynamoDB
39/// expressions][4].
40/// For example, [`.set()`], [`.if_not_exists()`], or [`.remove()`] are some
41/// methods for creating [update expressions][5]. [`.attribute_not_exists()`],
42/// [`.less_than()`], and [`.contains()`] are some methods for creating
43/// condition and filter expressions.
44///
45/// When you're ready to build an [`Expression`], use [`Expression::builder`].
46///
47/// When used in an [`Expression`], attribute names in a [`Path`] are
48/// automatically handled as [expression attribute names][2], allowing for names
49/// that would not otherwise be permitted by DynamoDB. For example,
50/// `foo[3][7].bar[2].baz` would become something similar to `#0[3][7].#1[2].#2`,
51/// and the names would be in the `expression_attribute_names`.
52///
53/// See also: [`Element`], [`Name`], [`IndexedField`]
54///
55/// # There are many ways to create a `Path`
56///
57/// For creating a new [`Path`]:
58/// * Parse from a string, as seen [below](#parsing). This is the preferred way. The only
59/// time when other constructors are needed is when you have an attribute name
60/// with a `.` in it that must not be treated as a separator for sub-attributes.
61/// * [`Path::new_name`] and [`Path::new_indexed_field`] constructors
62/// * [`Path::from`] for converting anything that's `Into<Element>` into a [`Path`]
63/// (see also: [`Element`])
64///
65/// For building a [`Path`] one step at a time:
66/// * Use the [`+=`] operator
67/// * Use the [`+`] operator
68/// * [`Path::append`]
69/// * [`Path::from_iter`]
70///
71/// ## Parsing
72///
73/// The safest way to construct a [`Path`] is to [parse] it. This treats `.` as a separator for
74/// sub-attributes, and `[n]` as indexes into fields.
75///
76/// Since `.` [is a valid character in an attribute name][3], see
77/// [below](#a-special-case-attribute-names-with--in-them) for examples of how
78/// to construct a [`Path`] when an attribute name contains a `.`.
79///
80/// ```
81/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
82/// use dynamodb_expression::{path::Element, Path};
83/// # use pretty_assertions::assert_eq;
84///
85/// let path: Path = "foo".parse()?;
86/// assert_eq!(
87/// Path::from_iter([
88/// Element::new_name("foo"),
89/// ]),
90/// path,
91/// );
92///
93/// let path: Path = "foo[3]".parse()?;
94/// assert_eq!(
95/// Path::from_iter([
96/// Element::new_indexed_field("foo", 3),
97/// ]),
98/// path,
99/// );
100///
101/// let path: Path = "foo[3][7]".parse()?;
102/// assert_eq!(
103/// Path::from_iter([
104/// Element::new_indexed_field("foo", [3, 7]),
105/// ]),
106/// path,
107/// );
108///
109/// let path: Path = "foo[3][7].bar".parse()?;
110/// assert_eq!(
111/// Path::from_iter([
112/// Element::new_indexed_field("foo", [3, 7]),
113/// Element::new_name("bar"),
114/// ]),
115/// path,
116/// );
117///
118/// let path: Path = "bar.baz".parse()?;
119/// assert_eq!(Path::from_iter([
120/// Element::new_name("bar"),
121/// Element::new_name("baz"),
122/// ]),
123/// path,
124/// );
125///
126/// let path: Path = "baz[0].foo".parse()?;
127/// assert_eq!(
128/// Path::from_iter([
129/// Element::new_indexed_field("baz", 0),
130/// Element::new_name("foo"),
131/// ]),
132/// path,
133/// );
134/// #
135/// # Ok(())
136/// # }
137/// ```
138///
139/// ## A special case: attribute names with `.` in them
140///
141/// If you have an attribute name with a `.` in it, and need it to _not_ be
142/// treated as a separator for sub-attributes (such as a domain name), you can
143/// construct the [`Path`] a using [`Path::new_name`] that element of the path
144/// using [`Element::new_name`].
145///
146/// ```
147/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
148/// use dynamodb_expression::{path::Element, Path};
149/// # use pretty_assertions::assert_eq;
150///
151/// let path = Path::new_name("example.com");
152/// assert_eq!(
153/// Path::from_iter([
154/// Element::new_name("example.com"),
155/// ]),
156/// path,
157/// );
158///
159/// let path = "foo".parse::<Path>()? + Path::new_name("example.com");
160/// assert_eq!(
161/// Path::from_iter([
162/// Element::new_name("foo"),
163/// Element::new_name("example.com"),
164/// ]),
165/// path,
166/// );
167///
168/// let mut path: Path = "foo[3]".parse()?;
169/// path += Element::new_name("example.com");
170/// assert_eq!(
171/// Path::from_iter([
172/// Element::new_indexed_field("foo", 3),
173/// Element::new_name("example.com"),
174/// ]),
175/// path,
176/// );
177/// #
178/// # Ok(())
179/// # }
180/// ```
181///
182/// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Attributes.html#Expressions.Attributes.NestedElements.DocumentPathExamples
183/// [2]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html
184/// [3]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Attributes.html#Expressions.Attributes.TopLevelAttributes
185/// [4]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html
186/// [5]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html
187/// [`.set()`]: Self::set
188/// [`.if_not_exists()`]: Self::if_not_exists
189/// [`.remove()`]: Self::remove
190/// [`.attribute_not_exists()`]: Self::attribute_not_exists
191/// [`.less_than()`]: Self::less_than
192/// [`.contains()`]: Self::contains
193/// [`Expression`]: crate::expression::Expression
194/// [`Expression::builder`]: crate::expression::Expression::builder
195/// [parse]: str::parse
196/// [`+=`]: #method.add_assign
197/// [`+`]: #method.add-1
198#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
199pub struct Path {
200 pub(crate) elements: Vec<Element>,
201}
202
203impl Path {
204 /// Constructs a [`Path`] for a single attribute name (with no indexes or
205 /// sub-attributes). If you have a attribute name with one or more indexes,
206 /// parse it from a string, or use [`Path::new_indexed_field`]. See the
207 /// [`Path`] type documentation for more examples.
208 ///
209 /// This treats `.` as a part of the attribute name rather than as a
210 /// separator for sub-attributes. To build a [`Path`] that contains a `.`
211 /// that is treated as a separator, see the examples in the documentation on
212 /// the [`Path`] type.
213 ///
214 /// # Examples
215 ///
216 /// ```
217 /// use dynamodb_expression::path::{Path, Element};
218 /// # use pretty_assertions::assert_eq;
219 ///
220 /// let path = Path::new_name("foo");
221 /// assert_eq!(
222 /// Path::from_iter([
223 /// Element::new_name("foo"),
224 /// ]),
225 /// path,
226 /// );
227 ///
228 /// let path = Path::new_name("foo.bar");
229 /// assert_eq!(
230 /// Path::from_iter([
231 /// Element::new_name("foo.bar"),
232 /// ]),
233 /// path,
234 /// );
235 /// ```
236 ///
237 /// Contrast the above result of `Path::new_name("foo.bar")` with parsing,
238 /// which treats `.` as a separator for sub-attributes:
239 /// ```
240 /// # use dynamodb_expression::path::{Path, Element};
241 /// # use pretty_assertions::assert_eq;
242 /// #
243 /// let path = "foo.bar".parse().unwrap();
244 /// assert_eq!(
245 /// Path::from_iter([
246 /// Element::new_name("foo"),
247 /// Element::new_name("bar"),
248 /// ]),
249 /// path,
250 /// );
251 /// ```
252 pub fn new_name<T>(name: T) -> Self
253 where
254 T: Into<Name>,
255 {
256 Self {
257 elements: vec![Element::new_name(name)],
258 }
259 }
260
261 /// Constructs a [`Path`] for an indexed field element of a document path.
262 /// For example, `foo[3]` or `foo[7][4]`. If you have a attribute name with
263 /// no indexes, you can pass an empty collection, parse from a string, or
264 /// use [`Path::new_name`]. See the [`Path`] type documentation for more
265 /// examples.
266 ///
267 /// This treats `.` as a part of an attribute name rather than as a
268 /// separator for sub-attributes. To build a [`Path`] that contains a `.`
269 /// that is treated as a separator, see the examples in the documentation on
270 /// the [`Path`] type.
271 ///
272 /// The `indexes` parameter, here, can be an array, slice, `Vec` of, or
273 /// single `usize`.
274 ///
275 /// # Examples
276 ///
277 /// ```
278 /// use dynamodb_expression::Path;
279 /// # use pretty_assertions::assert_eq;
280 ///
281 /// assert_eq!("foo[3]", Path::new_indexed_field("foo", 3).to_string());
282 /// assert_eq!("foo[3]", Path::new_indexed_field("foo", [3]).to_string());
283 /// assert_eq!("foo[3]", Path::new_indexed_field("foo", &[3]).to_string());
284 /// assert_eq!("foo[3]", Path::new_indexed_field("foo", vec![3]).to_string());
285 ///
286 /// assert_eq!("foo[7][4]", Path::new_indexed_field("foo", [7, 4]).to_string());
287 /// assert_eq!("foo[7][4]", Path::new_indexed_field("foo", &[7, 4]).to_string());
288 /// assert_eq!("foo[7][4]", Path::new_indexed_field("foo", vec![7, 4]).to_string());
289 ///
290 /// assert_eq!("foo", Path::new_indexed_field("foo", []).to_string());
291 /// assert_eq!("foo", Path::new_indexed_field("foo", &[]).to_string());
292 /// assert_eq!("foo", Path::new_indexed_field("foo", vec![]).to_string());
293 /// ```
294 ///
295 /// See also: [`IndexedField`], [`Element::new_indexed_field`]
296 pub fn new_indexed_field<N, I>(name: N, indexes: I) -> Self
297 where
298 N: Into<Name>,
299 I: Indexes,
300 {
301 Self {
302 elements: vec![Element::new_indexed_field(name, indexes)],
303 }
304 }
305
306 /// Appends another [`Path`] to the end of this one, separated with a `.`.
307 ///
308 /// Notice that each of these examples produces the same [`Path`]: `foo[3][7].bar[2].baz`
309 ///
310 /// See also: [`Element`], [`Name`]
311 ///
312 /// ```
313 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
314 /// use dynamodb_expression::{path::{Element, Name}, Path};
315 /// # use pretty_assertions::assert_eq;
316 /// #
317 /// # let expected = Path::from_iter([
318 /// # Element::new_indexed_field("foo", [3, 7]),
319 /// # Element::new_indexed_field("bar", 2),
320 /// # Element::new_name("baz"),
321 /// # ]);
322 ///
323 /// let mut path: Path = "foo[3][7]".parse()?;
324 /// path.append("bar[2].baz".parse()?);
325 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
326 /// # assert_eq!(expected, path);
327 ///
328 /// // You can start with an empty `Path` and append one element at a time.
329 /// let mut path = Path::default();
330 /// path.append(Element::new_indexed_field("foo", [3, 7]).into());
331 /// path.append(Element::new_indexed_field("bar", 2).into());
332 /// path.append(Element::new_name("baz").into());
333 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
334 /// # assert_eq!(expected, path);
335 ///
336 /// let mut path = Path::default();
337 /// path.append(("foo", [3, 7]).into());
338 /// path.append(("bar", 2).into());
339 /// path.append(Name::from("baz").into());
340 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
341 /// # assert_eq!(expected, path);
342 /// #
343 /// # Ok(())
344 /// # }
345 /// ```
346 pub fn append(&mut self, mut other: Path) {
347 self.elements.append(&mut other.elements)
348 }
349
350 /// Returns `true` if the [`Path`] contains no attributes.
351 ///
352 /// _Hint: you can use [`Path::append`] to add attributes to a [`Path`]._
353 pub fn is_empty(&self) -> bool {
354 self.elements.is_empty()
355 }
356}
357
358/// Methods related to building condition and filter expressions.
359impl Path {
360 /// Check if the value at this [`Path`] is equal to the given value.
361 ///
362 /// [DynamoDB documentation.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Comparators)
363 pub fn equal<T>(self, right: T) -> Condition
364 where
365 T: Into<Operand>,
366 {
367 equal(self, right).into()
368 }
369
370 /// Check if the value at this [`Path`] is not equal to the given value.
371 ///
372 /// [DynamoDB documentation.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Comparators)
373 pub fn not_equal<T>(self, right: T) -> Condition
374 where
375 T: Into<Operand>,
376 {
377 not_equal(self, right).into()
378 }
379
380 /// Check if the value at this [`Path`] is greater than the given value.
381 ///
382 /// [DynamoDB documentation.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Comparators)
383 pub fn greater_than<T>(self, right: T) -> Condition
384 where
385 T: Into<Operand>,
386 {
387 greater_than(self, right).into()
388 }
389
390 /// Check if the value at this [`Path`] is greater than or equal to the given value.
391 ///
392 /// [DynamoDB documentation.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Comparators)
393 pub fn greater_than_or_equal<T>(self, right: T) -> Condition
394 where
395 T: Into<Operand>,
396 {
397 greater_than_or_equal(self, right).into()
398 }
399
400 /// Check if the value at this [`Path`] is less than the given value.
401 ///
402 /// [DynamoDB documentation.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Comparators)
403 pub fn less_than<T>(self, right: T) -> Condition
404 where
405 T: Into<Operand>,
406 {
407 less_than(self, right).into()
408 }
409
410 /// Check if the value at this [`Path`] is less than or equal to the given value.
411 ///
412 /// [DynamoDB documentation.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Comparators)
413 pub fn less_than_or_equal<T>(self, right: T) -> Condition
414 where
415 T: Into<Operand>,
416 {
417 less_than_or_equal(self, right).into()
418 }
419
420 /// The [DynamoDB `BETWEEN` operator][1]. True if `self` is greater than or
421 /// equal to `lower`, and less than or equal to `upper`.
422 ///
423 /// See also: [`Between`], [`Key::between`]
424 ///
425 /// ```
426 /// use dynamodb_expression::{Num, Path};
427 /// # use pretty_assertions::assert_eq;
428 ///
429 /// let condition = Path::new_name("age").between(Num::new(10), Num::new(90));
430 /// assert_eq!(r#"age BETWEEN 10 AND 90"#, condition.to_string());
431 /// ```
432 ///
433 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Comparators
434 /// [`Key::between`]: crate::key::Key::between
435 pub fn between<L, U>(self, lower: L, upper: U) -> Condition
436 where
437 L: Into<Operand>,
438 U: Into<Operand>,
439 {
440 Condition::Between(Between {
441 op: self.into(),
442 lower: lower.into(),
443 upper: upper.into(),
444 })
445 }
446
447 /// A [DynamoDB `IN` operation][1]. True if the value from the
448 /// [`Operand`] (the `op` parameter) is equal to any value in the list (the
449 /// `items` parameter).
450 ///
451 /// The DynamoDB allows the list to contain up to 100 values. It must have at least 1.
452 ///
453 /// See also: [`In`]
454 ///
455 /// ```
456 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
457 /// use dynamodb_expression::{condition::In, operand::Operand, Path};
458 /// # use pretty_assertions::assert_eq;
459 ///
460 /// let condition = "name".parse::<Path>()?.in_(["Jack", "Jill"]);
461 /// assert_eq!(r#"name IN ("Jack","Jill")"#, condition.to_string());
462 /// #
463 /// # Ok(())
464 /// # }
465 /// ```
466 ///
467 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Comparators
468 pub fn in_<I, T>(self, items: I) -> Condition
469 where
470 I: IntoIterator<Item = T>,
471 T: Into<Operand>,
472 {
473 In::new(self, items).into()
474 }
475
476 /// The [DynamoDB `attribute_exists` function][1]. True if the item contains
477 /// the attribute specified by [`Path`].
478 ///
479 /// ```
480 /// use dynamodb_expression::Path;
481 /// # use pretty_assertions::assert_eq;
482 ///
483 /// let condition = Path::new_name("foo").attribute_exists();
484 /// assert_eq!("attribute_exists(foo)", condition.to_string());
485 /// ```
486 ///
487 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions
488 pub fn attribute_exists(self) -> Condition {
489 Condition::AttributeExists(self.into())
490 }
491
492 /// The [DynamoDB `attribute_not_exists` function][1]. True if the item does
493 /// not contain the attribute specified by [`Path`].
494 ///
495 /// ```
496 /// use dynamodb_expression::Path;
497 /// # use pretty_assertions::assert_eq;
498 ///
499 /// let condition = Path::new_name("foo").attribute_not_exists();
500 /// assert_eq!("attribute_not_exists(foo)", condition.to_string());
501 /// ```
502 ///
503 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions
504 pub fn attribute_not_exists(self) -> Condition {
505 Condition::AttributeNotExists(self.into())
506 }
507
508 /// The [DynamoDB `attribute_type` function][1]. True if the attribute at
509 /// the specified [`Path`] is of the specified data type.
510 ///
511 /// ```
512 /// use dynamodb_expression::{condition::attribute_type::Type, Path};
513 /// # use pretty_assertions::assert_eq;
514 ///
515 /// let condition = Path::new_name("foo").attribute_type(Type::String);
516 /// assert_eq!("attribute_type(foo, S)", condition.to_string());
517 /// ```
518 ///
519 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions
520 pub fn attribute_type(self, attribute_type: Type) -> Condition {
521 AttributeType::new(self, attribute_type).into()
522 }
523
524 /// The [DynamoDB `begins_with` function][1]. True if the attribute specified by
525 /// the [`Path`] begins with a particular substring.
526 ///
527 /// `begins_with` can take a string or a reference to an extended attribute
528 /// value. Here's an example.
529 ///
530 /// See also: [`Ref`]
531 ///
532 /// ```
533 /// use dynamodb_expression::{condition::BeginsWith, value::Ref, Path};
534 /// # use pretty_assertions::assert_eq;
535 ///
536 /// let begins_with = Path::new_name("foo").begins_with("T");
537 /// assert_eq!(r#"begins_with(foo, "T")"#, begins_with.to_string());
538 ///
539 /// let begins_with = Path::new_name("foo").begins_with(Ref::new("prefix"));
540 /// assert_eq!(r#"begins_with(foo, :prefix)"#, begins_with.to_string());
541 /// ```
542 ///
543 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions
544 /// [`Ref`]: crate::value::Ref
545 pub fn begins_with<T>(self, prefix: T) -> Condition
546 where
547 T: Into<StringOrRef>,
548 {
549 BeginsWith::new(self, prefix).into()
550 }
551
552 /// The [DynamoDB `contains` function][1]. True if the attribute specified
553 /// by [`Path`] is one of the following:
554 /// * A `String` that contains a particular substring.
555 /// * A `Set` that contains a particular element within the set.
556 /// * A `List` that contains a particular element within the list.
557 ///
558 /// The operand must be a `String` if the attribute specified by path is a
559 /// `String`. If the attribute specified by path is a `Set`, the operand
560 /// must be the sets element type.
561 ///
562 /// See also: [`Contains`]
563 ///
564 /// ```
565 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
566 /// use dynamodb_expression::{Num, Path};
567 ///
568 /// // String
569 /// let condition = "foo".parse::<Path>()?.contains("Quinn");
570 /// assert_eq!(r#"contains(foo, "Quinn")"#, condition.to_string());
571 ///
572 /// // Number
573 /// let condition = "foo".parse::<Path>()?.contains(Num::new(42));
574 /// assert_eq!(r#"contains(foo, 42)"#, condition.to_string());
575 ///
576 /// // Binary
577 /// let condition = "foo".parse::<Path>()?.contains(Vec::<u8>::from("fish"));
578 /// assert_eq!(r#"contains(foo, "ZmlzaA==")"#, condition.to_string());
579 /// #
580 /// # Ok(())
581 /// # }
582 /// ```
583 ///
584 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions
585 pub fn contains<V>(self, operand: V) -> Condition
586 where
587 V: Into<Value>,
588 {
589 Contains::new(self, operand).into()
590 }
591
592 /// The [DynamoDB `size` function][1]. Returns a number representing an attributes size.
593 ///
594 /// ```
595 /// use dynamodb_expression::{Num, Path};
596 /// # use pretty_assertions::assert_eq;
597 ///
598 /// let condition = Path::new_name("foo").size().greater_than(Num::new(0));
599 /// assert_eq!("size(foo) > 0", condition.to_string());
600 /// ```
601 ///
602 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions
603 pub fn size(self) -> Size {
604 self.into()
605 }
606}
607
608/// Methods related to building update expressions.
609///
610/// See also: [`Update`]
611///
612/// [`Update`]: crate::update::Update
613impl Path {
614 #[deprecated(since = "0.2.0-beta.6", note = "Use `.set(value)` instead")]
615 pub fn assign<T>(self, value: T) -> Assign
616 where
617 T: Into<Value>,
618 {
619 self.set(value)
620 }
621
622 /// Represents assigning a value of a [attribute][1], [list][2], or [map][3].
623 ///
624 /// See also: [`Update`]
625 ///
626 /// ```
627 /// use dynamodb_expression::{Num, Path, update::Update};
628 /// # use pretty_assertions::assert_eq;
629 ///
630 /// let assign = Path::new_name("name").set("Jill");
631 /// assert_eq!(r#"name = "Jill""#, assign.to_string());
632 ///
633 /// let update = Update::from(assign);
634 /// assert_eq!(r#"SET name = "Jill""#, update.to_string());
635 /// ```
636 ///
637 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.ModifyingAttributes
638 /// [2]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.AddingListElements
639 /// [3]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.AddingNestedMapAttributes
640 /// [`Update`]: crate::update::Update
641 pub fn set<T>(self, value: T) -> Assign
642 where
643 T: Into<Value>,
644 {
645 Assign::new(self, value)
646 }
647
648 /// Use for doing [math on a numeric attribute][1].
649 ///
650 /// Sets this as the destination in a [`Math`] builder.
651 ///
652 /// See also: [`Update`]
653 ///
654 /// # Examples
655 ///
656 /// ```
657 /// use dynamodb_expression::{Path, update::Update};
658 /// # use pretty_assertions::assert_eq;
659 ///
660 /// let math = Path::new_name("foo").math().add(4);
661 /// assert_eq!("foo = foo + 4", math.to_string());
662 ///
663 /// let math = Path::new_name("foo").math().src(Path::new_name("bar")).sub(7);
664 /// assert_eq!("foo = bar - 7", math.to_string());
665 ///
666 /// let update = Update::from(math);
667 /// assert_eq!("SET foo = bar - 7", update.to_string());
668 /// ```
669 ///
670 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.IncrementAndDecrement
671 /// [`Update`]: crate::update::Update
672 pub fn math(self) -> MathBuilder {
673 Math::builder(self)
674 }
675
676 /// Represents an update expression to [append elements to a list][1].
677 ///
678 /// See also: [`Update`]
679 ///
680 /// # Examples
681 ///
682 /// ```
683 /// use dynamodb_expression::{Num, Path, update::Update};
684 /// # use pretty_assertions::assert_eq;
685 ///
686 /// let list_append = "foo".parse::<Path>().unwrap().list_append().list([7, 8, 9].map(Num::new));
687 /// assert_eq!("foo = list_append(foo, [7, 8, 9])", list_append.to_string());
688 ///
689 /// let update = Update::from(list_append);
690 /// assert_eq!("SET foo = list_append(foo, [7, 8, 9])", update.to_string());
691 /// ```
692 ///
693 /// If you want to add the new values to the _beginning_ of the list instead,
694 /// use the [`.before()`] method.
695 /// ```
696 /// use dynamodb_expression::{Num, Path, update::Update};
697 /// # use pretty_assertions::assert_eq;
698 ///
699 /// let list_append = "foo".parse::<Path>().unwrap().list_append().before().list([1, 2, 3].map(Num::new));
700 /// assert_eq!("foo = list_append([1, 2, 3], foo)", list_append.to_string());
701 ///
702 /// let update = Update::from(list_append);
703 /// assert_eq!("SET foo = list_append([1, 2, 3], foo)", update.to_string());
704 /// ```
705 ///
706 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.UpdatingListElements
707 /// [`Update`]: crate::update::Update
708 /// [`.before()`]: ListAppendBuilder::before
709 pub fn list_append(self) -> ListAppendBuilder {
710 ListAppend::builder(self)
711 }
712
713 /// Represents an update expression to [set an attribute if it doesn't exist][1].
714 ///
715 /// See also: [`Update`]
716 ///
717 /// # Examples
718 ///
719 /// ```
720 /// use dynamodb_expression::{Num, Path, update::Update};
721 /// # use pretty_assertions::assert_eq;
722 ///
723 /// let if_not_exists = "foo".parse::<Path>().unwrap().if_not_exists().set(Num::new(7));
724 /// assert_eq!("foo = if_not_exists(foo, 7)", if_not_exists.to_string());
725 ///
726 /// let update = Update::from(if_not_exists);
727 /// assert_eq!("SET foo = if_not_exists(foo, 7)", update.to_string());
728 /// ```
729 ///
730 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.PreventingAttributeOverwrites
731 /// [`Update`]: crate::update::Update
732 pub fn if_not_exists(self) -> IfNotExistsBuilder {
733 IfNotExists::builder(self)
734 }
735
736 /// Creates a [`DELETE` statement for an update expression][1], for removing
737 /// one or more items from a value that is a [set][2].
738 ///
739 /// See also: [`Update`]
740 ///
741 /// # Examples
742 ///
743 /// ```
744 /// use dynamodb_expression::{Path, update::Update, value::StringSet};
745 /// # use pretty_assertions::assert_eq;
746 ///
747 /// let delete = Path::new_name("foo").delete(StringSet::new(["a", "b", "c"]));
748 /// assert_eq!(r#"DELETE foo ["a", "b", "c"]"#, delete.to_string());
749 ///
750 /// let update = Update::from(delete);
751 /// assert_eq!(r#"DELETE foo ["a", "b", "c"]"#, update.to_string());
752 /// ```
753 ///
754 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.DELETE
755 /// [2]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes
756 /// [`Update`]: crate::update::Update
757 pub fn delete<T>(self, set: T) -> Delete
758 where
759 T: Into<value::Set>,
760 {
761 Delete::new(self, set)
762 }
763
764 /// Represents an DynamoDB [`ADD` statement][1] in an [update expression][2].
765 ///
766 /// The [DynamoDB documentation recommends][1] against using `ADD`:
767 ///
768 /// > In general, we recommend using `SET` rather than `ADD`.
769 ///
770 /// To increment or decrement a number value, use [`Path::math`].
771 ///
772 /// To append items to a list, use [`Path::list_append`].
773 ///
774 /// See also: [`AddValue`], [`Update`], [`Set`]
775 ///
776 /// # Examples
777 ///
778 /// ```
779 /// use dynamodb_expression::{Num, Path, update::Update};
780 /// # use pretty_assertions::assert_eq;
781 ///
782 /// let add = Path::new_name("foo").add(Num::from(1));
783 /// assert_eq!("ADD foo 1", add.to_string());
784 ///
785 /// let update = Update::from(add);
786 /// assert_eq!("ADD foo 1", update.to_string());
787 /// ```
788 ///
789 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.ADD
790 /// [2]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html
791 /// [`Update`]: crate::update::Update
792 /// [`Set`]: crate::update::Set
793 #[allow(clippy::should_implement_trait)]
794 pub fn add<T>(self, value: T) -> Add
795 where
796 T: Into<AddValue>,
797 {
798 Add::new(self, value)
799 }
800
801 /// Creates an update expression to [remove attributes from an item][1], or
802 /// [elements from a list][2].
803 ///
804 /// See also: [`Remove`], [`Update`]
805 ///
806 /// # Examples
807 ///
808 /// ```
809 /// use dynamodb_expression::{Path, update::Update};
810 /// # use pretty_assertions::assert_eq;
811 ///
812 /// let remove = Path::new_name("foo").remove();
813 /// assert_eq!(r#"REMOVE foo"#, remove.to_string());
814 ///
815 /// let update = Update::from(remove);
816 /// assert_eq!(r#"REMOVE foo"#, update.to_string());
817 ///
818 /// let remove = Path::new_indexed_field("foo", [8]).remove();
819 /// assert_eq!(r#"REMOVE foo[8]"#, remove.to_string());
820 ///
821 /// let update = Update::from(remove);
822 /// assert_eq!(r#"REMOVE foo[8]"#, update.to_string());
823 /// ```
824 ///
825 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.REMOVE
826 /// [2]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.REMOVE.RemovingListElements
827 /// [`Update`]: crate::update::Update
828 pub fn remove(self) -> Remove {
829 self.into()
830 }
831}
832
833impl Path {
834 /// Turns this [`Path`] into a [`Key`], for building a [key condition expression][1].
835 ///
836 /// See also: [`Key`]
837 ///
838 /// ```
839 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
840 /// use dynamodb_expression::{key::KeyCondition, Expression, Num, Path};
841 ///
842 /// let key_condition: KeyCondition = "id"
843 /// .parse::<Path>()?
844 /// .key()
845 /// .equal(Num::new(42))
846 /// .and("category".parse::<Path>()?.key().begins_with("hardware."));
847 ///
848 /// let expression = Expression::builder().with_key_condition(key_condition).build();
849 /// # _ = expression;
850 /// #
851 /// # Ok(())
852 /// # }
853 /// ```
854 ///
855 /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.KeyConditionExpressions.html
856 pub fn key(self) -> Key {
857 self.into()
858 }
859}
860
861impl fmt::Display for Path {
862 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
863 let mut first = true;
864 self.elements.iter().try_for_each(|elem| {
865 if first {
866 first = false;
867 } else {
868 f.write_char('.')?;
869 }
870
871 elem.fmt(f)
872 })
873 }
874}
875
876impl<T> ops::Add<T> for Path
877where
878 T: Into<Path>,
879{
880 type Output = Self;
881
882 /// Allows for using the `+` operator to combine two [`Path`]s.
883 ///
884 /// Notice that each of these examples produces the same path:
885 /// `foo[3][7].bar[2].baz`
886 ///
887 /// ```
888 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
889 /// use dynamodb_expression::{path::{Element, Name}, Path};
890 /// # use pretty_assertions::assert_eq;
891 ///
892 /// # let expected: Path= "foo[3][7].bar[2].baz".parse()?;
893 /// #
894 /// let path: Path = "foo[3][7]".parse()?;
895 /// let path = path + "bar[2].baz".parse::<Path>()?;
896 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
897 /// # assert_eq!(expected, path);
898 ///
899 /// // You can `+` anything that is `Into<Path>` (or `Into<Element>`)
900 ///
901 /// let path = Path::new_indexed_field("foo", [3, 7]) +
902 /// Element::new_indexed_field("bar", 2) +
903 /// Element::new_name("baz");
904 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
905 /// # assert_eq!(expected, path);
906 ///
907 /// let path = Path::from(("foo", [3, 7])) +
908 /// ("bar", 2) +
909 /// Name::from("baz");
910 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
911 /// # assert_eq!(expected, path);
912 /// #
913 /// # Ok(())
914 /// # }
915 /// ```
916 fn add(mut self, rhs: T) -> Self::Output {
917 self.append(rhs.into());
918 self
919 }
920}
921
922impl<T> ops::AddAssign<T> for Path
923where
924 T: Into<Path>,
925{
926 /// Allows for using the `+=` operator to combine two [`Path`]s.
927 ///
928 /// Notice that each of these examples produces the same path:
929 /// `foo[3][7].bar[2].baz`
930 ///
931 /// ```
932 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
933 /// use dynamodb_expression::{path::{Element, Name}, Path};
934 /// # use pretty_assertions::assert_eq;
935 ///
936 /// # let expected: Path= "foo[3][7].bar[2].baz".parse()?;
937 /// #
938 /// let mut path: Path = "foo[3][7]".parse()?;
939 /// path += "bar[2].baz".parse::<Path>()?;
940 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
941 /// # assert_eq!(expected, path);
942 ///
943 /// // You can `+=` anything that is `Into<Path>` (or `Into<Element>`)
944 ///
945 /// let mut path = Path::default();
946 /// path += Path::new_indexed_field("foo", [3, 7]);
947 /// path += Element::new_indexed_field("bar", 2);
948 /// path += Element::new_name("baz");
949 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
950 /// # assert_eq!(expected, path);
951 ///
952 /// let mut path = Path::default();
953 /// path += Path::from(("foo", [3, 7]));
954 /// path += ("bar", 2);
955 /// path += Name::from("baz");
956 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
957 /// # assert_eq!(expected, path);
958 /// #
959 /// # Ok(())
960 /// # }
961 /// ```
962 fn add_assign(&mut self, rhs: T) {
963 self.append(rhs.into());
964 }
965}
966
967impl<T> From<T> for Path
968where
969 T: Into<Element>,
970{
971 fn from(value: T) -> Self {
972 Path {
973 elements: vec![value.into()],
974 }
975 }
976}
977
978impl<T> FromIterator<T> for Path
979where
980 T: Into<Path>,
981{
982 /// Comines multiple items into a single [`Path`], with each element
983 /// separated by `.`. Items must be `Into<Path>`.
984 ///
985 /// Notice that each of these examples produces the same [`Path`]:
986 /// `foo[3][7].bar[2].baz`
987 ///
988 /// ```
989 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
990 /// use dynamodb_expression::{path::Element, Path};
991 /// # use pretty_assertions::assert_eq;
992 /// #
993 /// # let expected = Path::from_iter([
994 /// # Element::new_indexed_field("foo", [3, 7]),
995 /// # Element::new_indexed_field("bar", 2),
996 /// # Element::new_name("baz"),
997 /// # ]);
998 ///
999 /// // `Path` items
1000 /// let path: Path = [
1001 /// "foo[3][7]".parse::<Path>()?,
1002 /// "bar[2]".parse::<Path>()?,
1003 /// "baz".parse::<Path>()?,
1004 /// ]
1005 /// .into_iter()
1006 /// .collect();
1007 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
1008 /// # assert_eq!(expected, path);
1009 ///
1010 /// // `Element` items
1011 /// let path: Path = [
1012 /// Element::new_indexed_field("foo", [3, 7]),
1013 /// Element::new_indexed_field("bar", 2),
1014 /// Element::new_name("baz"),
1015 /// ]
1016 /// .into_iter()
1017 /// .collect();
1018 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
1019 /// # assert_eq!(expected, path);
1020 ///
1021 /// // `Into<Element>` items
1022 /// let path: Path = [
1023 /// ("foo", vec![3, 7]),
1024 /// ("bar", vec![2]),
1025 /// ("baz", vec![]),
1026 /// ]
1027 /// .into_iter()
1028 /// .collect();
1029 /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
1030 /// # assert_eq!(expected, path);
1031 /// #
1032 /// # Ok(())
1033 /// # }
1034 /// ```
1035 fn from_iter<I>(iter: I) -> Self
1036 where
1037 I: IntoIterator<Item = T>,
1038 {
1039 Self {
1040 elements: iter
1041 .into_iter()
1042 .map(Into::into)
1043 .flat_map(|path| path.elements)
1044 .collect(),
1045 }
1046 }
1047}
1048
1049impl FromStr for Path {
1050 type Err = PathParseError;
1051
1052 fn from_str(s: &str) -> Result<Self, Self::Err> {
1053 Ok(Self {
1054 elements: s.split('.').map(Element::from_str).try_collect()?,
1055 })
1056 }
1057}
1058
1059impl From<Path> for String {
1060 fn from(path: Path) -> Self {
1061 path.elements
1062 .into_iter()
1063 .map(String::from)
1064 .collect_vec()
1065 .join(".")
1066 }
1067}
1068
1069impl From<Path> for Vec<Element> {
1070 fn from(path: Path) -> Self {
1071 path.elements
1072 }
1073}
1074
1075impl TryFrom<Path> for Name {
1076 type Error = Path;
1077
1078 /// A [`Path`] consisting of a single, unindexed attribute can be converted
1079 /// into a [`Name`].
1080 /// ```
1081 /// # use dynamodb_expression::path::{Element, Name, Path};
1082 /// #
1083 /// let path: Path = "foo".parse().unwrap();
1084 /// let name = Name::try_from(path).unwrap();
1085 /// assert_eq!(Name::from("foo"), name);
1086 /// ```
1087 ///
1088 /// If the [`Path`] has indexes, or has sub-attributes, it cannot be
1089 /// converted, and the original [`Path`] is returned.
1090 /// ```
1091 /// # use dynamodb_expression::path::{Element, Name, Path};
1092 /// #
1093 /// let path: Path = "foo[0]".parse().unwrap();
1094 /// let err = Name::try_from(path.clone()).unwrap_err();
1095 /// assert_eq!(path, err);
1096 ///
1097 /// let path: Path = "foo.bar".parse().unwrap();
1098 /// let err = Name::try_from(path.clone()).unwrap_err();
1099 /// assert_eq!(path, err);
1100 /// ```
1101 fn try_from(path: Path) -> Result<Self, Self::Error> {
1102 let element: [_; 1] = path
1103 .elements
1104 .try_into()
1105 .map_err(|elements| Path { elements })?;
1106
1107 if let [Element::Name(name)] = element {
1108 Ok(name)
1109 } else {
1110 Err(Path {
1111 elements: element.into(),
1112 })
1113 }
1114 }
1115}
1116
1117/// A [`Path`] (or [`Element`] of a path) failed to parse.
1118#[derive(Debug, PartialEq, Eq)]
1119pub struct PathParseError;
1120
1121impl std::error::Error for PathParseError {}
1122
1123impl fmt::Display for PathParseError {
1124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1125 f.write_str("invalid document path")
1126 }
1127}
1128
1129#[cfg(test)]
1130mod test {
1131 use pretty_assertions::assert_eq;
1132
1133 use crate::Num;
1134
1135 use super::{Element, Name, Path, PathParseError};
1136
1137 #[test]
1138 fn parse_path() {
1139 let path: Path = "foo".parse().unwrap();
1140 assert_eq!(Path::from(Name::from("foo")), path);
1141
1142 let path: Path = "foo[0]".parse().unwrap();
1143 assert_eq!(Path::from(("foo", 0)), path);
1144
1145 let path: Path = "foo[0][3]".parse().unwrap();
1146 assert_eq!(Path::from(("foo", [0, 3])), path);
1147
1148 let path: Path = "foo[42][37][9]".parse().unwrap();
1149 assert_eq!(Path::from(("foo", [42, 37, 9])), path);
1150
1151 let path: Path = "foo.bar".parse().unwrap();
1152 assert_eq!(Path::from_iter(["foo", "bar"].map(Name::from)), path);
1153
1154 let path: Path = "foo[42].bar".parse().unwrap();
1155 assert_eq!(
1156 Path::from_iter([
1157 Element::new_indexed_field("foo", 42),
1158 Element::new_name("bar")
1159 ]),
1160 path
1161 );
1162
1163 let path: Path = "foo.bar[37]".parse().unwrap();
1164 assert_eq!(
1165 Path::from_iter([
1166 Element::new_name("foo"),
1167 Element::new_indexed_field("bar", 37)
1168 ]),
1169 path
1170 );
1171
1172 let path: Path = "foo[42].bar[37]".parse().unwrap();
1173 assert_eq!(Path::from_iter([("foo", 42), ("bar", 37)]), path);
1174
1175 let path: Path = "foo[42][7].bar[37]".parse().unwrap();
1176 assert_eq!(
1177 Path::from_iter([("foo", vec![42, 7]), ("bar", vec![37])]),
1178 path
1179 );
1180
1181 let path: Path = "foo[42].bar[37][9]".parse().unwrap();
1182 assert_eq!(
1183 Path::from_iter([("foo", vec![42]), ("bar", vec![37, 9])]),
1184 path
1185 );
1186
1187 let path: Path = "foo[42][7].bar[37][9]".parse().unwrap();
1188 assert_eq!(Path::from_iter([("foo", [42, 7]), ("bar", [37, 9])]), path);
1189
1190 for prefix in ["foo", "foo[0]", "foo.bar", "foo[0]bar", "foo[0]bar[1]"] {
1191 for bad_index in ["[9", "[]", "][", "[", "]"] {
1192 let input = format!("{prefix}{bad_index}");
1193
1194 match input.parse::<Path>() {
1195 Ok(path) => {
1196 panic!("Should not have parsed invalid input {input:?} into: {path:?}");
1197 }
1198 Err(PathParseError) => { /* Got the expected error */ }
1199 }
1200 }
1201 }
1202
1203 // A few other odds and ends not covered above.
1204
1205 // Missing the '.' between elements.
1206 "foo[0]bar".parse::<Path>().unwrap_err();
1207 "foo[0]bar[3]".parse::<Path>().unwrap_err();
1208
1209 // A stray index without a name for the field.
1210 "[0]".parse::<Path>().unwrap_err();
1211 }
1212
1213 /// Demonstration/proof of how a [`Path`] can be expressed to prove usability.
1214 #[test]
1215 fn express_path() {
1216 let _: Element = ("foo", 0).into();
1217 let _: Path = ("foo", 0).into();
1218 }
1219
1220 #[test]
1221 fn display_name() {
1222 let path = Element::new_name("foo");
1223 assert_eq!("foo", path.to_string());
1224 }
1225
1226 #[test]
1227 fn display_indexed() {
1228 // Also tests that `Element::new_indexed_field()` can accept a few different types of input.
1229
1230 // From a usize
1231 let path = Element::new_indexed_field("foo", 42);
1232 assert_eq!("foo[42]", path.to_string());
1233
1234 // From an array of usize
1235 let path = Element::new_indexed_field("foo", [42]);
1236 assert_eq!("foo[42]", path.to_string());
1237
1238 // From a slice of usize
1239 let path = Element::new_indexed_field("foo", &([42, 37, 9])[..]);
1240 assert_eq!("foo[42][37][9]", path.to_string());
1241 }
1242
1243 #[test]
1244 fn display_path() {
1245 let path: Path = ["foo", "bar"].into_iter().map(Name::from).collect();
1246 assert_eq!("foo.bar", path.to_string());
1247
1248 let path = Path::from_iter([
1249 Element::new_name("foo"),
1250 Element::new_indexed_field("bar", 42),
1251 ]);
1252 assert_eq!("foo.bar[42]", path.to_string());
1253
1254 // TODO: I'm not sure this is a legal path based on these examples:
1255 // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Attributes.html#Expressions.Attributes.NestedElements.DocumentPathExamples
1256 // Test whether it's valid and remove this comment or handle it appropriately.
1257 let path = Path::from_iter([
1258 Element::new_indexed_field("foo", 42),
1259 Element::new_name("bar"),
1260 ]);
1261 assert_eq!("foo[42].bar", path.to_string());
1262 }
1263
1264 #[test]
1265 fn size() {
1266 assert_eq!(
1267 "size(a) = 0",
1268 "a".parse::<Path>()
1269 .unwrap()
1270 .size()
1271 .equal(Num::new(0))
1272 .to_string()
1273 );
1274 }
1275
1276 #[test]
1277 fn begins_with_string() {
1278 let begins_with = Path::new_indexed_field("foo", 3).begins_with("foo");
1279 assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
1280
1281 let begins_with = Path::new_indexed_field("foo", 3).begins_with(String::from("foo"));
1282 assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
1283
1284 #[allow(clippy::needless_borrows_for_generic_args)]
1285 let begins_with = Path::new_indexed_field("foo", 3).begins_with(&String::from("foo"));
1286 assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
1287
1288 #[allow(clippy::needless_borrows_for_generic_args)]
1289 let begins_with = Path::new_indexed_field("foo", 3).begins_with(&"foo");
1290 assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
1291 }
1292
1293 #[test]
1294 fn begins_with_value_ref() {
1295 use crate::{path::Path, value::Ref};
1296
1297 let begins_with = Path::new_indexed_field("foo", 3).begins_with(Ref::new("prefix"));
1298 assert_eq!("begins_with(foo[3], :prefix)", begins_with.to_string());
1299 }
1300
1301 #[test]
1302 fn in_() {
1303 use crate::Path;
1304
1305 let condition = Path::new_name("name").in_(["Jack", "Jill"]);
1306 assert_eq!(r#"name IN ("Jack","Jill")"#, condition.to_string());
1307 }
1308
1309 #[test]
1310 fn contains() {
1311 // String
1312 let condition = Path::new_name("foo").contains("Quinn");
1313 assert_eq!(r#"contains(foo, "Quinn")"#, condition.to_string());
1314
1315 // Number
1316 let condition = Path::new_name("foo").contains(Num::new(42));
1317 assert_eq!(r#"contains(foo, 42)"#, condition.to_string());
1318
1319 // Binary
1320 let condition = Path::new_name("foo").contains(b"fish".to_vec());
1321 assert_eq!(r#"contains(foo, "ZmlzaA==")"#, condition.to_string());
1322 }
1323
1324 #[test]
1325 fn empty() {
1326 assert!(Path::default().is_empty());
1327 assert!(Path::from_iter(Vec::<Element>::new()).is_empty());
1328 // TODO: Uncomment this when `Path::from_iter(Vec<Path>)` works.
1329 // assert!(Path::from_iter(Vec::<Path>::new()).is_empty());
1330 }
1331
1332 #[test]
1333 fn from_iter() {
1334 let path = Path::from_iter(["foo", "bar"].map(Name::from));
1335 assert_eq!("foo.bar", path.to_string());
1336 assert_eq!(
1337 vec![Element::new_name("foo"), Element::new_name("bar")],
1338 path.elements
1339 );
1340
1341 let path = Path::from_iter([("foo", 42), ("bar", 37)]);
1342 assert_eq!("foo[42].bar[37]", path.to_string());
1343 assert_eq!(
1344 vec![
1345 Element::new_indexed_field("foo", 42),
1346 Element::new_indexed_field("bar", 37),
1347 ],
1348 path.elements
1349 );
1350
1351 let path = Path::from_iter([("foo", vec![42, 7]), ("bar", vec![37])]);
1352 assert_eq!("foo[42][7].bar[37]", path.to_string());
1353 assert_eq!(
1354 vec![
1355 Element::new_indexed_field("foo", [42, 7]),
1356 Element::new_indexed_field("bar", 37),
1357 ],
1358 path.elements
1359 );
1360
1361 let path = Path::from_iter([("foo", [42, 7]), ("bar", [37, 9])]);
1362 assert_eq!("foo[42][7].bar[37][9]", path.to_string());
1363 assert_eq!(
1364 vec![
1365 Element::new_indexed_field("foo", [42, 7]),
1366 Element::new_indexed_field("bar", [37, 9]),
1367 ],
1368 path.elements
1369 );
1370
1371 let path = Path::from_iter([
1372 Element::new_name("foo"),
1373 Element::new_indexed_field("bar", 42),
1374 ]);
1375 assert_eq!("foo.bar[42]", path.to_string());
1376 assert_eq!(
1377 vec![
1378 Element::new_name("foo"),
1379 Element::new_indexed_field("bar", 42),
1380 ],
1381 path.elements
1382 );
1383
1384 let path = Path::from_iter([
1385 "foo.bar[42]".parse::<Path>().unwrap(),
1386 "baz.quux".parse::<Path>().unwrap(),
1387 ]);
1388 assert_eq!("foo.bar[42].baz.quux", path.to_string());
1389 assert_eq!(
1390 vec![
1391 Element::new_name("foo"),
1392 Element::new_indexed_field("bar", 42),
1393 Element::new_name("baz"),
1394 Element::new_name("quux"),
1395 ],
1396 path.elements
1397 );
1398 }
1399
1400 #[test]
1401 fn add() -> Result<(), Box<dyn std::error::Error>> {
1402 let path = "foo".parse::<Path>()? + Name::from("bar");
1403 assert_eq!("foo.bar", path.to_string());
1404 assert_eq!(
1405 vec![Element::new_name("foo"), Element::new_name("bar")],
1406 path.elements
1407 );
1408
1409 let path = "foo[42]".parse::<Path>()? + ("bar", 37);
1410 assert_eq!("foo[42].bar[37]", path.to_string());
1411 assert_eq!(
1412 vec![
1413 Element::new_indexed_field("foo", 42),
1414 Element::new_indexed_field("bar", 37),
1415 ],
1416 path.elements
1417 );
1418
1419 let path = "foo[42][7]".parse::<Path>()? + ("bar", vec![37]);
1420 assert_eq!("foo[42][7].bar[37]", path.to_string());
1421 assert_eq!(
1422 vec![
1423 Element::new_indexed_field("foo", [42, 7]),
1424 Element::new_indexed_field("bar", 37),
1425 ],
1426 path.elements
1427 );
1428
1429 let path = "foo[42][7]".parse::<Path>()? + ("bar", [37, 9]);
1430 assert_eq!("foo[42][7].bar[37][9]", path.to_string());
1431 assert_eq!(
1432 vec![
1433 Element::new_indexed_field("foo", [42, 7]),
1434 Element::new_indexed_field("bar", [37, 9]),
1435 ],
1436 path.elements
1437 );
1438
1439 let path = "foo".parse::<Path>()? + Element::new_indexed_field("bar", 42);
1440 assert_eq!("foo.bar[42]", path.to_string());
1441 assert_eq!(
1442 vec![
1443 Element::new_name("foo"),
1444 Element::new_indexed_field("bar", 42),
1445 ],
1446 path.elements
1447 );
1448
1449 let path = "foo.bar[42]".parse::<Path>()? + "baz.quux".parse::<Path>()?;
1450 assert_eq!("foo.bar[42].baz.quux", path.to_string());
1451 assert_eq!(
1452 vec![
1453 Element::new_name("foo"),
1454 Element::new_indexed_field("bar", 42),
1455 Element::new_name("baz"),
1456 Element::new_name("quux"),
1457 ],
1458 path.elements
1459 );
1460
1461 Ok(())
1462 }
1463
1464 #[test]
1465 fn add_assign() -> Result<(), Box<dyn std::error::Error>> {
1466 let mut path = "foo".parse::<Path>()?;
1467 path += Name::from("bar");
1468 assert_eq!("foo.bar", path.to_string());
1469 assert_eq!(
1470 vec![Element::new_name("foo"), Element::new_name("bar")],
1471 path.elements
1472 );
1473
1474 let mut path = "foo[42]".parse::<Path>()?;
1475 path += ("bar", 37);
1476 assert_eq!("foo[42].bar[37]", path.to_string());
1477 assert_eq!(
1478 vec![
1479 Element::new_indexed_field("foo", 42),
1480 Element::new_indexed_field("bar", 37),
1481 ],
1482 path.elements
1483 );
1484
1485 let mut path = "foo[42][7]".parse::<Path>()?;
1486 path += ("bar", vec![37]);
1487 assert_eq!("foo[42][7].bar[37]", path.to_string());
1488 assert_eq!(
1489 vec![
1490 Element::new_indexed_field("foo", [42, 7]),
1491 Element::new_indexed_field("bar", 37),
1492 ],
1493 path.elements
1494 );
1495
1496 let mut path = "foo[42][7]".parse::<Path>()?;
1497 path += ("bar", [37, 9]);
1498 assert_eq!("foo[42][7].bar[37][9]", path.to_string());
1499 assert_eq!(
1500 vec![
1501 Element::new_indexed_field("foo", [42, 7]),
1502 Element::new_indexed_field("bar", [37, 9]),
1503 ],
1504 path.elements
1505 );
1506
1507 let mut path = "foo".parse::<Path>()?;
1508 path += Element::new_indexed_field("bar", 42);
1509 assert_eq!("foo.bar[42]", path.to_string());
1510 assert_eq!(
1511 vec![
1512 Element::new_name("foo"),
1513 Element::new_indexed_field("bar", 42),
1514 ],
1515 path.elements
1516 );
1517
1518 let mut path = "foo.bar[42]".parse::<Path>()?;
1519 path += "baz.quux".parse::<Path>()?;
1520 assert_eq!("foo.bar[42].baz.quux", path.to_string());
1521 assert_eq!(
1522 vec![
1523 Element::new_name("foo"),
1524 Element::new_indexed_field("bar", 42),
1525 Element::new_name("baz"),
1526 Element::new_name("quux"),
1527 ],
1528 path.elements
1529 );
1530
1531 Ok(())
1532 }
1533}