diff --git a/Cargo.toml b/Cargo.toml
index 694c89ae73..6517dbda94 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,6 +56,7 @@ base64 = "0.22"
bytes = "1.0"
cargo_metadata = "0.18.1"
clap = { version = "4.4.16", features = ["derive"] }
+cfg-if = "1.0.0"
dyn-clone = "1.0"
fe2o3-amqp = { version = "0.12", features = ["native-tls", "tracing", "uuid"] }
fe2o3-amqp-ext = { version = "0.12", features = [] }
diff --git a/sdk/cosmos/azure_data_cosmos/Cargo.toml b/sdk/cosmos/azure_data_cosmos/Cargo.toml
index 7c6db29ba3..d0eb7a1fd4 100644
--- a/sdk/cosmos/azure_data_cosmos/Cargo.toml
+++ b/sdk/cosmos/azure_data_cosmos/Cargo.toml
@@ -13,9 +13,8 @@ documentation = "https://docs.rs/azure_data_cosmos"
keywords = ["sdk", "azure", "rest", "cloud", "cosmos", "database"]
categories = ["api-bindings"]
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
[dependencies]
+cfg-if.workspace = true
async-trait.workspace = true
azure_core.workspace = true
typespec_client_core = { workspace = true, features = ["derive"] }
@@ -44,6 +43,7 @@ default = ["hmac_rust"]
hmac_rust = ["azure_core/hmac_rust"]
hmac_openssl = ["azure_core/hmac_openssl"]
key_auth = [] # Enables support for key-based authentication (Primary Keys and Resource Tokens)
+control_plane = ["key_auth"] # Control-plane operations require key-based authentication.
[package.metadata.docs.rs]
-features = ["key_auth"]
+features = ["control_plane", "key_auth"]
diff --git a/sdk/cosmos/azure_data_cosmos/docs/control-plane-warning.md b/sdk/cosmos/azure_data_cosmos/docs/control-plane-warning.md
new file mode 100644
index 0000000000..7a5562dd68
--- /dev/null
+++ b/sdk/cosmos/azure_data_cosmos/docs/control-plane-warning.md
@@ -0,0 +1,5 @@
+
+
+This is a control-plane API and requires that you authenticate using a key. To use Entra ID to perform this operation, you must use the [Azure Resource Manager APIs](https://learn.microsoft.com/en-us/azure/templates/microsoft.documentdb/databaseaccounts).
+
+
diff --git a/sdk/cosmos/azure_data_cosmos/examples/cosmos/create.rs b/sdk/cosmos/azure_data_cosmos/examples/cosmos/create.rs
index 9a5a3d3ae5..f480267c7b 100644
--- a/sdk/cosmos/azure_data_cosmos/examples/cosmos/create.rs
+++ b/sdk/cosmos/azure_data_cosmos/examples/cosmos/create.rs
@@ -4,42 +4,129 @@ use azure_data_cosmos::{
clients::{ContainerClientMethods, DatabaseClientMethods},
CosmosClient, CosmosClientMethods, PartitionKey,
};
-use clap::Args;
+use clap::{Args, Subcommand};
-/// Creates a new item.
+#[cfg(feature = "control_plane")]
+use azure_data_cosmos::models::{ContainerProperties, PartitionKeyDefinition};
+
+/// Creates a new item, database, or container.
#[derive(Clone, Args)]
pub struct CreateCommand {
- /// The database in which to create the item.
- database: String,
+ #[command(subcommand)]
+ subcommand: Subcommands,
+}
+
+#[derive(Clone, Subcommand)]
+pub enum Subcommands {
+ /// Create an item in a container.
+ Item {
+ /// The database in which to create the item.
+ database: String,
+
+ /// The container in which to create the item.
+ container: String,
+
+ /// The partition key of the new item.
+ #[clap(long, short)]
+ partition_key: String,
+
+ /// The JSON of the new item.
+ #[clap(long, short)]
+ json: String,
+ },
- /// The container in which to create the item.
- container: String,
+ /// Create a database (does not support Entra ID).
+ #[cfg(feature = "control_plane")]
+ Database {
+ /// The ID of the new database to create.
+ id: String,
+ },
- /// The partition key of the new item.
- #[clap(long, short)]
- partition_key: String,
+ /// Create a container (does not support Entra ID).
+ #[cfg(feature = "control_plane")]
+ Container {
+ /// The ID of the database to create the container in.
+ database: String,
- /// The JSON of the new item.
- #[clap(long, short)]
- json: String,
+ /// The ID of the new container to create.
+ #[clap(long, short)]
+ id: Option,
+
+ /// The path to the partition key properties (supports up to 3).
+ #[clap(long, short)]
+ partition_key: Vec,
+
+ /// The JSON for a ContainerProperties value. The 'id' and 'partition key' options are ignored if this is set.
+ #[clap(long)]
+ json: Option,
+ },
}
impl CreateCommand {
pub async fn run(self, client: CosmosClient) -> Result<(), Box> {
- let db_client = client.database_client(&self.database);
- let container_client = db_client.container_client(&self.container);
-
- let pk = PartitionKey::from(&self.partition_key);
- let item: serde_json::Value = serde_json::from_str(&self.json)?;
-
- let created = container_client
- .create_item(pk, item, None)
- .await?
- .deserialize_body()
- .await?
- .unwrap();
- println!("Created item:");
- println!("{:#?}", created);
- Ok(())
+ match self.subcommand {
+ Subcommands::Item {
+ database,
+ container,
+ partition_key,
+ json,
+ } => {
+ let db_client = client.database_client(database);
+ let container_client = db_client.container_client(container);
+
+ let pk = PartitionKey::from(&partition_key);
+ let item: serde_json::Value = serde_json::from_str(&json)?;
+
+ let created = container_client
+ .create_item(pk, item, None)
+ .await?
+ .deserialize_body()
+ .await?
+ .unwrap();
+ println!("Created item:");
+ println!("{:#?}", created);
+ Ok(())
+ }
+
+ #[cfg(feature = "control_plane")]
+ Subcommands::Database { id } => {
+ let db = client
+ .create_database(id, None)
+ .await?
+ .deserialize_body()
+ .await?
+ .unwrap();
+ println!("Created database:");
+ println!("{:#?}", db);
+ Ok(())
+ }
+
+ #[cfg(feature = "control_plane")]
+ Subcommands::Container {
+ database,
+ id,
+ partition_key,
+ json,
+ } => {
+ let properties = match json {
+ Some(j) => serde_json::from_str(&j).unwrap(),
+ None => ContainerProperties {
+ id: id.expect("the ID is required when not using '--json'"),
+ partition_key: PartitionKeyDefinition::new(partition_key),
+ ..Default::default()
+ },
+ };
+ let container = client
+ .database_client(database)
+ .create_container(properties, None)
+ .await?
+ .deserialize_body()
+ .await?
+ .unwrap();
+ println!("Created container:");
+ println!("{:#?}", container);
+ Ok(())
+ }
+ }
}
}
diff --git a/sdk/cosmos/azure_data_cosmos/examples/cosmos/delete.rs b/sdk/cosmos/azure_data_cosmos/examples/cosmos/delete.rs
index ccc6d22047..28695e7561 100644
--- a/sdk/cosmos/azure_data_cosmos/examples/cosmos/delete.rs
+++ b/sdk/cosmos/azure_data_cosmos/examples/cosmos/delete.rs
@@ -5,39 +5,91 @@ use azure_data_cosmos::{
clients::{ContainerClientMethods, DatabaseClientMethods},
CosmosClient, CosmosClientMethods,
};
-use clap::Args;
+use clap::{Args, Subcommand};
-/// Deletes an item.
+/// Deletes an item, database, or container.
#[derive(Clone, Args)]
pub struct DeleteCommand {
- /// The database containing the item.
- database: String,
+ #[command(subcommand)]
+ subcommand: Subcommands,
+}
+
+#[derive(Clone, Subcommand)]
+pub enum Subcommands {
+ /// Delete an item in a container.
+ Item {
+ /// The database containing the item.
+ database: String,
+
+ /// The container containing the item.
+ container: String,
+
+ /// The ID of the item.
+ #[clap(long, short)]
+ item_id: String,
- /// The container containing the item.
- container: String,
+ /// The partition key of the item.
+ #[clap(long, short)]
+ partition_key: String,
+ },
- /// The ID of the item.
- #[clap(long, short)]
- item_id: String,
+ /// Create a database (does not support Entra ID).
+ #[cfg(feature = "control_plane")]
+ Database {
+ /// The ID of the database to delete.
+ id: String,
+ },
- /// The partition key of the item.
- #[clap(long, short)]
- partition_key: String,
+ /// Create a container (does not support Entra ID).
+ #[cfg(feature = "control_plane")]
+ Container {
+ /// The ID of the database the container is in.
+ database: String,
+
+ /// The ID of the container to delete
+ id: String,
+ },
}
impl DeleteCommand {
pub async fn run(self, client: CosmosClient) -> Result<(), Box> {
- let db_client = client.database_client(&self.database);
- let container_client = db_client.container_client(&self.container);
-
- let response = container_client
- .delete_item(&self.partition_key, &self.item_id, None)
- .await;
- match response {
- Err(e) if e.http_status() == Some(StatusCode::NotFound) => println!("Item not found!"),
- Ok(_) => println!("Item deleted"),
- Err(e) => return Err(e.into()),
- };
- Ok(())
+ match self.subcommand {
+ Subcommands::Item {
+ database,
+ container,
+ item_id,
+ partition_key,
+ } => {
+ let db_client = client.database_client(database);
+ let container_client = db_client.container_client(container);
+
+ let response = container_client
+ .delete_item(partition_key, item_id, None)
+ .await;
+ match response {
+ Err(e) if e.http_status() == Some(StatusCode::NotFound) => {
+ println!("Item not found!")
+ }
+ Ok(_) => println!("Item deleted"),
+ Err(e) => return Err(e.into()),
+ };
+ Ok(())
+ }
+
+ #[cfg(feature = "control_plane")]
+ Subcommands::Database { id } => {
+ let db_client = client.database_client(id);
+ db_client.delete(None).await?;
+ Ok(())
+ }
+
+ #[cfg(feature = "control_plane")]
+ Subcommands::Container { database, id } => {
+ let db_client = client.database_client(database);
+ let container_client = db_client.container_client(id);
+ container_client.delete(None).await?;
+ Ok(())
+ }
+ }
}
}
diff --git a/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs b/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs
index 00820e4286..fce90a7876 100644
--- a/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs
+++ b/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs
@@ -10,8 +10,12 @@ use crate::{
ItemOptions, PartitionKey, Query, QueryPartitionStrategy,
};
-use azure_core::{Context, Pager, Request, Response};
+#[cfg(feature = "control_plane")]
+use crate::DeleteContainerOptions;
+
+use azure_core::{Context, Method, Pager, Request, Response};
use serde::{de::DeserializeOwned, Serialize};
+use typespec_client_core::http::PagerResult;
use url::Url;
#[cfg(doc)]
@@ -46,8 +50,16 @@ pub trait ContainerClientMethods {
options: Option,
) -> azure_core::Result>;
- /// Returns the identifier of the Cosmos container.
- fn id(&self) -> &str;
+ /// Deletes this container.
+ ///
+ #[doc = include_str!("../../docs/control-plane-warning.md")]
+ ///
+ /// # Arguments
+ /// * `options` - Optional parameters for the request.
+ #[allow(async_fn_in_trait)] // REASON: See https://github.com/Azure/azure-sdk-for-rust/issues/1796 for detailed justification
+ #[cfg(feature = "control_plane")]
+ async fn delete(&self, options: Option)
+ -> azure_core::Result;
/// Creates a new item in the container.
///
@@ -314,18 +326,15 @@ pub trait ContainerClientMethods {
///
/// You can get a `Container` by calling [`DatabaseClient::container_client()`](crate::clients::DatabaseClient::container_client()).
pub struct ContainerClient {
- container_id: String,
container_url: Url,
pipeline: CosmosPipeline,
}
impl ContainerClient {
- pub(crate) fn new(pipeline: CosmosPipeline, database_url: &Url, container_id: &str) -> Self {
- let container_id = container_id.to_string();
- let container_url = database_url.with_path_segments(["colls", &container_id]);
+ pub(crate) fn new(pipeline: CosmosPipeline, database_url: &Url, container_name: &str) -> Self {
+ let container_url = database_url.with_path_segments(["colls", container_name]);
Self {
- container_id,
container_url,
pipeline,
}
@@ -340,14 +349,23 @@ impl ContainerClientMethods for ContainerClient {
// REASON: This is a documented public API so prefixing with '_' is undesirable.
options: Option,
) -> azure_core::Result> {
- let mut req = Request::new(self.container_url.clone(), azure_core::Method::Get);
+ let mut req = Request::new(self.container_url.clone(), Method::Get);
self.pipeline
.send(Context::new(), &mut req, ResourceType::Containers)
.await
}
- fn id(&self) -> &str {
- &self.container_id
+ #[cfg(feature = "control_plane")]
+ async fn delete(
+ &self,
+ #[allow(unused_variables)]
+ // REASON: This is a documented public API so prefixing with '_' is undesirable.
+ options: Option,
+ ) -> azure_core::Result {
+ let mut req = Request::new(self.container_url.clone(), Method::Delete);
+ self.pipeline
+ .send(Context::new(), &mut req, ResourceType::Containers)
+ .await
}
async fn create_item(
@@ -360,7 +378,7 @@ impl ContainerClientMethods for ContainerClient {
options: Option,
) -> azure_core::Result>> {
let url = self.container_url.with_path_segments(["docs"]);
- let mut req = Request::new(url, azure_core::Method::Post);
+ let mut req = Request::new(url, Method::Post);
req.insert_headers(&partition_key.into())?;
req.set_json(&item)?;
self.pipeline
@@ -381,7 +399,7 @@ impl ContainerClientMethods for ContainerClient {
let url = self
.container_url
.with_path_segments(["docs", item_id.as_ref()]);
- let mut req = Request::new(url, azure_core::Method::Put);
+ let mut req = Request::new(url, Method::Put);
req.insert_headers(&partition_key.into())?;
req.set_json(&item)?;
self.pipeline
@@ -399,7 +417,7 @@ impl ContainerClientMethods for ContainerClient {
options: Option,
) -> azure_core::Result>> {
let url = self.container_url.with_path_segments(["docs"]);
- let mut req = Request::new(url, azure_core::Method::Post);
+ let mut req = Request::new(url, Method::Post);
req.insert_header(constants::IS_UPSERT, "true");
req.insert_headers(&partition_key.into())?;
req.set_json(&item)?;
@@ -420,7 +438,7 @@ impl ContainerClientMethods for ContainerClient {
let url = self
.container_url
.with_path_segments(["docs", item_id.as_ref()]);
- let mut req = Request::new(url, azure_core::Method::Get);
+ let mut req = Request::new(url, Method::Get);
req.insert_headers(&partition_key.into())?;
self.pipeline
.send(Context::new(), &mut req, ResourceType::Items)
@@ -439,7 +457,7 @@ impl ContainerClientMethods for ContainerClient {
let url = self
.container_url
.with_path_segments(["docs", item_id.as_ref()]);
- let mut req = Request::new(url, azure_core::Method::Delete);
+ let mut req = Request::new(url, Method::Delete);
req.insert_headers(&partition_key.into())?;
self.pipeline
.send(Context::new(), &mut req, ResourceType::Items)
@@ -457,11 +475,38 @@ impl ContainerClientMethods for ContainerClient {
) -> azure_core::Result>> {
let mut url = self.container_url.clone();
url.append_path_segments(["docs"]);
- let mut base_request = Request::new(url, azure_core::Method::Post);
+ let mut base_req = Request::new(url, Method::Post);
+
+ base_req.insert_header(constants::QUERY, "True");
+ base_req.add_mandatory_header(&constants::QUERY_CONTENT_TYPE);
+
let QueryPartitionStrategy::SinglePartition(partition_key) = partition_key.into();
- base_request.insert_headers(&partition_key)?;
+ base_req.insert_headers(&partition_key)?;
- self.pipeline
- .send_query_request(query.into(), base_request, ResourceType::Items)
+ base_req.set_json(&query.into())?;
+
+ // We have to double-clone here.
+ // First we clone the pipeline to pass it in to the closure
+ let pipeline = self.pipeline.clone();
+ Ok(Pager::from_callback(move |continuation| {
+ // Then we have to clone it again to pass it in to the async block.
+ // This is because Pageable can't borrow any data, it has to own it all.
+ // That's probably good, because it means a Pageable can outlive the client that produced it, but it requires some extra cloning.
+ let pipeline = pipeline.clone();
+ let mut req = base_req.clone();
+ async move {
+ if let Some(continuation) = continuation {
+ req.insert_header(constants::CONTINUATION, continuation);
+ }
+
+ let response = pipeline
+ .send(Context::new(), &mut req, ResourceType::Items)
+ .await?;
+ Ok(PagerResult::from_response_header(
+ response,
+ &constants::CONTINUATION,
+ ))
+ }
+ }))
}
}
diff --git a/sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs b/sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs
index 178379abab..a2c437209e 100644
--- a/sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs
+++ b/sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs
@@ -1,15 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-use crate::clients::DatabaseClient;
-use crate::models::DatabaseQueryResults;
-use crate::pipeline::{AuthorizationPolicy, CosmosPipeline, ResourceType};
-use crate::utils::AppendPathSegments;
-use crate::{CosmosClientOptions, Query, QueryDatabasesOptions};
-use azure_core::credentials::TokenCredential;
-use azure_core::{Request, Url};
+use crate::{
+ clients::DatabaseClient,
+ models::DatabaseQueryResults,
+ pipeline::{AuthorizationPolicy, CosmosPipeline, ResourceType},
+ utils::AppendPathSegments,
+ CosmosClientOptions, Query, QueryDatabasesOptions,
+};
+use azure_core::{credentials::TokenCredential, Request, Url};
use std::sync::Arc;
+#[cfg(feature = "control_plane")]
+use crate::{
+ models::{DatabaseProperties, Item},
+ CreateDatabaseOptions,
+};
+#[cfg(feature = "control_plane")]
+use azure_core::{Context, Method, Response};
+#[cfg(feature = "control_plane")]
+use serde::Serialize;
+
#[cfg(feature = "key_auth")]
use azure_core::credentials::Secret;
@@ -65,6 +76,21 @@ pub trait CosmosClientMethods {
query: impl Into,
options: Option,
) -> azure_core::Result>;
+
+ /// Creates a new database.
+ ///
+ #[doc = include_str!("../../docs/control-plane-warning.md")]
+ ///
+ /// # Arguments
+ /// * `id` - The ID of the new database.
+ /// * `options` - Optional parameters for the request.
+ #[allow(async_fn_in_trait)] // REASON: See https://github.com/Azure/azure-sdk-for-rust/issues/1796 for detailed justification
+ #[cfg(feature = "control_plane")]
+ async fn create_database(
+ &self,
+ id: String,
+ options: Option,
+ ) -> azure_core::Result>>;
}
impl CosmosClient {
@@ -156,11 +182,33 @@ impl CosmosClientMethods for CosmosClient {
// REASON: This is a documented public API so prefixing with '_' is undesirable.
options: Option,
) -> azure_core::Result> {
- let mut url = self.endpoint.clone();
- url.append_path_segments(["dbs"]);
+ let url = self.endpoint.with_path_segments(["dbs"]);
let base_request = Request::new(url, azure_core::Method::Post);
self.pipeline
.send_query_request(query.into(), base_request, ResourceType::Databases)
}
+
+ #[cfg(feature = "control_plane")]
+ async fn create_database(
+ &self,
+ id: String,
+
+ #[allow(unused_variables)]
+ // REASON: This is a documented public API so prefixing with '_' is undesirable.
+ options: Option,
+ ) -> azure_core::Result>> {
+ #[derive(Serialize)]
+ struct RequestBody {
+ id: String,
+ }
+
+ let url = self.endpoint.with_path_segments(["dbs"]);
+ let mut req = Request::new(url, Method::Post);
+ req.set_json(&RequestBody { id })?;
+
+ self.pipeline
+ .send(Context::new(), &mut req, ResourceType::Databases)
+ .await
+ }
}
diff --git a/sdk/cosmos/azure_data_cosmos/src/clients/database_client.rs b/sdk/cosmos/azure_data_cosmos/src/clients/database_client.rs
index c81735b6a2..af90844be1 100644
--- a/sdk/cosmos/azure_data_cosmos/src/clients/database_client.rs
+++ b/sdk/cosmos/azure_data_cosmos/src/clients/database_client.rs
@@ -1,14 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-use crate::models::{ContainerQueryResults, DatabaseProperties};
-use crate::options::ReadDatabaseOptions;
-use crate::pipeline::ResourceType;
-use crate::utils::AppendPathSegments;
-use crate::{clients::ContainerClient, pipeline::CosmosPipeline};
-use crate::{Query, QueryContainersOptions};
+use crate::{
+ clients::ContainerClient,
+ models::{ContainerQueryResults, DatabaseProperties},
+ pipeline::{CosmosPipeline, ResourceType},
+ utils::AppendPathSegments,
+ Query, QueryContainersOptions, ReadDatabaseOptions,
+};
+
+#[cfg(feature = "control_plane")]
+use crate::{
+ models::{ContainerProperties, Item},
+ CreateContainerOptions, DeleteDatabaseOptions,
+};
use azure_core::{Context, Pager, Request, Response};
+
+#[cfg(feature = "control_plane")]
+use azure_core::Method;
+
use url::Url;
#[cfg(doc)]
@@ -80,6 +91,31 @@ pub trait DatabaseClientMethods {
query: impl Into,
options: Option,
) -> azure_core::Result>;
+
+ /// Creates a new container.
+ ///
+ #[doc = include_str!("../../docs/control-plane-warning.md")]
+ ///
+ /// # Arguments
+ /// * `properties` - A [`ContainerProperties`] describing the new container.
+ /// * `options` - Optional parameters for the request.
+ #[allow(async_fn_in_trait)] // REASON: See https://github.com/Azure/azure-sdk-for-rust/issues/1796 for detailed justification
+ #[cfg(feature = "control_plane")]
+ async fn create_container(
+ &self,
+ properties: ContainerProperties,
+ options: Option,
+ ) -> azure_core::Result>>;
+
+ /// Deletes this database.
+ ///
+ #[doc = include_str!("../../docs/control-plane-warning.md")]
+ ///
+ /// # Arguments
+ /// * `options` - Optional parameters for the request.
+ #[allow(async_fn_in_trait)] // REASON: See https://github.com/Azure/azure-sdk-for-rust/issues/1796 for detailed justification
+ #[cfg(feature = "control_plane")]
+ async fn delete(&self, options: Option) -> azure_core::Result;
}
/// A client for working with a specific database in a Cosmos DB account.
@@ -141,4 +177,35 @@ impl DatabaseClientMethods for DatabaseClient {
self.pipeline
.send_query_request(query.into(), base_request, ResourceType::Containers)
}
+
+ #[cfg(feature = "control_plane")]
+ async fn create_container(
+ &self,
+ properties: ContainerProperties,
+
+ #[allow(unused_variables)]
+ // REASON: This is a documented public API so prefixing with '_' is undesirable.
+ options: Option,
+ ) -> azure_core::Result>> {
+ let url = self.database_url.with_path_segments(["colls"]);
+ let mut req = Request::new(url, Method::Post);
+ req.set_json(&properties)?;
+
+ self.pipeline
+ .send(Context::new(), &mut req, ResourceType::Containers)
+ .await
+ }
+
+ #[cfg(feature = "control_plane")]
+ async fn delete(
+ &self,
+ #[allow(unused_variables)]
+ // REASON: This is a documented public API so prefixing with '_' is undesirable.
+ options: Option,
+ ) -> azure_core::Result {
+ let mut req = Request::new(self.database_url.clone(), Method::Delete);
+ self.pipeline
+ .send(Context::new(), &mut req, ResourceType::Databases)
+ .await
+ }
}
diff --git a/sdk/cosmos/azure_data_cosmos/src/constants.rs b/sdk/cosmos/azure_data_cosmos/src/constants.rs
index e42f88926e..bf3e184775 100644
--- a/sdk/cosmos/azure_data_cosmos/src/constants.rs
+++ b/sdk/cosmos/azure_data_cosmos/src/constants.rs
@@ -4,6 +4,8 @@
// Don't spell-check header names (which should start with 'x-').
// cSpell:ignoreRegExp /x-[^\s]+/
+//! Constants defining HTTP headers and other values relevant to Azure Cosmos DB APIs.
+
use azure_core::{headers::HeaderName, request_options::ContentType};
pub const QUERY: HeaderName = HeaderName::from_static("x-ms-documentdb-query");
diff --git a/sdk/cosmos/azure_data_cosmos/src/lib.rs b/sdk/cosmos/azure_data_cosmos/src/lib.rs
index 945847fabe..bda7be485b 100644
--- a/sdk/cosmos/azure_data_cosmos/src/lib.rs
+++ b/sdk/cosmos/azure_data_cosmos/src/lib.rs
@@ -20,7 +20,9 @@ pub(crate) mod utils;
pub mod models;
+#[doc(inline)]
pub use clients::{CosmosClient, CosmosClientMethods};
+
pub use options::*;
pub use partition_key::*;
pub use query::*;
diff --git a/sdk/cosmos/azure_data_cosmos/src/models/container_properties.rs b/sdk/cosmos/azure_data_cosmos/src/models/container_properties.rs
index f94e0a9d00..225e320dbe 100644
--- a/sdk/cosmos/azure_data_cosmos/src/models/container_properties.rs
+++ b/sdk/cosmos/azure_data_cosmos/src/models/container_properties.rs
@@ -1,9 +1,12 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
use std::time::Duration;
use azure_core::Model;
-use serde::{Deserialize, Deserializer};
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use crate::models::SystemProperties;
+use crate::models::{IndexingPolicy, PartitionKeyDefinition, SystemProperties};
#[cfg(doc)]
use crate::clients::ContainerClientMethods;
@@ -15,53 +18,89 @@ where
Ok(Option::::deserialize(deserializer)?.map(Duration::from_secs))
}
+fn serialize_ttl(duration: &Option, serializer: S) -> Result
+where
+ S: Serializer,
+{
+ match duration {
+ Some(d) => serializer.serialize_some(&d.as_secs()),
+ None => serializer.serialize_none(),
+ }
+}
+
/// Properties of a Cosmos DB container.
///
-/// Returned by [`ContainerClient::read()`](crate::clients::ContainerClient::read()).
-#[non_exhaustive]
-#[derive(Model, Clone, Debug, Deserialize, PartialEq, Eq)]
+/// # Constructing
+///
+/// When constructing this type, you should **always** use [Struct Update] syntax using `..Default::default()`, for example:
+///
+/// ```rust
+/// # use azure_data_cosmos::models::ContainerProperties;
+/// let properties = ContainerProperties {
+/// id: "NewContainer".to_string(),
+/// partition_key: "/partitionKey".into(),
+/// ..Default::default()
+/// };
+/// ```
+///
+/// Using this syntax has two purposes:
+///
+/// 1. It allows you to construct the type even though [`SystemProperties`] is not constructable (these properties should always be empty when you send a request).
+/// 2. It protects you if we add additional properties to this struct.
+///
+/// Also, note that the `id` and `partition_key` values are **required** by the server. You will get an error from the server if you omit them.
+///
+/// [Struct Update]: https://doc.rust-lang.org/stable/book/ch05-01-defining-structs.html?highlight=Struct#creating-instances-from-other-instances-with-struct-update-syntax
+#[derive(Model, Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ContainerProperties {
/// The ID of the container.
pub id: String,
- /// The time-to-live for items in the container.
- ///
- /// For more information see
- #[serde(default)]
- #[serde(deserialize_with = "deserialize_ttl")]
- pub default_ttl: Option,
-
- /// The time-to-live for the analytical store in the container.
- ///
- /// For more information see
- #[serde(default)]
- #[serde(deserialize_with = "deserialize_ttl")]
- pub analytical_storage_ttl: Option,
-
/// The definition of the partition key for the container.
pub partition_key: PartitionKeyDefinition,
/// The indexing policy for the container.
+ #[serde(skip_serializing_if = "Option::is_none")]
pub indexing_policy: Option,
/// The unique key policy for the container.
+ #[serde(skip_serializing_if = "Option::is_none")]
pub unique_key_policy: Option,
/// The conflict resolution policy for the container.
+ #[serde(skip_serializing_if = "Option::is_none")]
pub conflict_resolution_policy: Option,
/// The vector embedding policy for the container.
+ #[serde(skip_serializing_if = "Option::is_none")]
pub vector_embedding_policy: Option,
+ /// The time-to-live for items in the container.
+ ///
+ /// For more information see
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(deserialize_with = "deserialize_ttl")]
+ #[serde(serialize_with = "serialize_ttl")]
+ pub default_ttl: Option,
+
+ /// The time-to-live for the analytical store in the container.
+ ///
+ /// For more information see
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(deserialize_with = "deserialize_ttl")]
+ #[serde(serialize_with = "serialize_ttl")]
+ pub analytical_storage_ttl: Option,
+
/// A [`SystemProperties`] object containing common system properties for the container.
#[serde(flatten)]
pub system_properties: SystemProperties,
}
/// Represents the vector embedding policy for a container.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct VectorEmbeddingPolicy {
/// The [`VectorEmbedding`]s that describe the vector embeddings of items in the container.
@@ -70,8 +109,7 @@ pub struct VectorEmbeddingPolicy {
}
/// Represents the vector embedding policy for a container.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct VectorEmbedding {
/// The path to the property containing the vector.
@@ -88,8 +126,7 @@ pub struct VectorEmbedding {
}
/// Defines the data types of the elements of a vector.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum VectorDataType {
/// Represents the `float16` data type.
@@ -106,8 +143,7 @@ pub enum VectorDataType {
}
/// Defines the distance functions that can be used to calculate the distance between vectors.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum VectorDistanceFunction {
/// Represents the `euclidian` distance function.
@@ -121,136 +157,10 @@ pub enum VectorDistanceFunction {
DotProduct,
}
-/// Represents the partition key definition for a container.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct PartitionKeyDefinition {
- /// The list of partition keys paths.
- pub paths: Vec,
-
- /// The version of the partition key hash in use.
- #[serde(default)]
- pub version: i32,
-}
-
-/// Represents the indexing policy for a container.
-///
-/// For more information see
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct IndexingPolicy {
- /// Indicates that the indexing policy is automatic.
- #[serde(default)]
- pub automatic: bool,
-
- /// The indexing mode in use.
- #[serde(default)]
- pub indexing_mode: IndexingMode,
-
- /// The paths to be indexed.
- #[serde(default)]
- pub included_paths: Vec,
-
- /// The paths to be excluded.
- #[serde(default)]
- pub excluded_paths: Vec,
-
- /// A list of spatial indexes in the container.
- #[serde(default)]
- pub spatial_indexes: Vec,
-
- /// A list of composite indexes in the container
- #[serde(default)]
- pub composite_indexes: Vec,
-
- /// A list of vector indexes in the container
- #[serde(default)]
- pub vector_indexes: Vec,
-}
-
-/// Defines the indexing modes supported by Azure Cosmos DB.
-#[non_exhaustive]
-#[derive(Clone, Default, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub enum IndexingMode {
- Consistent,
-
- #[default]
- None,
-}
-
-/// Represents a JSON path.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct PropertyPath {
- // The path to the property referenced in this index.
- pub path: String,
-}
-
-/// Represents a spatial index
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct SpatialIndex {
- /// The path to the property referenced in this index.
- pub path: String,
-
- /// The spatial types used in this index
- pub types: Vec,
-}
-
-/// Defines the types of spatial data that can be indexed.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "PascalCase")]
-pub enum SpatialType {
- Point,
- Polygon,
- LineString,
- MultiPolygon,
-}
-
-/// Represents a composite index
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(transparent)]
-pub struct CompositeIndex {
- /// The properties in this composite index
- pub properties: Vec,
-}
-
-/// Describes a single property in a composite index.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct CompositeIndexProperty {
- /// The path to the property referenced in this index.
- pub path: String,
-
- /// The order of the composite index.
- ///
- /// For example, if you want to run the query "SELECT * FROM c ORDER BY c.age asc, c.height desc",
- /// then you'd specify the order for "/asc" to be *ascending* and the order for "/height" to be *descending*.
- pub order: CompositeIndexOrder,
-}
-
-/// Ordering values available for composite indexes.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub enum CompositeIndexOrder {
- Ascending,
- Descending,
-}
-
/// Represents a unique key policy for a container.
///
/// For more information see
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UniqueKeyPolicy {
/// The keys defined in this policy.
@@ -258,8 +168,7 @@ pub struct UniqueKeyPolicy {
}
/// Represents a single unique key for a container.
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UniqueKey {
/// The set of paths which must be unique for each item.
@@ -269,8 +178,7 @@ pub struct UniqueKey {
/// Represents a conflict resolution policy for a container
///
/// For more information, see
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ConflictResolutionPolicy {
/// The conflict resolution mode.
@@ -286,8 +194,7 @@ pub struct ConflictResolutionPolicy {
}
/// Defines conflict resolution types available in Azure Cosmos DB
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub enum ConflictResolutionMode {
/// Conflict resolution will be performed by using the highest value of the property specified by [`ConflictResolutionPolicy::resolution_path`].
@@ -297,172 +204,74 @@ pub enum ConflictResolutionMode {
Custom,
}
-/// Represents a vector index
-///
-/// For more information, see
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct VectorIndex {
- /// The path to the property referenced in this index.
- pub path: String,
+#[cfg(test)]
+mod tests {
+ use serde::{Deserialize, Serialize};
+ use std::time::Duration;
+
+ use crate::models::ContainerProperties;
+
+ #[cfg(test)]
+ #[derive(Debug, Deserialize, Serialize)]
+ struct DurationHolder {
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(deserialize_with = "super::deserialize_ttl")]
+ #[serde(serialize_with = "super::serialize_ttl")]
+ pub duration: Option,
+ }
- /// The type of the vector index.
- #[serde(rename = "type")] // "type" is a reserved word in Rust.
- pub index_type: VectorIndexType,
-}
+ #[test]
+ pub fn serialize_ttl() {
+ let value = DurationHolder {
+ duration: Some(Duration::from_secs(4200)),
+ };
+ let json = serde_json::to_string(&value).unwrap();
+ assert_eq!(r#"{"duration":4200}"#, json);
+ }
-/// Types of vector indexes supported by Cosmos DB
-#[non_exhaustive]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub enum VectorIndexType {
- Flat,
- QuantizedFlat,
- DiskANN,
-}
+ #[test]
+ pub fn serialize_missing_ttl() {
+ let value = DurationHolder { duration: None };
+ let json = serde_json::to_string(&value).unwrap();
+ assert_eq!(r#"{}"#, json);
+ }
-#[cfg(test)]
-mod tests {
- use crate::models::{
- CompositeIndex, CompositeIndexOrder, CompositeIndexProperty, IndexingMode, PropertyPath,
- SpatialIndex, SpatialType, VectorIndex, VectorIndexType,
- };
+ #[test]
+ pub fn deserialize_ttl() {
+ let value: DurationHolder = serde_json::from_str(r#"{"duration":4200}"#).unwrap();
+ assert_eq!(Some(Duration::from_secs(4200)), value.duration);
+ }
- use super::IndexingPolicy;
+ #[test]
+ pub fn deserialize_missing_ttl() {
+ let value: DurationHolder = serde_json::from_str(r#"{}"#).unwrap();
+ assert_eq!(None, value.duration);
+ }
+
+ #[test]
+ pub fn deserialize_null_ttl() {
+ let value: DurationHolder = serde_json::from_str(r#"{"duration":null}"#).unwrap();
+ assert_eq!(None, value.duration);
+ }
#[test]
- pub fn deserialize_indexing_policy() {
- // A fairly complete deserialization test that covers most of the indexing policies described in our docs.
- let policy = r#"
- {
- "indexingMode": "consistent",
- "includedPaths": [
- {
- "path": "/*"
- }
- ],
- "excludedPaths": [
- {
- "path": "/path/to/single/excluded/property/?"
- },
- {
- "path": "/path/to/root/of/multiple/excluded/properties/*"
- }
- ],
- "spatialIndexes": [
- {
- "path": "/path/to/geojson/property/?",
- "types": [
- "Point",
- "Polygon",
- "MultiPolygon",
- "LineString"
- ]
- }
- ],
- "vectorIndexes": [
- {
- "path": "/vector1",
- "type": "quantizedFlat"
- },
- {
- "path": "/vector2",
- "type": "diskANN"
- }
- ],
- "compositeIndexes":[
- [
- {
- "path":"/name",
- "order":"ascending"
- },
- {
- "path":"/age",
- "order":"descending"
- }
- ],
- [
- {
- "path":"/name2",
- "order":"descending"
- },
- {
- "path":"/age2",
- "order":"ascending"
- }
- ]
- ],
- "extraValueNotCurrentlyPresentInModel": {
- "this": "should not fail"
- }
- }
- "#;
-
- let policy: IndexingPolicy = serde_json::from_str(policy).unwrap();
+ pub fn container_properties_default_serialization() {
+ // This test asserts that the default value serializes the same way across SDK versions.
+ // When new properties are added to ContainerProperties, this test should not break.
+ // If it does, users who are using `..Default::default()` syntax will start sending an unexpected payload to the server.
+ // In rare cases, it's reasonable to update this test, if the new generated JSON is considered _equivalent_ to the original by the server.
+ // But in general, a failure in this test means that the same user code will send an unexpected value in a new version of the SDK.
+ let properties = ContainerProperties {
+ id: "MyContainer".to_string(),
+ partition_key: "/partitionKey".into(),
+ ..Default::default()
+ };
+ let json = serde_json::to_string(&properties).unwrap();
assert_eq!(
- IndexingPolicy {
- automatic: false,
- indexing_mode: IndexingMode::Consistent,
- included_paths: vec![PropertyPath {
- path: "/*".to_string(),
- }],
- excluded_paths: vec![
- PropertyPath {
- path: "/path/to/single/excluded/property/?".to_string()
- },
- PropertyPath {
- path: "/path/to/root/of/multiple/excluded/properties/*".to_string()
- },
- ],
- spatial_indexes: vec![SpatialIndex {
- path: "/path/to/geojson/property/?".to_string(),
- types: vec![
- SpatialType::Point,
- SpatialType::Polygon,
- SpatialType::MultiPolygon,
- SpatialType::LineString,
- ]
- }],
- composite_indexes: vec![
- CompositeIndex {
- properties: vec![
- CompositeIndexProperty {
- path: "/name".to_string(),
- order: CompositeIndexOrder::Ascending,
- },
- CompositeIndexProperty {
- path: "/age".to_string(),
- order: CompositeIndexOrder::Descending,
- },
- ]
- },
- CompositeIndex {
- properties: vec![
- CompositeIndexProperty {
- path: "/name2".to_string(),
- order: CompositeIndexOrder::Descending,
- },
- CompositeIndexProperty {
- path: "/age2".to_string(),
- order: CompositeIndexOrder::Ascending,
- },
- ]
- },
- ],
- vector_indexes: vec![
- VectorIndex {
- path: "/vector1".to_string(),
- index_type: VectorIndexType::QuantizedFlat,
- },
- VectorIndex {
- path: "/vector2".to_string(),
- index_type: VectorIndexType::DiskANN,
- }
- ]
- },
- policy
+ "{\"id\":\"MyContainer\",\"partitionKey\":{\"paths\":[\"/partitionKey\"],\"kind\":\"Hash\",\"version\":2}}",
+ json
);
}
}
diff --git a/sdk/cosmos/azure_data_cosmos/src/models/indexing_policy.rs b/sdk/cosmos/azure_data_cosmos/src/models/indexing_policy.rs
new file mode 100644
index 0000000000..dcab0ea197
--- /dev/null
+++ b/sdk/cosmos/azure_data_cosmos/src/models/indexing_policy.rs
@@ -0,0 +1,348 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+use serde::{Deserialize, Serialize};
+
+/// Represents the indexing policy for a container.
+///
+/// For more information see
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct IndexingPolicy {
+ /// Indicates that the indexing policy is automatic.
+ #[serde(default)]
+ pub automatic: bool,
+
+ /// The indexing mode in use.
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub indexing_mode: Option,
+
+ /// The paths to be indexed.
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub included_paths: Vec,
+
+ /// The paths to be excluded.
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub excluded_paths: Vec,
+
+ /// A list of spatial indexes in the container.
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub spatial_indexes: Vec,
+
+ /// A list of composite indexes in the container
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub composite_indexes: Vec,
+
+ /// A list of vector indexes in the container
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub vector_indexes: Vec,
+}
+
+/// Defines the indexing modes supported by Azure Cosmos DB.
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub enum IndexingMode {
+ Consistent,
+ None,
+}
+
+/// Represents a JSON path.
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct PropertyPath {
+ // The path to the property referenced in this index.
+ pub path: String,
+}
+
+/// Represents a spatial index
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct SpatialIndex {
+ /// The path to the property referenced in this index.
+ pub path: String,
+
+ /// The spatial types used in this index
+ pub types: Vec,
+}
+
+/// Defines the types of spatial data that can be indexed.
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "PascalCase")]
+pub enum SpatialType {
+ Point,
+ Polygon,
+ LineString,
+ MultiPolygon,
+}
+
+/// Represents a composite index
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(transparent)]
+pub struct CompositeIndex {
+ /// The properties in this composite index
+ pub properties: Vec,
+}
+
+/// Describes a single property in a composite index.
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct CompositeIndexProperty {
+ /// The path to the property referenced in this index.
+ pub path: String,
+
+ /// The order of the composite index.
+ ///
+ /// For example, if you want to run the query "SELECT * FROM c ORDER BY c.age asc, c.height desc",
+ /// then you'd specify the order for "/asc" to be *ascending* and the order for "/height" to be *descending*.
+ pub order: CompositeIndexOrder,
+}
+
+/// Ordering values available for composite indexes.
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub enum CompositeIndexOrder {
+ Ascending,
+ Descending,
+}
+
+/// Represents a vector index
+///
+/// For more information, see
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct VectorIndex {
+ /// The path to the property referenced in this index.
+ pub path: String,
+
+ /// The type of the vector index.
+ #[serde(rename = "type")] // "type" is a reserved word in Rust.
+ pub index_type: VectorIndexType,
+}
+
+/// Types of vector indexes supported by Cosmos DB
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub enum VectorIndexType {
+ /// Represents the `flat` vector index type.
+ Flat,
+
+ /// Represents the `quantizedFlat` vector index type.
+ QuantizedFlat,
+
+ /// Represents the `diskANN` vector index type.
+ DiskANN,
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::models::{
+ CompositeIndex, CompositeIndexOrder, CompositeIndexProperty, IndexingMode, IndexingPolicy,
+ PropertyPath, SpatialIndex, SpatialType, VectorIndex, VectorIndexType,
+ };
+
+ #[test]
+ pub fn deserialize_indexing_policy() {
+ // A fairly complete deserialization test that covers most of the indexing policies described in our docs.
+ let policy = r#"
+ {
+ "indexingMode": "consistent",
+ "includedPaths": [
+ {
+ "path": "/*"
+ }
+ ],
+ "excludedPaths": [
+ {
+ "path": "/path/to/single/excluded/property/?"
+ },
+ {
+ "path": "/path/to/root/of/multiple/excluded/properties/*"
+ }
+ ],
+ "spatialIndexes": [
+ {
+ "path": "/path/to/geojson/property/?",
+ "types": [
+ "Point",
+ "Polygon",
+ "MultiPolygon",
+ "LineString"
+ ]
+ }
+ ],
+ "vectorIndexes": [
+ {
+ "path": "/vector1",
+ "type": "quantizedFlat"
+ },
+ {
+ "path": "/vector2",
+ "type": "diskANN"
+ }
+ ],
+ "compositeIndexes":[
+ [
+ {
+ "path":"/name",
+ "order":"ascending"
+ },
+ {
+ "path":"/age",
+ "order":"descending"
+ }
+ ],
+ [
+ {
+ "path":"/name2",
+ "order":"descending"
+ },
+ {
+ "path":"/age2",
+ "order":"ascending"
+ }
+ ]
+ ],
+ "extraValueNotCurrentlyPresentInModel": {
+ "this": "should not fail"
+ }
+ }
+ "#;
+
+ let policy: IndexingPolicy = serde_json::from_str(policy).unwrap();
+
+ assert_eq!(
+ IndexingPolicy {
+ automatic: false,
+ indexing_mode: Some(IndexingMode::Consistent),
+ included_paths: vec![PropertyPath {
+ path: "/*".to_string(),
+ }],
+ excluded_paths: vec![
+ PropertyPath {
+ path: "/path/to/single/excluded/property/?".to_string()
+ },
+ PropertyPath {
+ path: "/path/to/root/of/multiple/excluded/properties/*".to_string()
+ },
+ ],
+ spatial_indexes: vec![SpatialIndex {
+ path: "/path/to/geojson/property/?".to_string(),
+ types: vec![
+ SpatialType::Point,
+ SpatialType::Polygon,
+ SpatialType::MultiPolygon,
+ SpatialType::LineString,
+ ]
+ }],
+ composite_indexes: vec![
+ CompositeIndex {
+ properties: vec![
+ CompositeIndexProperty {
+ path: "/name".to_string(),
+ order: CompositeIndexOrder::Ascending,
+ },
+ CompositeIndexProperty {
+ path: "/age".to_string(),
+ order: CompositeIndexOrder::Descending,
+ },
+ ]
+ },
+ CompositeIndex {
+ properties: vec![
+ CompositeIndexProperty {
+ path: "/name2".to_string(),
+ order: CompositeIndexOrder::Descending,
+ },
+ CompositeIndexProperty {
+ path: "/age2".to_string(),
+ order: CompositeIndexOrder::Ascending,
+ },
+ ]
+ },
+ ],
+ vector_indexes: vec![
+ VectorIndex {
+ path: "/vector1".to_string(),
+ index_type: VectorIndexType::QuantizedFlat,
+ },
+ VectorIndex {
+ path: "/vector2".to_string(),
+ index_type: VectorIndexType::DiskANN,
+ }
+ ]
+ },
+ policy
+ );
+ }
+
+ #[test]
+ pub fn serialize_indexing_policy() {
+ let policy = IndexingPolicy {
+ automatic: true,
+ indexing_mode: None,
+ included_paths: vec![PropertyPath {
+ path: "/*".to_string(),
+ }],
+ excluded_paths: vec![
+ PropertyPath {
+ path: "/path/to/single/excluded/property/?".to_string(),
+ },
+ PropertyPath {
+ path: "/path/to/root/of/multiple/excluded/properties/*".to_string(),
+ },
+ ],
+ spatial_indexes: vec![
+ SpatialIndex {
+ path: "/path/to/geojson/property/?".to_string(),
+ types: vec![
+ SpatialType::Point,
+ SpatialType::Polygon,
+ SpatialType::MultiPolygon,
+ SpatialType::LineString,
+ ],
+ },
+ SpatialIndex {
+ path: "/path/to/geojson/property2/?".to_string(),
+ types: vec![],
+ },
+ ],
+ composite_indexes: vec![
+ CompositeIndex {
+ properties: vec![
+ CompositeIndexProperty {
+ path: "/name".to_string(),
+ order: CompositeIndexOrder::Ascending,
+ },
+ CompositeIndexProperty {
+ path: "/age".to_string(),
+ order: CompositeIndexOrder::Descending,
+ },
+ ],
+ },
+ CompositeIndex { properties: vec![] },
+ ],
+ vector_indexes: vec![
+ VectorIndex {
+ path: "/vector1".to_string(),
+ index_type: VectorIndexType::QuantizedFlat,
+ },
+ VectorIndex {
+ path: "/vector2".to_string(),
+ index_type: VectorIndexType::DiskANN,
+ },
+ ],
+ };
+ let json = serde_json::to_string(&policy).unwrap();
+
+ assert_eq!(
+ "{\"automatic\":true,\"includedPaths\":[{\"path\":\"/*\"}],\"excludedPaths\":[{\"path\":\"/path/to/single/excluded/property/?\"},{\"path\":\"/path/to/root/of/multiple/excluded/properties/*\"}],\"spatialIndexes\":[{\"path\":\"/path/to/geojson/property/?\",\"types\":[\"Point\",\"Polygon\",\"MultiPolygon\",\"LineString\"]},{\"path\":\"/path/to/geojson/property2/?\",\"types\":[]}],\"compositeIndexes\":[[{\"path\":\"/name\",\"order\":\"ascending\"},{\"path\":\"/age\",\"order\":\"descending\"}],[]],\"vectorIndexes\":[{\"path\":\"/vector1\",\"type\":\"quantizedFlat\"},{\"path\":\"/vector2\",\"type\":\"diskANN\"}]}",
+ json
+ );
+ }
+}
diff --git a/sdk/cosmos/azure_data_cosmos/src/models/mod.rs b/sdk/cosmos/azure_data_cosmos/src/models/mod.rs
index 75c9699612..ebe4553e02 100644
--- a/sdk/cosmos/azure_data_cosmos/src/models/mod.rs
+++ b/sdk/cosmos/azure_data_cosmos/src/models/mod.rs
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-//! Model types sent to and received from the Cosmos DB API.
+//! Model types sent to and received from the Azure Cosmos DB API.
use azure_core::{date::OffsetDateTime, Model};
-use serde::{de::DeserializeOwned, Deserialize, Deserializer};
+use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize};
#[cfg(doc)]
use crate::{
@@ -13,10 +13,14 @@ use crate::{
};
mod container_properties;
+mod indexing_policy;
mod item;
+mod partition_key_definition;
pub use container_properties::*;
+pub use indexing_policy::*;
pub use item::*;
+pub use partition_key_definition::*;
fn deserialize_cosmos_timestamp<'de, D>(deserializer: D) -> Result