use crate::common;
use aws_sdk_dynamodb::{Client, error, operation, types};
use serde::Serialize;
use serde_dynamo::{Error, Result, to_item};
use std::collections;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BatchWriteItemRequestPutItem<T> {
pub item: T,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BatchWriteItemRequestDeleteItem<T> {
pub keys: common::key::Keys<T>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum BatchWriteItemRequest<T> {
PutItem(BatchWriteItemRequestPutItem<T>),
DeleteItem(BatchWriteItemRequestDeleteItem<T>),
}
impl<T: Serialize> TryFrom<BatchWriteItemRequest<T>> for types::WriteRequest {
type Error = Error;
fn try_from(write_request: BatchWriteItemRequest<T>) -> Result<Self> {
let builder = match write_request {
BatchWriteItemRequest::PutItem(put_item) => {
let item = to_item(put_item.item)?;
let put_request = types::PutRequest::builder()
.set_item(Some(item))
.build()
.unwrap();
Self::builder().set_put_request(Some(put_request))
}
BatchWriteItemRequest::DeleteItem(delete_item) => {
let keys = delete_item.keys.try_into()?;
let delete_request = types::DeleteRequest::builder()
.set_key(Some(keys))
.build()
.unwrap();
Self::builder().set_delete_request(Some(delete_request))
}
};
let request = builder.build();
Ok(request)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BatchWriteItem<T> {
pub request_items: collections::HashMap<String, Vec<BatchWriteItemRequest<T>>>,
pub return_consumed_capacity: Option<types::ReturnConsumedCapacity>,
pub return_item_collection_metrics: Option<types::ReturnItemCollectionMetrics>,
}
impl<T: Serialize> TryFrom<BatchWriteItem<T>> for operation::batch_write_item::BatchWriteItemInput {
type Error = Error;
fn try_from(batch_write_item: BatchWriteItem<T>) -> Result<Self> {
let mut request_items =
collections::HashMap::with_capacity(batch_write_item.request_items.len());
for (table_name, table_request_items) in batch_write_item.request_items {
let mut serialized_table_request_items = Vec::with_capacity(table_request_items.len());
for request_item in table_request_items {
let request_item = request_item.try_into()?;
serialized_table_request_items.push(request_item);
}
request_items.insert(table_name, serialized_table_request_items);
}
let operation = Self::builder()
.set_request_items(Some(request_items))
.set_return_consumed_capacity(batch_write_item.return_consumed_capacity)
.set_return_item_collection_metrics(batch_write_item.return_item_collection_metrics)
.build()
.unwrap();
Ok(operation)
}
}
impl<T: Serialize> BatchWriteItem<T> {
#[cfg_attr(
feature = "tracing",
tracing::instrument(name = "dynamodb_crud.batch_write_item", err)
)]
pub async fn send(
self,
client: &Client,
) -> Result<
operation::batch_write_item::BatchWriteItemOutput,
error::SdkError<operation::batch_write_item::BatchWriteItemError>,
> {
let batch_write_item: operation::batch_write_item::BatchWriteItemInput =
self.try_into().map_err(error::BuildError::other)?;
client
.batch_write_item()
.set_request_items(batch_write_item.request_items)
.set_return_consumed_capacity(batch_write_item.return_consumed_capacity)
.set_return_item_collection_metrics(batch_write_item.return_item_collection_metrics)
.send()
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use serde_json::{Value, json};
#[rstest]
#[case::empty(
BatchWriteItem {
request_items: collections::HashMap::from(
[(
"a".to_string(),
vec![
BatchWriteItemRequest::DeleteItem(
BatchWriteItemRequestDeleteItem {
keys: common::key::Keys {
partition_key: common::key::Key {
name: "b".to_string(),
value: Value::String(
"c".to_string()
),
},
..Default::default()
},
}
),
BatchWriteItemRequest::PutItem(
BatchWriteItemRequestPutItem {
item: json!(
{
"d": "e"
}
),
}
)
],
)]
),
..Default::default()
},
operation::batch_write_item::BatchWriteItemInput::builder()
.set_request_items(
Some(
collections::HashMap::from(
[(
"a".to_string(),
vec![
types::WriteRequest::builder()
.set_delete_request(
Some(
types::DeleteRequest::builder()
.set_key(
Some(
collections::HashMap::from(
[(
"b".to_string(),
types::AttributeValue::S(
"c".to_string()
),
)]
)
)
)
.build()
.unwrap(),
)
)
.build(),
types::WriteRequest::builder()
.set_put_request(
Some(
types::PutRequest::builder()
.set_item(
Some(
collections::HashMap::from(
[(
"d".to_string(),
types::AttributeValue::S(
"e".to_string()
),
)]
)
)
)
.build()
.unwrap(),
)
)
.build(),
],
)]
)
)
)
.build()
.unwrap()
)]
#[case::full(
BatchWriteItem {
request_items: collections::HashMap::from(
[
(
"a".to_string(),
vec![
BatchWriteItemRequest::DeleteItem(
BatchWriteItemRequestDeleteItem {
keys: common::key::Keys {
partition_key: common::key::Key {
name: "b".to_string(),
value: Value::String(
"c".to_string()
),
},
sort_key: Some(
common::key::Key {
name: "d".to_string(),
value: Value::String(
"e".to_string()
),
}
),
},
}
),
BatchWriteItemRequest::PutItem(
BatchWriteItemRequestPutItem {
item: json!(
{
"f": "g"
}
),
}
),
],
),
(
"h".to_string(),
vec![
BatchWriteItemRequest::DeleteItem(
BatchWriteItemRequestDeleteItem {
keys: common::key::Keys {
partition_key: common::key::Key {
name: "i".to_string(),
value: Value::String(
"j".to_string()
),
},
sort_key: Some(
common::key::Key {
name: "k".to_string(),
value: Value::String(
"l".to_string()
),
}
),
},
}
),
BatchWriteItemRequest::PutItem(
BatchWriteItemRequestPutItem {
item: json!(
{
"m": "n"
}
),
}
),
],
),
]
),
return_consumed_capacity: Some(
types::ReturnConsumedCapacity::Indexes
),
return_item_collection_metrics: Some(
types::ReturnItemCollectionMetrics::None
),
},
operation::batch_write_item::BatchWriteItemInput::builder()
.set_request_items(
Some(
collections::HashMap::from(
[
(
"a".to_string(),
vec![
types::WriteRequest::builder()
.set_delete_request(
Some(
types::DeleteRequest::builder()
.set_key(
Some(
collections::HashMap::from(
[
(
"b".to_string(),
types::AttributeValue::S(
"c".to_string()
)
),
(
"d".to_string(),
types::AttributeValue::S(
"e".to_string()
)
),
]
)
)
)
.build()
.unwrap(),
)
)
.build(),
types::WriteRequest::builder()
.set_put_request(
Some(
types::PutRequest::builder()
.set_item(
Some(
collections::HashMap::from(
[(
"f".to_string(),
types::AttributeValue::S(
"g".to_string()
),
)]
)
)
)
.build()
.unwrap(),
)
)
.build(),
],
),
(
"h".to_string(),
vec![
types::WriteRequest::builder()
.set_delete_request(
Some(
types::DeleteRequest::builder()
.set_key(
Some(
collections::HashMap::from(
[
(
"i".to_string(),
types::AttributeValue::S(
"j".to_string()
)
),
(
"k".to_string(),
types::AttributeValue::S(
"l".to_string()
)
),
]
)
)
)
.build()
.unwrap(),
)
)
.build(),
types::WriteRequest::builder()
.set_put_request(
Some(
types::PutRequest::builder()
.set_item(
Some(
collections::HashMap::from(
[(
"m".to_string(),
types::AttributeValue::S(
"n".to_string()
),
)]
)
)
)
.build()
.unwrap(),
)
)
.build(),
],
),
]
)
)
)
.set_return_consumed_capacity(
Some(
types::ReturnConsumedCapacity::Indexes
)
)
.set_return_item_collection_metrics(
Some(
types::ReturnItemCollectionMetrics::None
)
)
.build()
.unwrap()
)]
fn test_batch_write_item(
#[case] args: BatchWriteItem<Value>,
#[case] expected: operation::batch_write_item::BatchWriteItemInput,
) {
let actual: operation::batch_write_item::BatchWriteItemInput = args.try_into().unwrap();
assert_eq!(actual, expected);
}
}