1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use core::fmt;

use crate::{
    path::Path,
    update::set_remove::SetRemove,
    value::{Value, ValueOrRef},
};

use super::Set;

/// Represents assigning a value of an [attribute][1], [list][2], or [map][3]
/// for a DynamoDB update expression.
///
/// See also: [`Path::set`]
///
/// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.ModifyingAttributes
/// [2]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.AddingListElements
/// [3]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.AddingNestedMapAttributes
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use] // TODO: More detail
pub struct Assign {
    pub(crate) path: Path,
    pub(crate) value: ValueOrRef,
}

impl Assign {
    /// Allows for manual creation of an [`Assign`] statement.
    ///
    /// See also: [`Path::set`]
    pub fn new<P, V>(path: P, value: V) -> Self
    where
        P: Into<Path>,
        V: Into<Value>,
    {
        Self {
            path: path.into(),
            value: value.into().into(),
        }
    }

    /// Add an additional [`Set`] or [`Remove`] to this expression.
    ///
    /// ```
    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    /// use dynamodb_expression::{Num, Path};
    /// # use pretty_assertions::assert_eq;
    ///
    /// let set = "foo"
    ///     .parse::<Path>()?
    ///     .set(Num::new(7))
    ///     .and("bar".parse::<Path>()?.set("a value"))
    ///     .and("baz".parse::<Path>()?.remove());
    /// assert_eq!(r#"SET foo = 7, bar = "a value" REMOVE baz"#, set.to_string());
    /// #
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [`Remove`]: crate::update::Remove
    pub fn and<T>(self, other: T) -> SetRemove
    where
        T: Into<SetRemove>,
    {
        Set::from(self).and(other)
    }
}

impl fmt::Display for Assign {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.path.fmt(f)?;
        f.write_str(" = ")?;
        self.value.fmt(f)
    }
}

#[cfg(test)]
mod test {
    use pretty_assertions::assert_eq;

    use crate::{
        update::{set_remove::SetRemove, IfNotExists, Set, SetAction},
        Num, Path,
    };

    use super::Assign;

    #[test]
    fn and() -> Result<(), Box<dyn std::error::Error>> {
        let assign: Assign = "foo".parse::<Path>()?.set("a value");
        let if_not_exists: IfNotExists = "bar".parse::<Path>()?.if_not_exists().set(Num::new(8));

        // Should be able to concatenate anything that can be turned into a SetAction.

        let combined = assign.clone().and(if_not_exists.clone());
        assert_eq!(
            r#"SET foo = "a value", bar = if_not_exists(bar, 8)"#,
            combined.to_string()
        );

        // Should be able to concatenate a SetAction instance.

        let combined = assign.clone().and(SetAction::from(if_not_exists.clone()));
        assert_eq!(
            r#"SET foo = "a value", bar = if_not_exists(bar, 8)"#,
            combined.to_string()
        );

        // Should be able to concatenate a Set instance

        let set: Set = [
            SetAction::from(if_not_exists),
            SetAction::from("baz".parse::<Path>()?.math().add(1)),
        ]
        .into_iter()
        .collect();
        let combined = assign.clone().and(set);
        assert_eq!(
            r#"SET foo = "a value", bar = if_not_exists(bar, 8), baz = baz + 1"#,
            combined.to_string()
        );

        // Should be able to concatenate a Remove instance

        let combined = assign.clone().and("quux".parse::<Path>()?.remove());
        assert_eq!(r#"SET foo = "a value" REMOVE quux"#, combined.to_string());

        // Should be able to concatenate a SetRemove instance

        let combined = assign.and(SetRemove::from("quux".parse::<Path>()?.remove()));
        assert_eq!(r#"SET foo = "a value" REMOVE quux"#, combined.to_string());

        Ok(())
    }
}