use crate::models::{
AccountReference, ContainerReference, CosmosRequestHeaders, CosmosResourceReference,
DatabaseReference, ItemReference, OperationType, PartitionKey, Precondition, ResourceType,
};
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct CosmosOperation {
operation_type: OperationType,
resource_type: ResourceType,
resource_reference: CosmosResourceReference,
partition_key: Option<PartitionKey>,
request_headers: CosmosRequestHeaders,
body: Option<Vec<u8>>,
}
impl CosmosOperation {
pub fn operation_type(&self) -> OperationType {
self.operation_type
}
pub fn resource_type(&self) -> ResourceType {
self.resource_type
}
pub(crate) fn resource_reference(&self) -> &CosmosResourceReference {
&self.resource_reference
}
pub fn container(&self) -> Option<&ContainerReference> {
self.resource_reference.container()
}
pub fn partition_key(&self) -> Option<&PartitionKey> {
self.partition_key.as_ref()
}
pub fn request_headers(&self) -> &CosmosRequestHeaders {
&self.request_headers
}
pub fn body(&self) -> Option<&[u8]> {
self.body.as_deref()
}
pub fn with_partition_key(mut self, partition_key: impl Into<PartitionKey>) -> Self {
self.partition_key = Some(partition_key.into());
self
}
pub fn with_request_headers(mut self, headers: CosmosRequestHeaders) -> Self {
self.request_headers = headers;
self
}
pub fn with_session_token(
mut self,
session_token: impl Into<crate::models::SessionToken>,
) -> Self {
self.request_headers.session_token = Some(session_token.into());
self
}
pub fn with_activity_id(mut self, activity_id: crate::models::ActivityId) -> Self {
self.request_headers.activity_id = Some(activity_id);
self
}
pub fn with_precondition(mut self, precondition: Precondition) -> Self {
self.request_headers.precondition = Some(precondition);
self
}
pub fn precondition(&self) -> Option<&Precondition> {
self.request_headers.precondition.as_ref()
}
pub fn with_body(mut self, body: Vec<u8>) -> Self {
self.body = Some(body);
self
}
fn new(
operation_type: OperationType,
resource_reference: impl Into<CosmosResourceReference>,
) -> Self {
let resource_reference = resource_reference.into();
let resource_type = resource_reference.resource_type();
Self {
operation_type,
resource_type,
resource_reference,
partition_key: None,
request_headers: CosmosRequestHeaders::new(),
body: None,
}
}
pub fn create_database(account: AccountReference) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(account)
.with_resource_type(ResourceType::Database)
.into_feed_reference();
Self::new(OperationType::Create, resource_ref)
}
pub fn read_all_databases(account: AccountReference) -> Self {
let resource_ref = Into::<CosmosResourceReference>::into(account)
.with_resource_type(ResourceType::Database)
.into_feed_reference();
Self::new(OperationType::ReadFeed, resource_ref)
}
pub fn query_databases(account: AccountReference) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(account)
.with_resource_type(ResourceType::Database)
.into_feed_reference();
Self::new(OperationType::Query, resource_ref)
}
pub fn delete_database(database: DatabaseReference) -> Self {
let resource_ref: CosmosResourceReference = database.into();
Self::new(OperationType::Delete, resource_ref)
}
pub fn read_database(database: DatabaseReference) -> Self {
let resource_ref: CosmosResourceReference = database.into();
Self::new(OperationType::Read, resource_ref)
}
pub fn create_container(database: DatabaseReference) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(database)
.with_resource_type(ResourceType::DocumentCollection)
.into_feed_reference();
Self::new(OperationType::Create, resource_ref)
}
pub fn read_all_containers(database: DatabaseReference) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(database)
.with_resource_type(ResourceType::DocumentCollection)
.into_feed_reference();
Self::new(OperationType::ReadFeed, resource_ref)
}
pub fn query_containers(database: DatabaseReference) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(database)
.with_resource_type(ResourceType::DocumentCollection)
.into_feed_reference();
Self::new(OperationType::Query, resource_ref)
}
pub fn delete_container(container: ContainerReference) -> Self {
let resource_ref: CosmosResourceReference = container.into();
Self::new(OperationType::Delete, resource_ref)
}
pub fn read_container(container: ContainerReference) -> Self {
let resource_ref: CosmosResourceReference = container.into();
Self::new(OperationType::Read, resource_ref)
}
pub fn read_container_by_name(
database: DatabaseReference,
container_name: impl Into<std::borrow::Cow<'static, str>>,
) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(database)
.with_resource_type(ResourceType::DocumentCollection)
.with_name(container_name.into());
Self::new(OperationType::Read, resource_ref)
}
pub fn read_container_by_rid(
database: DatabaseReference,
container_rid: impl Into<std::borrow::Cow<'static, str>>,
) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(database)
.with_resource_type(ResourceType::DocumentCollection)
.with_rid(container_rid.into());
Self::new(OperationType::Read, resource_ref)
}
pub fn create_item(container: ContainerReference, partition_key: PartitionKey) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(container)
.with_resource_type(ResourceType::Document)
.into_feed_reference();
Self::new(OperationType::Create, resource_ref).with_partition_key(partition_key)
}
pub fn read_item(item: ItemReference) -> Self {
let partition_key = item.partition_key().clone();
Self::new(OperationType::Read, item).with_partition_key(partition_key)
}
pub fn delete_item(item: ItemReference) -> Self {
let partition_key = item.partition_key().clone();
Self::new(OperationType::Delete, item).with_partition_key(partition_key)
}
pub fn upsert_item(item: ItemReference) -> Self {
let partition_key = item.partition_key().clone();
Self::new(OperationType::Upsert, item).with_partition_key(partition_key)
}
pub fn replace_item(item: ItemReference) -> Self {
let partition_key = item.partition_key().clone();
Self::new(OperationType::Replace, item).with_partition_key(partition_key)
}
pub fn read_all_items(container: ContainerReference, partition_key: PartitionKey) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(container)
.with_resource_type(ResourceType::Document)
.into_feed_reference();
Self::new(OperationType::ReadFeed, resource_ref).with_partition_key(partition_key)
}
pub fn read_all_items_cross_partition(container: ContainerReference) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(container)
.with_resource_type(ResourceType::Document)
.into_feed_reference();
Self::new(OperationType::ReadFeed, resource_ref)
}
pub fn query_items(container: ContainerReference, partition_key: PartitionKey) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(container)
.with_resource_type(ResourceType::Document)
.into_feed_reference();
Self::new(OperationType::Query, resource_ref).with_partition_key(partition_key)
}
pub fn query_items_cross_partition(container: ContainerReference) -> Self {
let resource_ref: CosmosResourceReference = CosmosResourceReference::from(container)
.with_resource_type(ResourceType::Document)
.into_feed_reference();
Self::new(OperationType::Query, resource_ref)
}
pub fn is_read_only(&self) -> bool {
self.operation_type.is_read_only()
}
pub fn is_idempotent(&self) -> bool {
self.operation_type.is_idempotent()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{
AccountReference, ContainerProperties, ContainerReference, PartitionKeyDefinition,
SystemProperties,
};
use url::Url;
fn test_account() -> AccountReference {
AccountReference::with_master_key(
Url::parse("https://test.documents.azure.com:443/").unwrap(),
"test-key",
)
}
fn test_partition_key_definition(path: &str) -> PartitionKeyDefinition {
serde_json::from_str(&format!(r#"{{"paths":["{path}"]}}"#)).unwrap()
}
fn test_container_props() -> ContainerProperties {
ContainerProperties {
id: "testcontainer".into(),
partition_key: test_partition_key_definition("/pk"),
system_properties: SystemProperties::default(),
}
}
fn test_container() -> ContainerReference {
ContainerReference::new(
test_account(),
"testdb",
"testdb_rid",
"testcontainer",
"testcontainer_rid",
&test_container_props(),
)
}
#[test]
fn create_operation() {
let item_ref =
ItemReference::from_name(&test_container(), PartitionKey::from("pk1"), "doc1");
let resource_ref: CosmosResourceReference = item_ref.into();
let op = CosmosOperation::new(OperationType::Create, resource_ref);
assert_eq!(op.operation_type(), OperationType::Create);
assert_eq!(op.resource_type(), ResourceType::Document);
assert!(!op.is_read_only());
assert!(!op.is_idempotent());
}
#[test]
fn read_operation() {
let item_ref =
ItemReference::from_name(&test_container(), PartitionKey::from("pk1"), "doc1");
let resource_ref: CosmosResourceReference = item_ref.into();
let op = CosmosOperation::new(OperationType::Read, resource_ref);
assert_eq!(op.operation_type(), OperationType::Read);
assert_eq!(op.resource_type(), ResourceType::Document);
assert!(op.is_read_only());
assert!(op.is_idempotent());
}
#[test]
fn operation_with_partition_key() {
let item_ref =
ItemReference::from_name(&test_container(), PartitionKey::from("pk1"), "doc1");
let resource_ref: CosmosResourceReference = item_ref.into();
let op = CosmosOperation::new(OperationType::Read, resource_ref)
.with_partition_key(PartitionKey::from("pk1"));
assert!(op.partition_key().is_some());
}
#[test]
fn operation_with_body() {
let item_ref =
ItemReference::from_name(&test_container(), PartitionKey::from("pk1"), "doc1");
let resource_ref: CosmosResourceReference = item_ref.into();
let body = b"{\"id\":\"doc1\"}".to_vec();
let op = CosmosOperation::new(OperationType::Create, resource_ref).with_body(body.clone());
assert_eq!(op.body(), Some(body.as_slice()));
}
#[test]
fn replace_is_idempotent() {
let item_ref =
ItemReference::from_name(&test_container(), PartitionKey::from("pk1"), "doc1");
let resource_ref: CosmosResourceReference = item_ref.into();
let op = CosmosOperation::new(OperationType::Replace, resource_ref);
assert!(!op.is_read_only());
assert!(op.is_idempotent());
}
#[test]
fn upsert_is_not_idempotent() {
let item_ref =
ItemReference::from_name(&test_container(), PartitionKey::from("pk1"), "doc1");
let resource_ref: CosmosResourceReference = item_ref.into();
let op = CosmosOperation::new(OperationType::Upsert, resource_ref);
assert!(!op.is_read_only());
assert!(!op.is_idempotent());
}
}