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}