dynamodb_expression/
lib.rs

1/*!
2A crate to help build DynamoDB condition, filter, key condition, and update
3expressions in a type-safe way, including using [expression attribute names][1]
4and [expression attribute values][2].
5
6[`Path`] represents a [DynamoDB document path][3], and has many methods for
7building various expressions. [`Expression`] is the type to use for a [DynamoDB
8expression][4].
9
10An example showing a how to use this crate to perform a query:
11
12```no_run
13# async fn example_query() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
14use aws_config::BehaviorVersion;
15use aws_sdk_dynamodb::Client;
16use dynamodb_expression::{Expression, Num, Path};
17
18let client = Client::new(&aws_config::load_defaults(BehaviorVersion::latest()).await);
19
20let query_output = Expression::builder()
21    .with_filter(
22        "name"
23            .parse::<Path>()?
24            .attribute_exists()
25            .and("age".parse::<Path>()?.greater_than_or_equal(Num::new(2.5))),
26    )
27    .with_projection(["name", "age"])
28    .with_key_condition("id".parse::<Path>()?.key().equal(Num::new(42)))
29    .build()
30    .query(&client)
31    .table_name("people")
32    .send()
33    .await?;
34#
35# _ = query_output;
36# Ok(())
37# }
38```
39
40From here, see [`Path`] for building updates, filters, or conditions, and then
41[`Expression`] to turn those into DynamoDB expressions.
42
43# What about Rusoto?
44
45[Rusoto][5] is intentionally not supported.
46
47If you are using Rusoto and want to take advantage of this crate, you can still
48build an [`Expression`], then convert the [`aws_sdk_dynamodb::types::AttributeValue`]
49that are in the `expression_attribute_values` field into [`rusoto_dynamodb::AttributeValue`].
50The rest of the fields are already what's needed.
51
52```no_run
53# async fn example_rusoto() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
54use aws_sdk_dynamodb::{primitives::Blob, types::AttributeValue as AwsAv};
55use dynamodb_expression::{Expression, Num, Path};
56use itermap::IterMap;
57use rusoto_core::Region;
58use rusoto_dynamodb::{AttributeValue as RusotoAv, DynamoDb, DynamoDbClient, QueryInput};
59
60let expression = Expression::builder()
61    .with_filter(
62        "name"
63            .parse::<Path>()?
64            .attribute_exists()
65            .and("age".parse::<Path>()?.greater_than_or_equal(Num::new(2.5))),
66    )
67    .with_projection(["name", "age"])
68    .with_key_condition("id".parse::<Path>()?.key().equal(Num::new(42)))
69    .build();
70
71let input = QueryInput {
72    filter_expression: expression.filter_expression,
73    projection_expression: expression.projection_expression,
74    key_condition_expression: expression.key_condition_expression,
75    expression_attribute_names: expression.expression_attribute_names,
76    expression_attribute_values: expression
77        .expression_attribute_values
78        .map(|values| values.into_iter().map_values(convert_av).collect()),
79    table_name: String::from("people"),
80    ..QueryInput::default()
81};
82
83let output = DynamoDbClient::new(Region::UsEast1).query(input).await?;
84
85fn convert_av(av: AwsAv) -> RusotoAv {
86    let mut rav = RusotoAv::default();
87
88    match av {
89        AwsAv::B(av) => rav.b = Some(av.into_inner().into()),
90        AwsAv::Bool(av) => rav.bool = av.into(),
91        AwsAv::Bs(av) => {
92            rav.bs = Some(
93                av.into_iter()
94                    .map(Blob::into_inner)
95                    .map(Into::into)
96                    .collect(),
97            )
98        }
99        AwsAv::L(av) => rav.l = Some(av.into_iter().map(convert_av).collect()),
100        AwsAv::M(av) => rav.m = Some(av.into_iter().map_values(convert_av).collect()),
101        AwsAv::N(av) => rav.n = av.into(),
102        AwsAv::Ns(av) => rav.ns = av.into(),
103        AwsAv::Null(av) => rav.null = av.into(),
104        AwsAv::S(av) => rav.s = av.into(),
105        AwsAv::Ss(av) => rav.ss = av.into(),
106        _ => unimplemented!(
107            "A variant was added to aws_sdk_dynamodb::types::AttributeValue \
108                and not implemented here: {av:?}",
109        ),
110    }
111
112    rav
113}
114#
115# _ = output;
116# Ok(())
117# }
118```
119
120[1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html
121[2]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeValues.html
122[3]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Attributes.html#Expressions.Attributes.NestedElements.DocumentPathExamples
123[4]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html
124[5]: https://docs.rs/rusoto_dynamodb/
125[`rusoto_dynamodb::AttributeValue`]: https://docs.rs/rusoto_dynamodb/latest/rusoto_dynamodb/struct.AttributeValue.html
126*/
127
128// Re-export the crates publicly exposed in our API
129pub use ::aws_sdk_dynamodb;
130pub use ::num;
131
132pub mod condition;
133mod expression;
134pub mod key;
135pub mod operand;
136pub mod path;
137pub mod update;
138pub mod value;
139
140pub use expression::{Builder, Expression};
141pub use path::Path;
142pub use value::{Map, Num, Scalar, Set, Value};
143
144/// This exists just for formatting the doc examples.
145#[cfg(test)]
146mod examples {
147    #[allow(dead_code)]
148    async fn example_query() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
149        use crate::{Expression, Num, Path};
150        use aws_config::BehaviorVersion;
151        use aws_sdk_dynamodb::Client;
152
153        let client = Client::new(&aws_config::load_defaults(BehaviorVersion::latest()).await);
154
155        let query_output = Expression::builder()
156            .with_filter(
157                "name"
158                    .parse::<Path>()?
159                    .attribute_exists()
160                    .and("age".parse::<Path>()?.greater_than_or_equal(Num::new(2.5))),
161            )
162            .with_projection(["name", "age"])
163            .with_key_condition("id".parse::<Path>()?.key().equal(Num::new(42)))
164            .build()
165            .query(&client)
166            .table_name("people")
167            .send()
168            .await?;
169
170        _ = query_output;
171        Ok(())
172    }
173
174    #[allow(dead_code)]
175    async fn example_rusoto() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
176        use crate::{Expression, Num, Path};
177        use aws_sdk_dynamodb::{primitives::Blob, types::AttributeValue as AwsAv};
178        use itermap::IterMap;
179        use rusoto_core::Region;
180        use rusoto_dynamodb::{AttributeValue as RusotoAv, DynamoDb, DynamoDbClient, QueryInput};
181
182        let expression = Expression::builder()
183            .with_filter(
184                "name"
185                    .parse::<Path>()?
186                    .attribute_exists()
187                    .and("age".parse::<Path>()?.greater_than_or_equal(Num::new(2.5))),
188            )
189            .with_projection(["name", "age"])
190            .with_key_condition("id".parse::<Path>()?.key().equal(Num::new(42)))
191            .build();
192
193        let input = QueryInput {
194            filter_expression: expression.filter_expression,
195            projection_expression: expression.projection_expression,
196            key_condition_expression: expression.key_condition_expression,
197            expression_attribute_names: expression.expression_attribute_names,
198            expression_attribute_values: expression
199                .expression_attribute_values
200                .map(|values| values.into_iter().map_values(convert_av).collect()),
201            table_name: String::from("people"),
202            ..QueryInput::default()
203        };
204
205        let output = DynamoDbClient::new(Region::UsEast1).query(input).await?;
206
207        fn convert_av(av: AwsAv) -> RusotoAv {
208            let mut rav = RusotoAv::default();
209
210            match av {
211                AwsAv::B(av) => rav.b = Some(av.into_inner().into()),
212                AwsAv::Bool(av) => rav.bool = av.into(),
213                AwsAv::Bs(av) => {
214                    rav.bs = Some(
215                        av.into_iter()
216                            .map(Blob::into_inner)
217                            .map(Into::into)
218                            .collect(),
219                    )
220                }
221                AwsAv::L(av) => rav.l = Some(av.into_iter().map(convert_av).collect()),
222                AwsAv::M(av) => rav.m = Some(av.into_iter().map_values(convert_av).collect()),
223                AwsAv::N(av) => rav.n = av.into(),
224                AwsAv::Ns(av) => rav.ns = av.into(),
225                AwsAv::Null(av) => rav.null = av.into(),
226                AwsAv::S(av) => rav.s = av.into(),
227                AwsAv::Ss(av) => rav.ss = av.into(),
228                _ => unimplemented!(
229                    "A variant was added to aws_sdk_dynamodb::types::AttributeValue \
230                        and not implemented here: {av:?}",
231                ),
232            }
233
234            rav
235        }
236
237        _ = output;
238        Ok(())
239    }
240}