busbar-sf-api 0.0.3

Salesforce API client library for Rust
Documentation

busbar-sf-api

Crates.io Documentation License: MIT OR Apache-2.0

A comprehensive Salesforce API client library for Rust, providing type-safe access to Salesforce APIs with built-in authentication, retry logic, and error handling.

This repository is undergoing rapid development and is not yet stable for production use. We're refactoring these API's out of other projects as reusable crates after realizing there is shockingly little coverage of Salesforce APIs in Rust, yet. More to come on that front. For now, if you're interested in providing feedback or even collaborating, let us know in [Discussions](https://github.com/composable-delivery/busbar-sf-apii>.

Features

  • Authentication - OAuth 2.0 flows, JWT Bearer, and credentials management
  • REST API - CRUD operations, queries, composite requests, and collections
  • QueryBuilder - Fluent API with automatic SOQL injection prevention (secure by default)
  • Bulk API 2.0 - Large-scale data operations with efficient processing
  • Tooling API - Apex operations, debug logs, and code coverage
  • Metadata API - Deploy and retrieve Salesforce metadata
  • Async/Await - Built on Tokio for high-performance async operations
  • Retry Logic - Automatic retries with exponential backoff
  • Security - Sensitive data redaction in debug output and logging
  • Tracing - Built-in tracing support for observability

Crates

This workspace includes the following crates:

Installation

Add this to your Cargo.toml:

[dependencies]
busbar-sf-api = "0.0.2"
tokio = { version = "1.40", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Or install individual crates as needed:

[dependencies]
busbar-sf-auth = "0.0.2"
busbar-sf-rest = "0.0.2"

Quick Start

Safe Query Builder (Recommended)

The QueryBuilder provides a fluent API with automatic SOQL injection prevention:

use busbar_sf_auth::SalesforceCredentials;
use busbar_sf_rest::{QueryBuilder, SalesforceRestClient};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Account {
    #[serde(rename = "Id")]
    id: String,
    #[serde(rename = "Name")]
    name: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let creds = SalesforceCredentials::from_sfdx_alias("my-org").await?;
    let client = SalesforceRestClient::new(
        creds.instance_url(),
        creds.access_token(),
    )?;

    // User input is automatically escaped - safe by default!
    let user_input = "O'Brien's Company";
    
    let accounts: Vec<Account> = QueryBuilder::new("Account")?
        .select(&["Id", "Name", "Industry"])
        .where_eq("Name", user_input)?  // Automatically escaped!
        .limit(10)
        .execute(&client)
        .await?;

    for account in accounts {
        println!("{}: {}", account.id, account.name);
    }

    Ok(())
}

Using Credentials from Salesforce CLI

use busbar_sf_auth::SalesforceCredentials;
use busbar_sf_rest::SalesforceRestClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Get credentials from SF CLI
    let creds = SalesforceCredentials::from_sfdx_alias("my-org").await?;

    // Create REST client
    let client = SalesforceRestClient::new(
        creds.instance_url(),
        creds.access_token(),
    )?;

    // Query accounts
    let accounts: Vec<serde_json::Value> = client
        .query_all("SELECT Id, Name FROM Account LIMIT 10")
        .await?;

    println!("Retrieved {} accounts", accounts.len());
    // Note: In production, be cautious about logging sensitive data
    // For debugging:
    // for account in &accounts {
    //     println!("Account: {}", account["Name"]);
    // }

    Ok(())
}

OAuth 2.0 Authentication

use busbar_sf_auth::{OAuthConfig, OAuthFlow};
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load credentials from environment variables - NEVER hardcode credentials!
    let config = OAuthConfig {
        client_id: env::var("SF_CLIENT_ID")?,
        client_secret: env::var("SF_CLIENT_SECRET").ok(),
        redirect_uri: env::var("SF_REDIRECT_URI")
            .unwrap_or_else(|_| "http://localhost:8080/callback".to_string()),
        ..Default::default()
    };

    let flow = OAuthFlow::new(config);
    
    // Get authorization URL
    let auth_url = flow.authorization_url(&["api", "refresh_token"]);
    println!("Visit: {}", auth_url);

    // After user authorizes, exchange code for token
    let token = flow.exchange_code("authorization_code").await?;
    
    Ok(())
}

