Edit

Share via


Build a Rust console app with Azure Cosmos DB for MongoDB vCore

In this guide, you create a Rust console application to connect to an Azure Cosmos DB for MongoDB vCore cluster. The guide covers setting up your development environment, using the azure_identity crate from the Azure SDK for Rust to authenticate, and managing documents within the database.

Prerequisites

  • An existing Azure Cosmos DB for MongoDB (vCore) cluster.
  • Microsoft Entra authentication configured for the cluster with your identity granted dbOwner role.

  • Latest version of Python.

Configure your console application

Next, create a new console application project and import the necessary libraries to authenticate to your cluster.

  1. Create a new Rust project using cargo new.

    cargo new cosmos-mongodb-app
    cd cosmos-mongodb-app
    
  2. Add the azure_core crate to your dependencies.

    cargo add azure_core
    
  3. Add the azure_identity crate for authentication.

    cargo add azure_identity
    
  4. Add the mongodb driver crate to interact with your cluster.

    cargo add mongodb
    
  5. For async operations, also add the supporting tokio, futures, and serde crates.

    cargo add tokio --features full
    cargo add futures
    cargo add serde --features derive
    

Connect to the cluster

Now, use the Azure.Identity library to get a TokenCredential to use to connect to your cluster. The official MongoDB driver has a special interface that must be implemented to obtain tokens from Microsoft Entra for use when connecting to the cluster.

  1. Open your main.rs file and import the necessary crates and modules.

    use azure_core::credentials::TokenCredential;
    use azure_identity::DefaultAzureCredential;
    use futures::{FutureExt, TryStreamExt};
    use mongodb::{
        Client,
        bson::doc,
        options::{
            AuthMechanism, ClientOptions, Credential,
            oidc::{self, IdpServerResponse},
        },
    };
    use serde::{Deserialize, Serialize};
    
  2. Create the main async function with the necessary error handling.

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
    
        Ok(())
    }
    
  3. Create a new instance of struct azure_identity::DefaultAzureCredential.

    let credential = DefaultAzureCredential::new()?;
    
  4. Create a credential callback to handle token requests from the MongoDB client.

    let azure_identity_token_credential = Credential::builder()
        .mechanism(AuthMechanism::MongoDbOidc)
        .oidc_callback(oidc::Callback::machine(move |_| {
            let azure_credential = credential.clone();
            async move {
                let access_token = azure_credential
                    .get_token(&["https://ossrdbms-aad.database.windows.net/.default"])
                    .await
                    .map_err(|e| {
                        mongodb::error::Error::custom(format!("Azure token error: {}", e))
                    })?;
                Ok(IdpServerResponse::builder()
                    .access_token(access_token.token.secret().to_owned())
                    .build())
            }
            .boxed()
        }))
        .build()
        .into();
    
  5. Define a uniform resource indicator (URI) from your cluster using its name, scheme, and the global endpoint.

    let cluster_name = "<azure-cosmos-db-mongodb-vcore-cluster-name>";
    
    let uri = format!(
        "mongodb+srv://{}.global.mongocluster.cosmos.azure.com/",
        cluster_name
    );
    
  6. Construct a mongodb::ClientOptions instance using best practices configuration, your URI, and the credential callback.

    let mut client_options = ClientOptions::parse(uri).await?;
    
    client_options.connect_timeout = Some(std::time::Duration::from_secs(120));
    client_options.tls = Some(mongodb::options::Tls::Enabled(Default::default()));
    client_options.retry_writes = Some(true);
    
    client_options.credential = Some(azure_identity_token_credential);
    
  7. Create a new instance of mongodb::Client using the constructed settings.

    let client = Client::with_options(client_options)?;
    
    println!("Client created");
    

Perform common operations

Finally, use the official library to perform common tasks with databases, collections, and documents. Here, you use the same classes and methods you would use to interact with MongoDB or DocumentDB to manage your collections and items.

  1. Create a Rust struct to represent your Product documents with serde serialization support.

    #[derive(Serialize, Deserialize, Debug)]
    struct Product {
        _id: String,
        category: String,
        name: String,
        quantity: i32,
        price: f64,
        clearance: bool,
    }
    
  2. Get a reference to your database by name.

    let database = client.database("<database-name>");
    
    println!("Database pointer created");
    
  3. Get a reference to your collection.

    let collection = database.collection::<Product>("<collection-name>");
    
    println!("Collection pointer created");
    
  4. Create a document using collection.update_one and upsert it into the collection.

    let document = Product {
        _id: "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb".to_string(),
        category: "gear-surf-surfboards".to_string(),
        name: "Yamba Surfboard".to_string(),
        quantity: 12,
        price: 850.00,
        clearance: false,
    };
    
    let response = collection
        .update_one(
            doc! { "_id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb" },
            doc! { "$set": mongodb::bson::to_document(&document)? },
        )
        .upsert(true)
        .await?;
    
    println!("Documents upserted count:\t{}", response.modified_count);
    
  5. Read a specific document from the collection using collection.find_one and a filter.

    let document = collection
        .find_one(doc! { "_id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb" })
        .await?;
    
    println!("Read document _id:\t{:#?}", document.unwrap()._id);
    
  6. Query for multiple documents matching a filter using collection.find.

    let filter = doc! { "category": "gear-surf-surfboards" };
    
    let mut cursor = collection.find(filter).await?;
    
    while let Some(document) = cursor.try_next().await? {
        println!("Found document:\t{:#?}", document);
    }