mod element;
mod name;
pub use self::{
element::{Element, IndexedField, Indexes},
name::Name,
};
use core::{
fmt::{self, Write},
ops,
str::FromStr,
};
use itertools::Itertools;
use crate::{
condition::{
attribute_type::Type, equal, greater_than, greater_than_or_equal, less_than,
less_than_or_equal, not_equal, AttributeType, BeginsWith, Between, Condition, Contains, In,
},
key::Key,
operand::{Operand, Size},
update::{
if_not_exists::Builder as IfNotExistsBuilder, list_append::Builder as ListAppendBuilder,
math::Builder as MathBuilder, Add, AddValue, Assign, Delete, IfNotExists, ListAppend, Math,
Remove,
},
value::{self, StringOrRef, Value},
};
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Path {
pub(crate) elements: Vec<Element>,
}
impl Path {
pub fn new_name<T>(name: T) -> Self
where
T: Into<Name>,
{
Self {
elements: vec![Element::new_name(name)],
}
}
pub fn new_indexed_field<N, I>(name: N, indexes: I) -> Self
where
N: Into<Name>,
I: Indexes,
{
Self {
elements: vec![Element::new_indexed_field(name, indexes)],
}
}
pub fn append(&mut self, mut other: Path) {
self.elements.append(&mut other.elements)
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
}
impl Path {
pub fn equal<T>(self, right: T) -> Condition
where
T: Into<Operand>,
{
equal(self, right).into()
}
pub fn not_equal<T>(self, right: T) -> Condition
where
T: Into<Operand>,
{
not_equal(self, right).into()
}
pub fn greater_than<T>(self, right: T) -> Condition
where
T: Into<Operand>,
{
greater_than(self, right).into()
}
pub fn greater_than_or_equal<T>(self, right: T) -> Condition
where
T: Into<Operand>,
{
greater_than_or_equal(self, right).into()
}
pub fn less_than<T>(self, right: T) -> Condition
where
T: Into<Operand>,
{
less_than(self, right).into()
}
pub fn less_than_or_equal<T>(self, right: T) -> Condition
where
T: Into<Operand>,
{
less_than_or_equal(self, right).into()
}
pub fn between<L, U>(self, lower: L, upper: U) -> Condition
where
L: Into<Operand>,
U: Into<Operand>,
{
Condition::Between(Between {
op: self.into(),
lower: lower.into(),
upper: upper.into(),
})
}
pub fn in_<I, T>(self, items: I) -> Condition
where
I: IntoIterator<Item = T>,
T: Into<Operand>,
{
In::new(self, items).into()
}
pub fn attribute_exists(self) -> Condition {
Condition::AttributeExists(self.into())
}
pub fn attribute_not_exists(self) -> Condition {
Condition::AttributeNotExists(self.into())
}
pub fn attribute_type(self, attribute_type: Type) -> Condition {
AttributeType::new(self, attribute_type).into()
}
pub fn begins_with<T>(self, prefix: T) -> Condition
where
T: Into<StringOrRef>,
{
BeginsWith::new(self, prefix).into()
}
pub fn contains<V>(self, operand: V) -> Condition
where
V: Into<Value>,
{
Contains::new(self, operand).into()
}
pub fn size(self) -> Size {
self.into()
}
}
impl Path {
#[deprecated(since = "0.2.0-beta.6", note = "Use `.set(value)` instead")]
pub fn assign<T>(self, value: T) -> Assign
where
T: Into<Value>,
{
self.set(value)
}
pub fn set<T>(self, value: T) -> Assign
where
T: Into<Value>,
{
Assign::new(self, value)
}
pub fn math(self) -> MathBuilder {
Math::builder(self)
}
pub fn list_append(self) -> ListAppendBuilder {
ListAppend::builder(self)
}
pub fn if_not_exists(self) -> IfNotExistsBuilder {
IfNotExists::builder(self)
}
pub fn delete<T>(self, set: T) -> Delete
where
T: Into<value::Set>,
{
Delete::new(self, set)
}
#[allow(clippy::should_implement_trait)]
pub fn add<T>(self, value: T) -> Add
where
T: Into<AddValue>,
{
Add::new(self, value)
}
pub fn remove(self) -> Remove {
self.into()
}
}
impl Path {
pub fn key(self) -> Key {
self.into()
}
}
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
self.elements.iter().try_for_each(|elem| {
if first {
first = false;
} else {
f.write_char('.')?;
}
elem.fmt(f)
})
}
}
impl<T> ops::Add<T> for Path
where
T: Into<Path>,
{
type Output = Self;
fn add(mut self, rhs: T) -> Self::Output {
self.append(rhs.into());
self
}
}
impl<T> ops::AddAssign<T> for Path
where
T: Into<Path>,
{
fn add_assign(&mut self, rhs: T) {
self.append(rhs.into());
}
}
impl<T> From<T> for Path
where
T: Into<Element>,
{
fn from(value: T) -> Self {
Path {
elements: vec![value.into()],
}
}
}
impl<T> FromIterator<T> for Path
where
T: Into<Path>,
{
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
Self {
elements: iter
.into_iter()
.map(Into::into)
.flat_map(|path| path.elements)
.collect(),
}
}
}
impl FromStr for Path {
type Err = PathParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self {
elements: s.split('.').map(Element::from_str).try_collect()?,
})
}
}
impl From<Path> for String {
fn from(path: Path) -> Self {
path.elements
.into_iter()
.map(String::from)
.collect_vec()
.join(".")
}
}
impl From<Path> for Vec<Element> {
fn from(path: Path) -> Self {
path.elements
}
}
impl TryFrom<Path> for Name {
type Error = Path;
fn try_from(path: Path) -> Result<Self, Self::Error> {
let element: [_; 1] = path
.elements
.try_into()
.map_err(|elements| Path { elements })?;
if let [Element::Name(name)] = element {
Ok(name)
} else {
Err(Path {
elements: element.into(),
})
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct PathParseError;
impl std::error::Error for PathParseError {}
impl fmt::Display for PathParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid document path")
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use crate::Num;
use super::{Element, Name, Path, PathParseError};
#[test]
fn parse_path() {
let path: Path = "foo".parse().unwrap();
assert_eq!(Path::from(Name::from("foo")), path);
let path: Path = "foo[0]".parse().unwrap();
assert_eq!(Path::from(("foo", 0)), path);
let path: Path = "foo[0][3]".parse().unwrap();
assert_eq!(Path::from(("foo", [0, 3])), path);
let path: Path = "foo[42][37][9]".parse().unwrap();
assert_eq!(Path::from(("foo", [42, 37, 9])), path);
let path: Path = "foo.bar".parse().unwrap();
assert_eq!(Path::from_iter(["foo", "bar"].map(Name::from)), path);
let path: Path = "foo[42].bar".parse().unwrap();
assert_eq!(
Path::from_iter([
Element::new_indexed_field("foo", 42),
Element::new_name("bar")
]),
path
);
let path: Path = "foo.bar[37]".parse().unwrap();
assert_eq!(
Path::from_iter([
Element::new_name("foo"),
Element::new_indexed_field("bar", 37)
]),
path
);
let path: Path = "foo[42].bar[37]".parse().unwrap();
assert_eq!(Path::from_iter([("foo", 42), ("bar", 37)]), path);
let path: Path = "foo[42][7].bar[37]".parse().unwrap();
assert_eq!(
Path::from_iter([("foo", vec![42, 7]), ("bar", vec![37])]),
path
);
let path: Path = "foo[42].bar[37][9]".parse().unwrap();
assert_eq!(
Path::from_iter([("foo", vec![42]), ("bar", vec![37, 9])]),
path
);
let path: Path = "foo[42][7].bar[37][9]".parse().unwrap();
assert_eq!(Path::from_iter([("foo", [42, 7]), ("bar", [37, 9])]), path);
for prefix in ["foo", "foo[0]", "foo.bar", "foo[0]bar", "foo[0]bar[1]"] {
for bad_index in ["[9", "[]", "][", "[", "]"] {
let input = format!("{prefix}{bad_index}");
match input.parse::<Path>() {
Ok(path) => {
panic!("Should not have parsed invalid input {input:?} into: {path:?}");
}
Err(PathParseError) => { }
}
}
}
"foo[0]bar".parse::<Path>().unwrap_err();
"foo[0]bar[3]".parse::<Path>().unwrap_err();
"[0]".parse::<Path>().unwrap_err();
}
#[test]
fn express_path() {
let _: Element = ("foo", 0).into();
let _: Path = ("foo", 0).into();
}
#[test]
fn display_name() {
let path = Element::new_name("foo");
assert_eq!("foo", path.to_string());
}
#[test]
fn display_indexed() {
let path = Element::new_indexed_field("foo", 42);
assert_eq!("foo[42]", path.to_string());
let path = Element::new_indexed_field("foo", [42]);
assert_eq!("foo[42]", path.to_string());
let path = Element::new_indexed_field("foo", &([42, 37, 9])[..]);
assert_eq!("foo[42][37][9]", path.to_string());
}
#[test]
fn display_path() {
let path: Path = ["foo", "bar"].into_iter().map(Name::from).collect();
assert_eq!("foo.bar", path.to_string());
let path = Path::from_iter([
Element::new_name("foo"),
Element::new_indexed_field("bar", 42),
]);
assert_eq!("foo.bar[42]", path.to_string());
let path = Path::from_iter([
Element::new_indexed_field("foo", 42),
Element::new_name("bar"),
]);
assert_eq!("foo[42].bar", path.to_string());
}
#[test]
fn size() {
assert_eq!(
"size(a) = 0",
"a".parse::<Path>()
.unwrap()
.size()
.equal(Num::new(0))
.to_string()
);
}
#[test]
fn begins_with_string() {
let begins_with = Path::new_indexed_field("foo", 3).begins_with("foo");
assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
let begins_with = Path::new_indexed_field("foo", 3).begins_with(String::from("foo"));
assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
#[allow(clippy::needless_borrows_for_generic_args)]
let begins_with = Path::new_indexed_field("foo", 3).begins_with(&String::from("foo"));
assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
#[allow(clippy::needless_borrows_for_generic_args)]
let begins_with = Path::new_indexed_field("foo", 3).begins_with(&"foo");
assert_eq!(r#"begins_with(foo[3], "foo")"#, begins_with.to_string());
}
#[test]
fn begins_with_value_ref() {
use crate::{path::Path, value::Ref};
let begins_with = Path::new_indexed_field("foo", 3).begins_with(Ref::new("prefix"));
assert_eq!("begins_with(foo[3], :prefix)", begins_with.to_string());
}
#[test]
fn in_() {
use crate::Path;
let condition = Path::new_name("name").in_(["Jack", "Jill"]);
assert_eq!(r#"name IN ("Jack","Jill")"#, condition.to_string());
}
#[test]
fn contains() {
let condition = Path::new_name("foo").contains("Quinn");
assert_eq!(r#"contains(foo, "Quinn")"#, condition.to_string());
let condition = Path::new_name("foo").contains(Num::new(42));
assert_eq!(r#"contains(foo, 42)"#, condition.to_string());
let condition = Path::new_name("foo").contains(b"fish".to_vec());
assert_eq!(r#"contains(foo, "ZmlzaA==")"#, condition.to_string());
}
#[test]
fn empty() {
assert!(Path::default().is_empty());
assert!(Path::from_iter(Vec::<Element>::new()).is_empty());
}
#[test]
fn from_iter() {
let path = Path::from_iter(["foo", "bar"].map(Name::from));
assert_eq!("foo.bar", path.to_string());
assert_eq!(
vec![Element::new_name("foo"), Element::new_name("bar")],
path.elements
);
let path = Path::from_iter([("foo", 42), ("bar", 37)]);
assert_eq!("foo[42].bar[37]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", 42),
Element::new_indexed_field("bar", 37),
],
path.elements
);
let path = Path::from_iter([("foo", vec![42, 7]), ("bar", vec![37])]);
assert_eq!("foo[42][7].bar[37]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", [42, 7]),
Element::new_indexed_field("bar", 37),
],
path.elements
);
let path = Path::from_iter([("foo", [42, 7]), ("bar", [37, 9])]);
assert_eq!("foo[42][7].bar[37][9]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", [42, 7]),
Element::new_indexed_field("bar", [37, 9]),
],
path.elements
);
let path = Path::from_iter([
Element::new_name("foo"),
Element::new_indexed_field("bar", 42),
]);
assert_eq!("foo.bar[42]", path.to_string());
assert_eq!(
vec![
Element::new_name("foo"),
Element::new_indexed_field("bar", 42),
],
path.elements
);
let path = Path::from_iter([
"foo.bar[42]".parse::<Path>().unwrap(),
"baz.quux".parse::<Path>().unwrap(),
]);
assert_eq!("foo.bar[42].baz.quux", path.to_string());
assert_eq!(
vec![
Element::new_name("foo"),
Element::new_indexed_field("bar", 42),
Element::new_name("baz"),
Element::new_name("quux"),
],
path.elements
);
}
#[test]
fn add() -> Result<(), Box<dyn std::error::Error>> {
let path = "foo".parse::<Path>()? + Name::from("bar");
assert_eq!("foo.bar", path.to_string());
assert_eq!(
vec![Element::new_name("foo"), Element::new_name("bar")],
path.elements
);
let path = "foo[42]".parse::<Path>()? + ("bar", 37);
assert_eq!("foo[42].bar[37]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", 42),
Element::new_indexed_field("bar", 37),
],
path.elements
);
let path = "foo[42][7]".parse::<Path>()? + ("bar", vec![37]);
assert_eq!("foo[42][7].bar[37]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", [42, 7]),
Element::new_indexed_field("bar", 37),
],
path.elements
);
let path = "foo[42][7]".parse::<Path>()? + ("bar", [37, 9]);
assert_eq!("foo[42][7].bar[37][9]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", [42, 7]),
Element::new_indexed_field("bar", [37, 9]),
],
path.elements
);
let path = "foo".parse::<Path>()? + Element::new_indexed_field("bar", 42);
assert_eq!("foo.bar[42]", path.to_string());
assert_eq!(
vec![
Element::new_name("foo"),
Element::new_indexed_field("bar", 42),
],
path.elements
);
let path = "foo.bar[42]".parse::<Path>()? + "baz.quux".parse::<Path>()?;
assert_eq!("foo.bar[42].baz.quux", path.to_string());
assert_eq!(
vec![
Element::new_name("foo"),
Element::new_indexed_field("bar", 42),
Element::new_name("baz"),
Element::new_name("quux"),
],
path.elements
);
Ok(())
}
#[test]
fn add_assign() -> Result<(), Box<dyn std::error::Error>> {
let mut path = "foo".parse::<Path>()?;
path += Name::from("bar");
assert_eq!("foo.bar", path.to_string());
assert_eq!(
vec![Element::new_name("foo"), Element::new_name("bar")],
path.elements
);
let mut path = "foo[42]".parse::<Path>()?;
path += ("bar", 37);
assert_eq!("foo[42].bar[37]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", 42),
Element::new_indexed_field("bar", 37),
],
path.elements
);
let mut path = "foo[42][7]".parse::<Path>()?;
path += ("bar", vec![37]);
assert_eq!("foo[42][7].bar[37]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", [42, 7]),
Element::new_indexed_field("bar", 37),
],
path.elements
);
let mut path = "foo[42][7]".parse::<Path>()?;
path += ("bar", [37, 9]);
assert_eq!("foo[42][7].bar[37][9]", path.to_string());
assert_eq!(
vec![
Element::new_indexed_field("foo", [42, 7]),
Element::new_indexed_field("bar", [37, 9]),
],
path.elements
);
let mut path = "foo".parse::<Path>()?;
path += Element::new_indexed_field("bar", 42);
assert_eq!("foo.bar[42]", path.to_string());
assert_eq!(
vec![
Element::new_name("foo"),
Element::new_indexed_field("bar", 42),
],
path.elements
);
let mut path = "foo.bar[42]".parse::<Path>()?;
path += "baz.quux".parse::<Path>()?;
assert_eq!("foo.bar[42].baz.quux", path.to_string());
assert_eq!(
vec![
Element::new_name("foo"),
Element::new_indexed_field("bar", 42),
Element::new_name("baz"),
Element::new_name("quux"),
],
path.elements
);
Ok(())
}
}