Bulk API Operations

use busbar_sf_bulk::{BulkClient, BulkOperation};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Account {
    #[serde(rename = "Name")]
    name: String,
    #[serde(rename = "Industry")]
    industry: Option<String>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let creds = SalesforceCredentials::from_sfdx_alias("my-org").await?;
    let client = BulkClient::new(
        creds.instance_url(),
        creds.access_token(),
    )?;

    // Create insert job
    let accounts = vec![
        Account { name: "Acme Corp".to_string(), industry: Some("Technology".to_string()) },
        Account { name: "Global Industries".to_string(), industry: Some("Manufacturing".to_string()) },
    ];

    let job_id = client
        .create_job("Account", BulkOperation::Insert)
        .await?;

    client.upload_job_data(&job_id, &accounts).await?;
    let results = client.wait_for_job(&job_id).await?;

    println!("Processed {} records", results.number_records_processed);
    
    Ok(())
}

Metadata API

use busbar_sf_metadata::MetadataClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let creds = SalesforceCredentials::from_sfdx_alias("my-org").await?;
    let client = MetadataClient::new(
        creds.instance_url(),
        creds.access_token(),
    ).await?;

    // Retrieve metadata
    let metadata_types = vec!["ApexClass", "ApexTrigger"];
    let retrieve_id = client.retrieve(&metadata_types).await?;
    
    // Check retrieve status
    let status = client.check_retrieve_status(&retrieve_id).await?;
    
    if status.done {
        println!("Retrieve complete!");
    }

    Ok(())
}

Examples

See the examples directory for comprehensive examples:

Run any example with:

cargo run --example basic_auth
cargo run --example rest_crud
cargo run --example queries

Security

This library is designed with security in mind. See SECURITY.md for full details.

Key Security Features:

  • QueryBuilder - Fluent API with automatic SOQL injection prevention (RECOMMENDED)
  • ✅ Automatic credential redaction in logs and debug output
  • ✅ SOQL injection prevention utilities (escape_string, field validation)
  • ✅ URL parameter encoding to prevent path traversal
  • ✅ Secure token storage with restrictive file permissions
  • ✅ Input validation for IDs, field names, and SObject names

Security Best Practices:

use busbar_sf_rest::QueryBuilder;

// RECOMMENDED - QueryBuilder is safe by default
let accounts: Vec<Account> = QueryBuilder::new("Account")?
    .select(&["Id", "Name"])
    .where_eq("Name", user_input)?  // Automatically escaped!
    .execute(&client)
    .await?;

// Alternative - Manual escaping (easy to forget!)
use busbar_sf_client::security::soql;
let safe_name = soql::escape_string(user_input);
let query = format!("SELECT Id FROM Account WHERE Name = '{}'", safe_name);

For security vulnerabilities, see our Security Policy

Requirements

  • Rust 1.88 or later
  • Tokio runtime for async operations

Documentation

Contributing

We welcome contributions! Please see our Contributing Guidelines for details.

Development

# Clone the repository
git clone https://github.com/composable-delivery/busbar-sf-api.git
cd busbar-sf-api

# Build all crates
cargo build --workspace

# Run tests
cargo test --workspace

# Run integration tests (requires SF_AUTH_URL or authenticated SF CLI)
# See INTEGRATION_TESTING.md for details
SF_AUTH_URL="force://..." cargo test --test integration_sf_auth_url -- --ignored
SF_AUTH_URL="force://..." cargo test --test integration_examples -- --ignored

# Run linter
cargo clippy --workspace -- -D warnings

# Format code
cargo fmt --workspace

Community

License

http://www.apache.org/licenses/LICENSE-2.0 Licensed under either of:http://opensource.org/licenses/MIT

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.