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}