Skip to main content

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    #[rustversion::attr(before(1.81), allow(clippy::should_implement_trait))]
794    #[rustversion::attr(
795        since(1.81),
796        expect(
797            clippy::should_implement_trait,
798            reason = "This is for the DynamoDB `ADD` operation, not the Rust `+` operator."
799        )
800    )]
801    pub fn add<T>(self, value: T) -> Add
802    where
803        T: Into<AddValue>,
804    {
805        Add::new(self, value)
806    }
807
808    /// Creates an update expression to [remove attributes from an item][1], or
809    /// [elements from a list][2].
810    ///
811    /// See also: [`Remove`], [`Update`]
812    ///
813    /// # Examples
814    ///
815    /// ```
816    /// use dynamodb_expression::{Path, update::Update};
817    /// # use pretty_assertions::assert_eq;
818    ///
819    /// let remove = Path::new_name("foo").remove();
820    /// assert_eq!(r#"REMOVE foo"#, remove.to_string());
821    ///
822    /// let update = Update::from(remove);
823    /// assert_eq!(r#"REMOVE foo"#, update.to_string());
824    ///
825    /// let remove = Path::new_indexed_field("foo", [8]).remove();
826    /// assert_eq!(r#"REMOVE foo[8]"#, remove.to_string());
827    ///
828    /// let update = Update::from(remove);
829    /// assert_eq!(r#"REMOVE foo[8]"#, update.to_string());
830    /// ```
831    ///
832    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.REMOVE
833    /// [2]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.REMOVE.RemovingListElements
834    /// [`Update`]: crate::update::Update
835    pub fn remove(self) -> Remove {
836        self.into()
837    }
838}
839
840impl Path {
841    /// Turns this [`Path`] into a [`Key`], for building a [key condition expression][1].
842    ///
843    /// See also: [`Key`]
844    ///
845    /// ```
846    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
847    /// use dynamodb_expression::{key::KeyCondition, Expression, Num, Path};
848    ///
849    /// let key_condition: KeyCondition = "id"
850    ///     .parse::<Path>()?
851    ///     .key()
852    ///     .equal(Num::new(42))
853    ///     .and("category".parse::<Path>()?.key().begins_with("hardware."));
854    ///
855    /// let expression = Expression::builder().with_key_condition(key_condition).build();
856    /// # _ = expression;
857    /// #
858    /// # Ok(())
859    /// # }
860    /// ```
861    ///
862    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.KeyConditionExpressions.html
863    pub fn key(self) -> Key {
864        self.into()
865    }
866}
867
868impl fmt::Display for Path {
869    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
870        let mut first = true;
871        self.elements.iter().try_for_each(|elem| {
872            if first {
873                first = false;
874            } else {
875                f.write_char('.')?;
876            }
877
878            elem.fmt(f)
879        })
880    }
881}
882
883impl<T> ops::Add<T> for Path
884where
885    T: Into<Path>,
886{
887    type Output = Self;
888
889    /// Allows for using the `+` operator to combine two [`Path`]s.
890    ///
891    /// Notice that each of these examples produces the same path:
892    /// `foo[3][7].bar[2].baz`
893    ///
894    /// ```
895    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
896    /// use dynamodb_expression::{path::{Element, Name}, Path};
897    /// # use pretty_assertions::assert_eq;
898    ///
899    /// # let expected: Path= "foo[3][7].bar[2].baz".parse()?;
900    /// #
901    /// let path: Path = "foo[3][7]".parse()?;
902    /// let path = path + "bar[2].baz".parse::<Path>()?;
903    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
904    /// # assert_eq!(expected, path);
905    ///
906    /// // You can `+` anything that is `Into<Path>` (or `Into<Element>`)
907    ///
908    /// let path = Path::new_indexed_field("foo", [3, 7]) +
909    ///     Element::new_indexed_field("bar", 2) +
910    ///     Element::new_name("baz");
911    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
912    /// # assert_eq!(expected, path);
913    ///
914    /// let path = Path::from(("foo", [3, 7])) +
915    ///     ("bar", 2) +
916    ///     Name::from("baz");
917    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
918    /// # assert_eq!(expected, path);
919    /// #
920    /// # Ok(())
921    /// # }
922    /// ```
923    fn add(mut self, rhs: T) -> Self::Output {
924        self.append(rhs.into());
925        self
926    }
927}
928
929impl<T> ops::AddAssign<T> for Path
930where
931    T: Into<Path>,
932{
933    /// Allows for using the `+=` operator to combine two [`Path`]s.
934    ///
935    /// Notice that each of these examples produces the same path:
936    /// `foo[3][7].bar[2].baz`
937    ///
938    /// ```
939    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
940    /// use dynamodb_expression::{path::{Element, Name}, Path};
941    /// # use pretty_assertions::assert_eq;
942    ///
943    /// # let expected: Path= "foo[3][7].bar[2].baz".parse()?;
944    /// #
945    /// let mut path: Path = "foo[3][7]".parse()?;
946    /// path += "bar[2].baz".parse::<Path>()?;
947    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
948    /// # assert_eq!(expected, path);
949    ///
950    /// // You can `+=` anything that is `Into<Path>` (or `Into<Element>`)
951    ///
952    /// let mut path = Path::default();
953    /// path += Path::new_indexed_field("foo", [3, 7]);
954    /// path += Element::new_indexed_field("bar", 2);
955    /// path += Element::new_name("baz");
956    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
957    /// # assert_eq!(expected, path);
958    ///
959    /// let mut path = Path::default();
960    /// path += Path::from(("foo", [3, 7]));
961    /// path += ("bar", 2);
962    /// path += Name::from("baz");
963    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
964    /// # assert_eq!(expected, path);
965    /// #
966    /// # Ok(())
967    /// # }
968    /// ```
969    fn add_assign(&mut self, rhs: T) {
970        self.append(rhs.into());
971    }
972}
973
974impl<T> From<T> for Path
975where
976    T: Into<Element>,
977{
978    fn from(value: T) -> Self {
979        Path {
980            elements: vec![value.into()],
981        }
982    }
983}
984
985impl<T> FromIterator<T> for Path
986where
987    T: Into<Path>,
988{
989    /// Comines multiple items into a single [`Path`], with each element
990    /// separated by `.`. Items must be `Into<Path>`.
991    ///
992    /// Notice that each of these examples produces the same [`Path`]:
993    /// `foo[3][7].bar[2].baz`
994    ///
995    /// ```
996    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
997    /// use dynamodb_expression::{path::Element, Path};
998    /// # use pretty_assertions::assert_eq;
999    /// #
1000    /// # let expected = Path::from_iter([
1001    /// #     Element::new_indexed_field("foo", [3, 7]),
1002    /// #     Element::new_indexed_field("bar", 2),
1003    /// #     Element::new_name("baz"),
1004    /// # ]);
1005    ///
1006    /// // `Path` items
1007    /// let path: Path = [
1008    ///         "foo[3][7]".parse::<Path>()?,
1009    ///         "bar[2]".parse::<Path>()?,
1010    ///         "baz".parse::<Path>()?,
1011    ///     ]
1012    ///     .into_iter()
1013    ///     .collect();
1014    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
1015    /// # assert_eq!(expected, path);
1016    ///
1017    /// // `Element` items
1018    /// let path: Path = [
1019    ///         Element::new_indexed_field("foo", [3, 7]),
1020    ///         Element::new_indexed_field("bar", 2),
1021    ///         Element::new_name("baz"),
1022    ///     ]
1023    ///     .into_iter()
1024    ///     .collect();
1025    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
1026    /// # assert_eq!(expected, path);
1027    ///
1028    /// // `Into<Element>` items
1029    /// let path: Path = [
1030    ///         ("foo", vec![3, 7]),
1031    ///         ("bar", vec![2]),
1032    ///         ("baz", vec![]),
1033    ///     ]
1034    ///     .into_iter()
1035    ///     .collect();
1036    /// assert_eq!("foo[3][7].bar[2].baz", path.to_string());
1037    /// # assert_eq!(expected, path);
1038    /// #
1039    /// # Ok(())
1040    /// # }
1041    /// ```
1042    fn from_iter<I>(iter: I) -> Self
1043    where
1044        I: IntoIterator<Item = T>,
1045    {
1046        Self {
1047            elements: iter
1048                .into_iter()
1049                .map(Into::into)
1050                .flat_map(|path| path.elements)
1051                .collect(),
1052        }
1053    }
1054}
1055
1056impl FromStr for Path {
1057    type Err = PathParseError;
1058
1059    fn from_str(s: &str) -> Result<Self, Self::Err> {
1060        Ok(Self {
1061            elements: s.split('.').map(Element::from_str).try_collect()?,
1062        })
1063    }
1064}
1065
1066impl From<Path> for String {
1067    fn from(path: Path) -> Self {
1068        path.elements
1069            .into_iter()
1070            .map(String::from)
1071            .collect_vec()
1072            .join(".")
1073    }
1074}
1075
1076impl From<Path> for Vec<Element> {
1077    fn from(path: Path) -> Self {
1078        path.elements
1079    }
1080}
1081
1082impl TryFrom<Path> for Name {
1083    type Error = Path;
1084
1085    /// A [`Path`] consisting of a single, unindexed attribute can be converted
1086    /// into a [`Name`].
1087    /// ```
1088    /// # use dynamodb_expression::path::{Element, Name, Path};
1089    /// #
1090    /// let path: Path = "foo".parse().unwrap();
1091    /// let name = Name::try_from(path).unwrap();
1092    /// assert_eq!(Name::from("foo"), name);
1093    /// ```
1094    ///
1095    /// If the [`Path`] has indexes, or has sub-attributes, it cannot be
1096    /// converted, and the original [`Path`] is returned.
1097    /// ```
1098    /// # use dynamodb_expression::path::{Element, Name, Path};
1099    /// #
1100    /// let path: Path = "foo[0]".parse().unwrap();
1101    /// let err = Name::try_from(path.clone()).unwrap_err();
1102    /// assert_eq!(path, err);
1103    ///
1104    /// let path: Path = "foo.bar".parse().unwrap();
1105    /// let err = Name::try_from(path.clone()).unwrap_err();
1106    /// assert_eq!(path, err);
1107    /// ```
1108    fn try_from(path: Path) -> Result<Self, Self::Error> {
1109        let element: [_; 1] = path
1110            .elements
1111            .try_into()
1112            .map_err(|elements| Path { elements })?;
1113
1114        if let [Element::Name(name)] = element {
1115            Ok(name)
1116        } else {
1117            Err(Path {
1118                elements: element.into(),
1119            })
1120        }
1121    }
1122}
1123
1124/// A [`Path`] (or [`Element`] of a path) failed to parse.
1125#[derive(Debug, PartialEq, Eq)]
1126pub struct PathParseError;
1127
1128impl std::error::Error for PathParseError {}
1129
1130impl fmt::Display for PathParseError {
1131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1132        f.write_str("invalid document path")
1133    }
1134}
1135
1136#[cfg(test)]
1137mod test {
1138    use pretty_assertions::assert_eq;
1139
1140    use crate::Num;
1141
1142    use super::{Element, Name, Path, PathParseError};
1143
1144    #[test]
1145    fn parse_path() {
1146        let path: Path = "foo".parse().unwrap();
1147        assert_eq!(Path::from(Name::from("foo")), path);
1148
1149        let path: Path = "foo[0]".parse().unwrap();
1150        assert_eq!(Path::from(("foo", 0)), path);
1151
1152        let path: Path = "foo[0][3]".parse().unwrap();
1153        assert_eq!(Path::from(("foo", [0, 3])), path);
1154
1155        let path: Path = "foo[42][37][9]".parse().unwrap();
1156        assert_eq!(Path::from(("foo", [42, 37, 9])), path);
1157
1158        let path: Path = "foo.bar".parse().unwrap();
1159        assert_eq!(Path::from_iter(["foo", "bar"].map(Name::from)), path);
1160
1161        let path: Path = "foo[42].bar".parse().unwrap();
1162        assert_eq!(
1163            Path::from_iter([
1164                Element::new_indexed_field("foo", 42),
1165                Element::new_name("bar")
1166            ]),
1167            path
1168        );
1169
1170        let path: Path = "foo.bar[37]".parse().unwrap();
1171        assert_eq!(
1172            Path::from_iter([
1173                Element::new_name("foo"),
1174                Element::new_indexed_field("bar", 37)
1175            ]),
1176            path
1177        );
1178
1179        let path: Path = "foo[42].bar[37]".parse().unwrap();
1180        assert_eq!(Path::from_iter([("foo", 42), ("bar", 37)]), path);
1181
1182        let path: Path = "foo[42][7].bar[37]".parse().unwrap();
1183        assert_eq!(
1184            Path::from_iter([("foo", vec![42, 7]), ("bar", vec![37])]),
1185            path
1186        );
1187
1188        let path: Path = "foo[42].bar[37][9]".parse().unwrap();
1189        assert_eq!(
1190            Path::from_iter([("foo", vec![42]), ("bar", vec![37, 9])]),
1191            path
1192        );
1193
1194        let path: Path = "foo[42][7].bar[37][9]".parse().unwrap();
1195        assert_eq!(Path::from_iter([("foo", [42, 7]), ("bar", [37, 9])]), path);
1196
1197        for prefix in ["foo", "foo[0]", "foo.bar", "foo[0]bar", "foo[0]bar[1]"] {
1198            for bad_index in ["[9", "[]", "][", "[", "]"] {
1199                let input = format!("{prefix}{bad_index}");
1200
1201                match input.parse::<Path>() {
1202                    Ok(path) => {
1203                        panic!("Should not have parsed invalid input {input:?} into: {path:?}");
1204                    }
1205                    Err(PathParseError) => { /* Got the expected error */ }
1206                }
1207            }
1208        }
1209
1210        // A few other odds and ends not covered above.
1211
1212        // Missing the '.' between elements.
1213        "foo[0]bar".parse::<Path>().unwrap_err();
1214        "foo[0]bar[3]".parse::<Path>().unwrap_err();
1215
1216        // A stray index without a name for the field.
1217        "[0]".parse::<Path>().unwrap_err();
1218    }
1219
1220    /// Demonstration/proof of how a [`Path`] can be expressed to prove usability.
1221    #[test]
1222    fn express_path() {
1223        let _: Element = ("foo", 0).into();
1224        let _: Path = ("foo", 0).into();
1225    }
1226
1227    #[test]
1228    fn display_path() {
1229        let path: Path = ["foo", "bar"].into_iter().map(Name::from).collect();
1230        assert_eq!("foo.bar", path.to_string());
1231
1232        let path = Path::from_iter([
1233            Element::new_name("foo"),
1234            Element::new_indexed_field("bar", 42),
1235        ]);
1236        assert_eq!("foo.bar[42]", path.to_string());
1237
1238        let path = Path::from_iter([
1239            Element::new_indexed_field("foo", 42),
1240            Element::new_name("bar"),
1241        ]);
1242        assert_eq!("foo[42].bar", path.to_string());
1243    }
1244
1245    #[test]
1246    fn size() {
1247        assert_eq!(
1248            "size(a) = 0",
1249            "a".parse::<Path>()
1250                .unwrap()
1251                .size()
1252                .equal(Num::new(0))
1253                .to_string()
1254        );
1255    }
1256
1257    #[test]
1258    fn begins_with_string() {
1259        let begins_with = Path::new_indexed_field("foo", 3).begins_with("foo");
1260        assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
1261
1262        let begins_with = Path::new_indexed_field("foo", 3).begins_with(String::from("foo"));
1263        assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
1264
1265        #[allow(clippy::needless_borrows_for_generic_args)]
1266        let begins_with = Path::new_indexed_field("foo", 3).begins_with(&String::from("foo"));
1267        assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
1268
1269        #[allow(clippy::needless_borrows_for_generic_args)]
1270        let begins_with = Path::new_indexed_field("foo", 3).begins_with(&"foo");
1271        assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
1272    }
1273
1274    #[test]
1275    fn begins_with_value_ref() {
1276        use crate::{path::Path, value::Ref};
1277
1278        let begins_with = Path::new_indexed_field("foo", 3).begins_with(Ref::new("prefix"));
1279        assert_eq!("begins_with(foo[3], :prefix)", begins_with.to_string());
1280    }
1281
1282    #[test]
1283    fn in_() {
1284        use crate::Path;
1285
1286        let condition = Path::new_name("name").in_(["Jack", "Jill"]);
1287        assert_eq!(r#"name IN ("Jack","Jill")"#, condition.to_string());
1288    }
1289
1290    #[test]
1291    fn contains() {
1292        // String
1293        let condition = Path::new_name("foo").contains("Quinn");
1294        assert_eq!(r#"contains(foo, "Quinn")"#, condition.to_string());
1295
1296        // Number
1297        let condition = Path::new_name("foo").contains(Num::new(42));
1298        assert_eq!(r#"contains(foo, 42)"#, condition.to_string());
1299
1300        // Binary
1301        let condition = Path::new_name("foo").contains(b"fish".to_vec());
1302        assert_eq!(r#"contains(foo, "ZmlzaA==")"#, condition.to_string());
1303    }
1304
1305    #[test]
1306    fn empty() {
1307        assert!(Path::default().is_empty());
1308        assert!(Path::from_iter(Vec::<Element>::new()).is_empty());
1309        assert!(Path::from_iter(Vec::<Path>::new()).is_empty());
1310    }
1311
1312    #[test]
1313    fn from_iter() {
1314        let path = Path::from_iter(["foo", "bar"].map(Name::from));
1315        assert_eq!("foo.bar", path.to_string());
1316        assert_eq!(
1317            vec![Element::new_name("foo"), Element::new_name("bar")],
1318            path.elements
1319        );
1320
1321        let path = Path::from_iter([("foo", 42), ("bar", 37)]);
1322        assert_eq!("foo[42].bar[37]", path.to_string());
1323        assert_eq!(
1324            vec![
1325                Element::new_indexed_field("foo", 42),
1326                Element::new_indexed_field("bar", 37),
1327            ],
1328            path.elements
1329        );
1330
1331        let path = Path::from_iter([("foo", vec![42, 7]), ("bar", vec![37])]);
1332        assert_eq!("foo[42][7].bar[37]", path.to_string());
1333        assert_eq!(
1334            vec![
1335                Element::new_indexed_field("foo", [42, 7]),
1336                Element::new_indexed_field("bar", 37),
1337            ],
1338            path.elements
1339        );
1340
1341        let path = Path::from_iter([("foo", [42, 7]), ("bar", [37, 9])]);
1342        assert_eq!("foo[42][7].bar[37][9]", path.to_string());
1343        assert_eq!(
1344            vec![
1345                Element::new_indexed_field("foo", [42, 7]),
1346                Element::new_indexed_field("bar", [37, 9]),
1347            ],
1348            path.elements
1349        );
1350
1351        let path = Path::from_iter([
1352            Element::new_name("foo"),
1353            Element::new_indexed_field("bar", 42),
1354        ]);
1355        assert_eq!("foo.bar[42]", path.to_string());
1356        assert_eq!(
1357            vec![
1358                Element::new_name("foo"),
1359                Element::new_indexed_field("bar", 42),
1360            ],
1361            path.elements
1362        );
1363
1364        let path = Path::from_iter([
1365            "foo.bar[42]".parse::<Path>().unwrap(),
1366            "baz.quux".parse::<Path>().unwrap(),
1367        ]);
1368        assert_eq!("foo.bar[42].baz.quux", path.to_string());
1369        assert_eq!(
1370            vec![
1371                Element::new_name("foo"),
1372                Element::new_indexed_field("bar", 42),
1373                Element::new_name("baz"),
1374                Element::new_name("quux"),
1375            ],
1376            path.elements
1377        );
1378    }
1379
1380    #[test]
1381    fn add() -> Result<(), Box<dyn std::error::Error>> {
1382        let path = "foo".parse::<Path>()? + Name::from("bar");
1383        assert_eq!("foo.bar", path.to_string());
1384        assert_eq!(
1385            vec![Element::new_name("foo"), Element::new_name("bar")],
1386            path.elements
1387        );
1388
1389        let path = "foo[42]".parse::<Path>()? + ("bar", 37);
1390        assert_eq!("foo[42].bar[37]", path.to_string());
1391        assert_eq!(
1392            vec![
1393                Element::new_indexed_field("foo", 42),
1394                Element::new_indexed_field("bar", 37),
1395            ],
1396            path.elements
1397        );
1398
1399        let path = "foo[42][7]".parse::<Path>()? + ("bar", vec![37]);
1400        assert_eq!("foo[42][7].bar[37]", path.to_string());
1401        assert_eq!(
1402            vec![
1403                Element::new_indexed_field("foo", [42, 7]),
1404                Element::new_indexed_field("bar", 37),
1405            ],
1406            path.elements
1407        );
1408
1409        let path = "foo[42][7]".parse::<Path>()? + ("bar", [37, 9]);
1410        assert_eq!("foo[42][7].bar[37][9]", path.to_string());
1411        assert_eq!(
1412            vec![
1413                Element::new_indexed_field("foo", [42, 7]),
1414                Element::new_indexed_field("bar", [37, 9]),
1415            ],
1416            path.elements
1417        );
1418
1419        let path = "foo".parse::<Path>()? + Element::new_indexed_field("bar", 42);
1420        assert_eq!("foo.bar[42]", path.to_string());
1421        assert_eq!(
1422            vec![
1423                Element::new_name("foo"),
1424                Element::new_indexed_field("bar", 42),
1425            ],
1426            path.elements
1427        );
1428
1429        let path = "foo.bar[42]".parse::<Path>()? + "baz.quux".parse::<Path>()?;
1430        assert_eq!("foo.bar[42].baz.quux", path.to_string());
1431        assert_eq!(
1432            vec![
1433                Element::new_name("foo"),
1434                Element::new_indexed_field("bar", 42),
1435                Element::new_name("baz"),
1436                Element::new_name("quux"),
1437            ],
1438            path.elements
1439        );
1440
1441        Ok(())
1442    }
1443
1444    #[test]
1445    fn add_assign() -> Result<(), Box<dyn std::error::Error>> {
1446        let mut path = "foo".parse::<Path>()?;
1447        path += Name::from("bar");
1448        assert_eq!("foo.bar", path.to_string());
1449        assert_eq!(
1450            vec![Element::new_name("foo"), Element::new_name("bar")],
1451            path.elements
1452        );
1453
1454        let mut path = "foo[42]".parse::<Path>()?;
1455        path += ("bar", 37);
1456        assert_eq!("foo[42].bar[37]", path.to_string());
1457        assert_eq!(
1458            vec![
1459                Element::new_indexed_field("foo", 42),
1460                Element::new_indexed_field("bar", 37),
1461            ],
1462            path.elements
1463        );
1464
1465        let mut path = "foo[42][7]".parse::<Path>()?;
1466        path += ("bar", vec![37]);
1467        assert_eq!("foo[42][7].bar[37]", path.to_string());
1468        assert_eq!(
1469            vec![
1470                Element::new_indexed_field("foo", [42, 7]),
1471                Element::new_indexed_field("bar", 37),
1472            ],
1473            path.elements
1474        );
1475
1476        let mut path = "foo[42][7]".parse::<Path>()?;
1477        path += ("bar", [37, 9]);
1478        assert_eq!("foo[42][7].bar[37][9]", path.to_string());
1479        assert_eq!(
1480            vec![
1481                Element::new_indexed_field("foo", [42, 7]),
1482                Element::new_indexed_field("bar", [37, 9]),
1483            ],
1484            path.elements
1485        );
1486
1487        let mut path = "foo".parse::<Path>()?;
1488        path += Element::new_indexed_field("bar", 42);
1489        assert_eq!("foo.bar[42]", path.to_string());
1490        assert_eq!(
1491            vec![
1492                Element::new_name("foo"),
1493                Element::new_indexed_field("bar", 42),
1494            ],
1495            path.elements
1496        );
1497
1498        let mut path = "foo.bar[42]".parse::<Path>()?;
1499        path += "baz.quux".parse::<Path>()?;
1500        assert_eq!("foo.bar[42].baz.quux", path.to_string());
1501        assert_eq!(
1502            vec![
1503                Element::new_name("foo"),
1504                Element::new_indexed_field("bar", 42),
1505                Element::new_name("baz"),
1506                Element::new_name("quux"),
1507            ],
1508            path.elements
1509        );
1510
1511        Ok(())
1512    }
1513}