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}