Edit

Use the azure_core crate for advanced scenarios in Rust applications

The azure_core crate provides fundamental types, traits, and abstractions that form the foundation of all Azure SDK for Rust crates. This article demonstrates how to implement custom HTTP clients, create custom policies for request/response processing, and access detailed error information from Azure services.

Crates | API reference documentation | Source code

HTTP requests

This section covers how to customize HTTP client behavior and implement custom HTTP clients for Azure SDK operations.

Implement a custom HTTP client

You can replace the default HTTP client (reqwest) with your own implementation by implementing the HttpClient trait. This is useful when you need specific HTTP client features, want to avoid tokio dependencies, or need to integrate with existing HTTP infrastructure. This example shows how to use the ureq HTTP client, which is a synchronous client that can be useful in embedded or resource-constrained environments.

First, implement the HttpClient trait for your custom client:

#[derive(Debug)]
struct Agent(ureq::Agent);

impl Default for Agent {
    fn default() -> Self {
        Self(
            ureq::Agent::config_builder()
                .https_only(true)
                .tls_config(
                    TlsConfig::builder()
                        .provider(TlsProvider::NativeTls)
                        .build(),
                )
                .build()
                .into(),
        )
    }
}

Then, configure the client with your custom HTTP implementation:

#[async_trait]
impl HttpClient for Agent {
    async fn execute_request(&self, request: &Request) -> azure_core::Result<AsyncRawResponse> {
        let request = into_request(request)?;
        let response = self
            .0
            .run(request)
            .with_context_fn(ErrorKind::Io, || "failed to send request")?;

        into_response(response)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let vault_url = env::var("AZURE_KEYVAULT_URL")
        .map_err(|_| "Environment variable AZURE_KEYVAULT_URL is required")?;

    let credential = DeveloperToolsCredential::new(None)?;

    let agent = Arc::new(Agent::default());
    let options = SecretClientOptions {
        client_options: ClientOptions {
            transport: Some(Transport::new(agent)),
            ..Default::default()
        },
        ..Default::default()
    };

    let client = SecretClient::new(&vault_url, credential.clone(), Some(options))?;
    let mut pager = client.list_secret_properties(None)?;
    while let Some(secret) = pager.try_next().await? {
        let name = secret.resource_id()?.name;
        println!("Secret: {name}");
    }

    Ok(())
}

Implement custom HTTP policies

You can customize request and response processing by implementing the Policy trait. Policies let you modify requests before they're sent or inspect responses before they're returned. This example shows how to create a policy that removes the request's User-Agent header:

#[derive(Debug)]
struct RemoveUserAgent;

#[async_trait]
impl Policy for RemoveUserAgent {
    async fn send(
        &self,
        ctx: &Context,
        request: &mut Request,
        next: &[Arc<dyn Policy>],
    ) -> PolicyResult {
        let headers = request.headers_mut();

        // Note: HTTP headers are case-insensitive but client-added headers are normalized to lowercase.
        headers.remove("user-agent");

        next[0].send(ctx, request, &next[1..]).await
    }
}

Add your custom policy to the client options object:

// Policies are created in an Arc to be generally shared.
let remove_user_agent = Arc::new(RemoveUserAgent);

// Construct client options with your policy that runs after the built-in per-call UserAgentPolicy.
let mut options = SecretClientOptions::default();
options
    .client_options
    .per_call_policies
    .push(remove_user_agent);

Then construct the client:

let client = SecretClient::new(
    "https://my-vault.vault.azure.net",
    credential.clone(),
    Some(options),
)?;

Service error details

You can access detailed errors returned by the Azure service. The following example demonstrates deserializing a standard Azure error response to get more details such as the error_code and error details.

let error = client
    .set_secret("secret_name", secret.try_into()?, None)
    .await
    .unwrap_err();
let ErrorKind::HttpResponse {
    status,
    error_code,
    raw_response: Some(raw_response),
} = error.kind()
else {
    panic!("expected HTTP error response");
};

Next steps