Skip to main content

SalesforceRestClient

Struct SalesforceRestClient 

Source
pub struct SalesforceRestClient { /* private fields */ }
Expand description

Salesforce REST API client.

Provides typed methods for all REST API operations:

  • CRUD operations on SObjects
  • SOQL queries with automatic pagination
  • SOSL search
  • Describe operations
  • Composite API
  • SObject Collections

§Example

use sf_rest::SalesforceRestClient;

let client = SalesforceRestClient::new(
    "https://myorg.my.salesforce.com",
    "access_token_here",
)?;

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

// Create
let id = client.create("Account", &json!({"Name": "New Account"})).await?;

// Update
client.update("Account", &id, &json!({"Name": "Updated"})).await?;

// Delete
client.delete("Account", &id).await?;

Implementations§

Source§

impl SalesforceRestClient

Source

pub fn new( instance_url: impl Into<String>, access_token: impl Into<String>, ) -> Result<SalesforceRestClient, Error>

Create a new REST client with the given instance URL and access token.

Examples found in repository?
examples/error_handling.rs (line 25)
19async fn main() -> Result<(), Box<dyn std::error::Error>> {
20    // Initialize tracing/logging if needed
21
22    println!("=== Salesforce Error Handling Examples ===\n");
23
24    let creds = get_credentials().await?;
25    let client = SalesforceRestClient::new(creds.instance_url(), creds.access_token())?;
26
27    // Error handling examples
28    example_basic_error_handling(&client).await;
29    example_retry_logic(&client).await;
30    example_rate_limit_handling(&client).await;
31    example_auth_error_detection(&client).await;
32    example_error_categorization().await;
33
34    println!("\n✓ All error handling examples completed!");
35
36    Ok(())
37}
More examples
Hide additional examples
examples/rest_crud.rs (line 44)
34async fn main() -> Result<(), Box<dyn std::error::Error>> {
35    // Initialize tracing for better logging and debugging
36    tracing_subscriber::fmt::init();
37
38    println!("=== Salesforce REST API CRUD Examples ===\n");
39
40    // Get credentials (try SFDX first, then environment)
41    let creds = get_credentials().await?;
42
43    // Create REST client
44    let client = SalesforceRestClient::new(creds.instance_url(), creds.access_token())?;
45
46    // Run CRUD examples - showing BOTH patterns
47    println!("--- Type-Safe Struct Pattern ---\n");
48    let account_id = example_create_typed(&client).await?;
49    example_read_typed(&client, &account_id).await?;
50    example_update_typed(&client, &account_id).await?;
51
52    println!("\n--- Dynamic JSON Pattern ---\n");
53    let dynamic_id = example_create_dynamic(&client).await?;
54    example_read_dynamic(&client, &dynamic_id).await?;
55
56    // Clean up
57    example_delete(&client, &account_id).await?;
58    example_delete(&client, &dynamic_id).await?;
59
60    // Advanced operations
61    example_upsert(&client).await?;
62    example_create_multiple(&client).await?;
63
64    println!("\n✓ All CRUD examples completed successfully!");
65
66    Ok(())
67}
examples/queries.rs (line 72)
65async fn main() -> Result<(), Box<dyn std::error::Error>> {
66    // Initialize tracing for better observability
67    tracing_subscriber::fmt::init();
68
69    println!("=== Salesforce SOQL Query Examples ===\n");
70
71    let creds = get_credentials().await?;
72    let client = SalesforceRestClient::new(creds.instance_url(), creds.access_token())?;
73
74    // RECOMMENDED: Use QueryBuilder (safe by default)
75    println!("--- QueryBuilder Pattern (RECOMMENDED) ---\n");
76    example_query_builder_typed(&client).await?;
77    example_query_builder_dynamic(&client).await?;
78    example_query_builder_advanced(&client).await?;
79
80    // Alternative: Raw queries (less safe, but flexible)
81    println!("\n--- Raw Query Patterns ---\n");
82    example_basic_query_typed(&client).await?;
83    example_basic_query_dynamic(&client).await?;
84
85    // Manual escaping (NOT recommended, but shown for completeness)
86    println!("\n--- Manual Escaping (NOT RECOMMENDED) ---\n");
87    example_manual_escaping(&client).await?;
88
89    // Advanced queries
90    println!("\n--- Advanced Queries ---\n");
91    example_relationship_query(&client).await?;
92    example_aggregate_query(&client).await?;
93
94    println!("\n✓ All query examples completed successfully!");
95
96    Ok(())
97}
Source

pub fn with_config( instance_url: impl Into<String>, access_token: impl Into<String>, config: ClientConfig, ) -> Result<SalesforceRestClient, Error>

Create a new REST client with custom HTTP configuration.

Source

pub fn from_client(client: SalesforceClient) -> SalesforceRestClient

Create a REST client from an existing SalesforceClient.

Source

pub fn inner(&self) -> &SalesforceClient

Get the underlying SalesforceClient.

Source

pub fn instance_url(&self) -> &str

Get the instance URL.

Source

pub fn api_version(&self) -> &str

Get the API version.

Source

pub fn with_api_version( self, version: impl Into<String>, ) -> SalesforceRestClient

Set the API version.

Source

pub async fn describe_global(&self) -> Result<DescribeGlobalResult, Error>

Get a list of all SObjects available in the org.

This is equivalent to calling /services/data/vXX.0/sobjects/.

Source

pub async fn describe_sobject( &self, sobject: &str, ) -> Result<DescribeSObjectResult, Error>

Get detailed metadata for a specific SObject.

This is equivalent to calling /services/data/vXX.0/sobjects/{sobject}/describe.

Source

pub async fn create<T>( &self, sobject: &str, record: &T, ) -> Result<String, Error>
where T: Serialize,

Create a new record.

Returns the ID of the created record.

Examples found in repository?
examples/error_handling.rs (line 223)
215async fn create_account_with_context(
216    client: &SalesforceRestClient,
217    name: &str,
218) -> Result<String, String> {
219    let account = serde_json::json!({
220        "Name": name
221    });
222
223    client.create("Account", &account).await.map_err(|e| {
224        // Add context to the error
225        format!("Failed to create account '{}': {}", name, e)
226    })
227}
More examples
Hide additional examples
examples/rest_crud.rs (line 84)
70async fn example_create_typed(
71    client: &SalesforceRestClient,
72) -> Result<String, Box<dyn std::error::Error>> {
73    println!("Example 1a: Create with Type-Safe Struct");
74    println!("------------------------------------------");
75
76    let account = Account {
77        id: None,
78        name: "Acme Corporation".to_string(),
79        industry: Some("Technology".to_string()),
80        phone: Some("+1-555-0100".to_string()),
81        website: Some("https://acme.example.com".to_string()),
82    };
83
84    let id = client.create("Account", &account).await?;
85    println!("✓ Created account with ID: {}", id);
86    println!("  Benefits: Type safety, IDE support, compile-time checking");
87    println!();
88
89    Ok(id)
90}
91
92/// Example 1b: Create with dynamic JSON
93async fn example_create_dynamic(
94    client: &SalesforceRestClient,
95) -> Result<String, Box<dyn std::error::Error>> {
96    println!("Example 1b: Create with Dynamic JSON");
97    println!("--------------------------------------");
98
99    let account = serde_json::json!({
100        "Name": "Dynamic Industries",
101        "Industry": "Technology",
102        "Phone": "+1-555-0200"
103    });
104
105    let id = client.create("Account", &account).await?;
106    println!("✓ Created account with ID: {}", id);
107    println!("  Benefits: Flexible, good for exploration/prototyping");
108    println!();
109
110    Ok(id)
111}
Source

pub async fn get<T>( &self, sobject: &str, id: &str, fields: Option<&[&str]>, ) -> Result<T, Error>

Get a record by ID.

Optionally specify which fields to retrieve.

Examples found in repository?
examples/rest_crud.rs (lines 122-126)
114async fn example_read_typed(
115    client: &SalesforceRestClient,
116    account_id: &str,
117) -> Result<(), Box<dyn std::error::Error>> {
118    println!("Example 2a: Read with Type-Safe Struct");
119    println!("----------------------------------------");
120
121    let account: Account = client
122        .get(
123            "Account",
124            account_id,
125            Some(&["Id", "Name", "Industry", "Phone", "Website"]),
126        )
127        .await?;
128
129    println!("✓ Retrieved account:");
130    println!("  ID: {:?}", account.id);
131    println!("  Name: {}", account.name);
132    println!("  Industry: {:?}", account.industry);
133    println!("  Phone: {:?}", account.phone);
134    println!("  Website: {:?}", account.website);
135    println!();
136
137    Ok(())
138}
139
140/// Example 2b: Read with dynamic JSON
141async fn example_read_dynamic(
142    client: &SalesforceRestClient,
143    account_id: &str,
144) -> Result<(), Box<dyn std::error::Error>> {
145    println!("Example 2b: Read with Dynamic JSON");
146    println!("------------------------------------");
147
148    let account: serde_json::Value = client
149        .get(
150            "Account",
151            account_id,
152            Some(&["Id", "Name", "Industry", "Phone"]),
153        )
154        .await?;
155
156    println!("✓ Retrieved account:");
157    println!("  ID: {}", account["Id"]);
158    println!("  Name: {}", account["Name"]);
159    println!("  Industry: {}", account["Industry"]);
160    println!("  Phone: {}", account["Phone"]);
161    println!();
162
163    Ok(())
164}
More examples
Hide additional examples
examples/error_handling.rs (line 46)
40async fn example_basic_error_handling(client: &SalesforceRestClient) {
41    println!("Example 1: Basic Error Handling");
42    println!("--------------------------------");
43
44    // Try to get a record that might not exist
45    let result: Result<serde_json::Value, _> = client
46        .get("Account", "001000000000000", None) // Invalid ID
47        .await;
48
49    match result {
50        Ok(account) => {
51            println!("✓ Found account: {:?}", account);
52        }
53        Err(e) => {
54            println!("✗ Error occurred: {}", e);
55            println!("  Error type: {:?}", e.kind);
56
57            // Check if it's a "not found" error
58            if matches!(e.kind, busbar_sf_rest::ErrorKind::Salesforce { .. }) {
59                println!("  This is a Salesforce API error");
60            }
61        }
62    }
63
64    println!();
65}
66
67/// Example 2: Retry logic for transient errors
68async fn example_retry_logic(client: &SalesforceRestClient) {
69    println!("Example 2: Retry Logic");
70    println!("----------------------");
71
72    let max_retries = 3;
73    let mut attempt = 0;
74
75    loop {
76        attempt += 1;
77        println!("Attempt {}/{}", attempt, max_retries);
78
79        // Try to query accounts
80        let result: Result<Vec<serde_json::Value>, _> = client
81            .query_all("SELECT Id, Name FROM Account LIMIT 10")
82            .await;
83
84        match result {
85            Ok(accounts) => {
86                println!("✓ Successfully retrieved {} accounts", accounts.len());
87                break;
88            }
89            Err(e) => {
90                println!("✗ Error: {}", e);
91
92                // For demonstration, retry on any error (in production, check error type)
93                if attempt < max_retries {
94                    let backoff = Duration::from_secs(2u64.pow(attempt - 1));
95                    println!("  Retrying after {:?}...", backoff);
96                    sleep(backoff).await;
97                } else {
98                    println!("  Max retries reached");
99                    break;
100                }
101            }
102        }
103    }
104
105    println!();
106}
107
108/// Example 3: Rate limit handling
109async fn example_rate_limit_handling(client: &SalesforceRestClient) {
110    println!("Example 3: Rate Limit Handling");
111    println!("-------------------------------");
112
113    // Simulate checking org limits
114    match client.limits().await {
115        Ok(limits) => {
116            println!("✓ Retrieved org limits");
117
118            // Check API usage
119            if let Some(daily_api) = limits.get("DailyApiRequests") {
120                if let (Some(max), Some(remaining)) = (
121                    daily_api.get("Max").and_then(|v| v.as_i64()),
122                    daily_api.get("Remaining").and_then(|v| v.as_i64()),
123                ) {
124                    let usage_percent = ((max - remaining) as f64 / max as f64) * 100.0;
125                    println!(
126                        "  Daily API Usage: {:.1}% ({}/{})",
127                        usage_percent,
128                        max - remaining,
129                        max
130                    );
131
132                    if usage_percent > 80.0 {
133                        println!("  ⚠ Warning: API usage is above 80%!");
134                    }
135                }
136            }
137        }
138        Err(e) => {
139            println!("✗ Error retrieving limits: {}", e);
140            println!("  Note: Check if rate limited or other error occurred");
141        }
142    }
143
144    println!();
145}
146
147/// Example 4: Authentication error detection
148async fn example_auth_error_detection(client: &SalesforceRestClient) {
149    println!("Example 4: Authentication Error Detection");
150    println!("------------------------------------------");
151
152    // Try an operation (this will likely succeed with valid credentials)
153    let result: Result<Vec<serde_json::Value>, _> =
154        client.query_all("SELECT Id FROM Account LIMIT 1").await;
155
156    match result {
157        Ok(_) => {
158            println!("✓ Authentication is valid");
159        }
160        Err(e) => {
161            println!("✗ Error: {}", e);
162            // Check error kind
163            if matches!(e.kind, busbar_sf_rest::ErrorKind::Auth(_)) {
164                println!("  This is an authentication error!");
165                println!("  Action: Refresh access token or re-authenticate");
166            }
167        }
168    }
169
170    println!();
171}
172
173/// Example 5: Error categorization
174async fn example_error_categorization() {
175    println!("Example 5: Error Categorization");
176    println!("--------------------------------");
177
178    // Create different error types for demonstration
179    let errors = vec![
180        (
181            "Rate Limited",
182            ErrorKind::RateLimited {
183                retry_after: Some(Duration::from_secs(30)),
184            },
185        ),
186        ("Timeout", ErrorKind::Timeout),
187        (
188            "Authentication",
189            ErrorKind::Authentication("Invalid token".to_string()),
190        ),
191        ("Not Found", ErrorKind::NotFound("Account".to_string())),
192        (
193            "Connection",
194            ErrorKind::Connection("Network error".to_string()),
195        ),
196    ];
197
198    for (name, error_kind) in errors {
199        println!("\n{} Error:", name);
200        println!("  Retryable: {}", error_kind.is_retryable());
201
202        if let ErrorKind::RateLimited {
203            retry_after: Some(duration),
204        } = error_kind
205        {
206            println!("  Retry after: {:?}", duration);
207        }
208    }
209
210    println!();
211}
212
213/// Example: Custom error handling with context
214#[allow(dead_code)]
215async fn create_account_with_context(
216    client: &SalesforceRestClient,
217    name: &str,
218) -> Result<String, String> {
219    let account = serde_json::json!({
220        "Name": name
221    });
222
223    client.create("Account", &account).await.map_err(|e| {
224        // Add context to the error
225        format!("Failed to create account '{}': {}", name, e)
226    })
227}
228
229/// Example: Error recovery strategies
230#[allow(dead_code)]
231async fn query_with_fallback(
232    client: &SalesforceRestClient,
233) -> Result<Vec<serde_json::Value>, Box<dyn std::error::Error>> {
234    // Try primary query
235    let result = client
236        .query_all("SELECT Id, Name, CustomField__c FROM Account")
237        .await;
238
239    match result {
240        Ok(accounts) => Ok(accounts),
241        Err(e) => {
242            // If field doesn't exist, try without it
243            if let busbar_sf_rest::ErrorKind::Salesforce { error_code, .. } = &e.kind {
244                if error_code == "INVALID_FIELD" {
245                    println!("  CustomField__c doesn't exist, trying without it...");
246                    return client
247                        .query_all("SELECT Id, Name FROM Account")
248                        .await
249                        .map_err(Into::into);
250                }
251            }
252            Err(e.into())
253        }
254    }
255}
256
257/// Example: Bulk error handling
258#[allow(dead_code)]
259async fn process_with_partial_failures(
260    client: &SalesforceRestClient,
261    account_ids: Vec<String>,
262) -> (Vec<serde_json::Value>, Vec<String>) {
263    let mut successful = Vec::new();
264    let mut failed = Vec::new();
265
266    for id in account_ids {
267        match client.get::<serde_json::Value>("Account", &id, None).await {
268            Ok(account) => successful.push(account),
269            Err(e) => {
270                eprintln!("Failed to get account {}: {}", id, e);
271                failed.push(id);
272            }
273        }
274    }
275
276    (successful, failed)
277}
Source

pub async fn update<T>( &self, sobject: &str, id: &str, record: &T, ) -> Result<(), Error>
where T: Serialize,

Update a record.

Examples found in repository?
examples/rest_crud.rs (line 180)
167async fn example_update_typed(
168    client: &SalesforceRestClient,
169    account_id: &str,
170) -> Result<(), Box<dyn std::error::Error>> {
171    println!("Example 3: Update Record");
172    println!("------------------------");
173
174    // For updates, dynamic JSON is often more convenient
175    let updates = serde_json::json!({
176        "Name": "Acme Corporation (Updated)",
177        "Phone": "+1-555-0101"
178    });
179
180    client.update("Account", account_id, &updates).await?;
181    println!("✓ Updated account {}", account_id);
182    println!();
183
184    Ok(())
185}
Source

pub async fn delete(&self, sobject: &str, id: &str) -> Result<(), Error>

Delete a record.

Examples found in repository?
examples/rest_crud.rs (line 230)
223async fn example_delete(
224    client: &SalesforceRestClient,
225    account_id: &str,
226) -> Result<(), Box<dyn std::error::Error>> {
227    println!("Example 5: Delete Record");
228    println!("------------------------");
229
230    client.delete("Account", account_id).await?;
231    println!("✓ Deleted account {}", account_id);
232    println!();
233
234    Ok(())
235}
Source

pub async fn upsert<T>( &self, sobject: &str, external_id_field: &str, external_id_value: &str, record: &T, ) -> Result<UpsertResult, Error>
where T: Serialize,

Upsert a record using an external ID field.

Creates the record if it doesn’t exist, updates it if it does.

Examples found in repository?
examples/rest_crud.rs (line 202)
188async fn example_upsert(client: &SalesforceRestClient) -> Result<(), Box<dyn std::error::Error>> {
189    println!("Example 4: Upsert Record");
190    println!("------------------------");
191
192    let account = serde_json::json!({
193        "Name": "Global Industries",
194        "Industry": "Manufacturing"
195    });
196
197    // Use a custom external ID field (must exist in your org)
198    // This example uses AccountNumber as an example
199    let external_id = "EXT-12345";
200
201    match client
202        .upsert("Account", "AccountNumber", external_id, &account)
203        .await
204    {
205        Ok(result) => {
206            if result.created {
207                println!("✓ Created new account: {}", result.id);
208            } else {
209                println!("✓ Updated existing account: {}", result.id);
210            }
211        }
212        Err(e) => {
213            println!("Note: Upsert requires an external ID field in your org");
214            println!("  Error: {}", e);
215        }
216    }
217    println!();
218
219    Ok(())
220}
Source

pub async fn query<T>(&self, soql: &str) -> Result<QueryResult<T>, Error>

Execute a SOQL query.

Returns the first page of results. Use query_all for automatic pagination.

§Security

IMPORTANT: If you are including user-provided values in the WHERE clause, you MUST escape them to prevent SOQL injection attacks. Use the security utilities:

use busbar_sf_client::security::soql;

// WRONG - vulnerable to injection:
let query = format!("SELECT Id FROM Account WHERE Name = '{}'", user_input);

// CORRECT - properly escaped:
let safe_value = soql::escape_string(user_input);
let query = format!("SELECT Id FROM Account WHERE Name = '{}'", safe_value);
Examples found in repository?
examples/queries.rs (line 190)
183async fn example_basic_query_typed(
184    client: &SalesforceRestClient,
185) -> Result<(), Box<dyn std::error::Error>> {
186    println!("Example 1a: Type-Safe Query");
187    println!("----------------------------");
188
189    let query = "SELECT Id, Name, Industry FROM Account LIMIT 5";
190    let result: busbar_sf_client::QueryResult<Account> = client.query(query).await?;
191
192    println!(
193        "✓ Found {} accounts (total: {})",
194        result.records.len(),
195        result.total_size
196    );
197    for account in &result.records {
198        println!("  - {} (Industry: {:?})", account.name, account.industry);
199    }
200    println!("  Benefits: Type safety, field access without unwrapping");
201    println!();
202
203    Ok(())
204}
205
206/// Example 1b: Dynamic JSON query with proper serde_json patterns
207async fn example_basic_query_dynamic(
208    client: &SalesforceRestClient,
209) -> Result<(), Box<dyn std::error::Error>> {
210    println!("Example 1b: Dynamic JSON Query");
211    println!("-------------------------------");
212
213    let query = "SELECT Id, Name, Industry FROM Account LIMIT 5";
214
215    // Use HashMap for more ergonomic access than raw Value
216    let result: busbar_sf_client::QueryResult<HashMap<String, serde_json::Value>> =
217        client.query(query).await?;
218
219    println!("✓ Found {} accounts", result.records.len());
220    for account in &result.records {
221        // Much more ergonomic than account["Name"].as_str().unwrap_or()
222        let name = account
223            .get("Name")
224            .and_then(|v| v.as_str())
225            .unwrap_or("Unknown");
226        let industry = account
227            .get("Industry")
228            .and_then(|v| v.as_str())
229            .unwrap_or("None");
230        println!("  - {} (Industry: {})", name, industry);
231    }
232    println!("  Benefits: HashMap provides .get() method, no indexing panics");
233    println!();
234
235    Ok(())
236}
237
238/// Example 2: Automatic pagination with type safety
239#[allow(dead_code)]
240async fn example_query_pagination_typed(
241    client: &SalesforceRestClient,
242) -> Result<(), Box<dyn std::error::Error>> {
243    println!("Example 2: Pagination with Type Safety");
244    println!("---------------------------------------");
245
246    // query_all automatically handles pagination
247    let query = "SELECT Id, Name FROM Account LIMIT 100";
248    let accounts: Vec<Account> = client.query_all(query).await?;
249
250    println!(
251        "✓ Retrieved {} accounts (automatic pagination)",
252        accounts.len()
253    );
254    println!();
255
256    Ok(())
257}
258
259/// Example 3: Manual escaping - NOT RECOMMENDED but shown for completeness
260///
261/// WARNING: This approach is error-prone!
262/// - Easy to forget to escape
263/// - Easy to use wrong escape function (escape_string vs escape_like)
264/// - Not safe by default
265///
266/// Prefer the SafeQueryBuilder pattern shown above!
267async fn example_manual_escaping(
268    client: &SalesforceRestClient,
269) -> Result<(), Box<dyn std::error::Error>> {
270    println!("Example 3: Manual Escaping (NOT RECOMMENDED)");
271    println!("---------------------------------------------");
272    println!("⚠️  WARNING: Easy to forget! Use QueryBuilder instead.");
273    println!();
274
275    let user_input = "O'Brien's Company";
276    let malicious_input = "'; DELETE FROM Account--";
277
278    // Manual escaping - requires developer to remember!
279    let safe_name = soql::escape_string(user_input);
280    let query = format!("SELECT Id, Name FROM Account WHERE Name = '{}'", safe_name);
281
282    let accounts: Vec<Account> = client.query_all(&query).await?;
283    println!("  Found {} accounts", accounts.len());
284
285    // Show what happens if you forget to escape (DON'T DO THIS!)
286    let safe_malicious = soql::escape_string(malicious_input);
287    println!("\n  Injection attempt:");
288    println!("  Raw input:      {}", malicious_input);
289    println!("  After escaping: {}", safe_malicious);
290    println!("\n  ❌ Problem: Relies on developer remembering to escape");
291    println!("  ✅ Solution: Use QueryBuilder that escapes automatically");
292    println!();
293
294    Ok(())
295}
296
297/// Example 4: Field validation
298#[allow(dead_code)]
299async fn example_field_validation(
300    client: &SalesforceRestClient,
301) -> Result<(), Box<dyn std::error::Error>> {
302    println!("Example 4: Field Validation");
303    println!("---------------------------");
304
305    // User-provided field names (could be malicious)
306    let user_fields = vec![
307        "Id",
308        "Name",
309        "Industry",
310        "Bad'; DROP TABLE--", // Injection attempt
311        "CustomField__c",
312    ];
313
314    // Filter to only safe field names
315    let safe_fields: Vec<&str> = soql::filter_safe_fields(user_fields.iter().copied()).collect();
316
317    println!("  Original fields: {:?}", user_fields);
318    println!("  Safe fields:     {:?}", safe_fields);
319
320    // Build SELECT clause with safe fields
321    if let Some(select_clause) = soql::build_safe_select(&safe_fields) {
322        let query = format!("SELECT {} FROM Account LIMIT 5", select_clause);
323
324        // Use HashMap for dynamic field access
325        let result: busbar_sf_client::QueryResult<HashMap<String, serde_json::Value>> =
326            client.query(&query).await?;
327        println!("✓ Retrieved {} records", result.records.len());
328    } else {
329        println!("✗ No safe fields to query");
330    }
331
332    println!();
333
334    Ok(())
335}
Source

pub async fn query_all<T>(&self, soql: &str) -> Result<Vec<T>, Error>

Execute a SOQL query and return all results (automatic pagination).

§Security

IMPORTANT: Escape user-provided values with busbar_sf_client::security::soql::escape_string() to prevent SOQL injection attacks. See query() for examples.

Examples found in repository?
examples/queries.rs (line 248)
240async fn example_query_pagination_typed(
241    client: &SalesforceRestClient,
242) -> Result<(), Box<dyn std::error::Error>> {
243    println!("Example 2: Pagination with Type Safety");
244    println!("---------------------------------------");
245
246    // query_all automatically handles pagination
247    let query = "SELECT Id, Name FROM Account LIMIT 100";
248    let accounts: Vec<Account> = client.query_all(query).await?;
249
250    println!(
251        "✓ Retrieved {} accounts (automatic pagination)",
252        accounts.len()
253    );
254    println!();
255
256    Ok(())
257}
258
259/// Example 3: Manual escaping - NOT RECOMMENDED but shown for completeness
260///
261/// WARNING: This approach is error-prone!
262/// - Easy to forget to escape
263/// - Easy to use wrong escape function (escape_string vs escape_like)
264/// - Not safe by default
265///
266/// Prefer the SafeQueryBuilder pattern shown above!
267async fn example_manual_escaping(
268    client: &SalesforceRestClient,
269) -> Result<(), Box<dyn std::error::Error>> {
270    println!("Example 3: Manual Escaping (NOT RECOMMENDED)");
271    println!("---------------------------------------------");
272    println!("⚠️  WARNING: Easy to forget! Use QueryBuilder instead.");
273    println!();
274
275    let user_input = "O'Brien's Company";
276    let malicious_input = "'; DELETE FROM Account--";
277
278    // Manual escaping - requires developer to remember!
279    let safe_name = soql::escape_string(user_input);
280    let query = format!("SELECT Id, Name FROM Account WHERE Name = '{}'", safe_name);
281
282    let accounts: Vec<Account> = client.query_all(&query).await?;
283    println!("  Found {} accounts", accounts.len());
284
285    // Show what happens if you forget to escape (DON'T DO THIS!)
286    let safe_malicious = soql::escape_string(malicious_input);
287    println!("\n  Injection attempt:");
288    println!("  Raw input:      {}", malicious_input);
289    println!("  After escaping: {}", safe_malicious);
290    println!("\n  ❌ Problem: Relies on developer remembering to escape");
291    println!("  ✅ Solution: Use QueryBuilder that escapes automatically");
292    println!();
293
294    Ok(())
295}
296
297/// Example 4: Field validation
298#[allow(dead_code)]
299async fn example_field_validation(
300    client: &SalesforceRestClient,
301) -> Result<(), Box<dyn std::error::Error>> {
302    println!("Example 4: Field Validation");
303    println!("---------------------------");
304
305    // User-provided field names (could be malicious)
306    let user_fields = vec![
307        "Id",
308        "Name",
309        "Industry",
310        "Bad'; DROP TABLE--", // Injection attempt
311        "CustomField__c",
312    ];
313
314    // Filter to only safe field names
315    let safe_fields: Vec<&str> = soql::filter_safe_fields(user_fields.iter().copied()).collect();
316
317    println!("  Original fields: {:?}", user_fields);
318    println!("  Safe fields:     {:?}", safe_fields);
319
320    // Build SELECT clause with safe fields
321    if let Some(select_clause) = soql::build_safe_select(&safe_fields) {
322        let query = format!("SELECT {} FROM Account LIMIT 5", select_clause);
323
324        // Use HashMap for dynamic field access
325        let result: busbar_sf_client::QueryResult<HashMap<String, serde_json::Value>> =
326            client.query(&query).await?;
327        println!("✓ Retrieved {} records", result.records.len());
328    } else {
329        println!("✗ No safe fields to query");
330    }
331
332    println!();
333
334    Ok(())
335}
336
337/// Example 7: Relationship query
338async fn example_relationship_query(
339    client: &SalesforceRestClient,
340) -> Result<(), Box<dyn std::error::Error>> {
341    println!("Example 7: Relationship Query");
342    println!("-----------------------------");
343
344    let query =
345        "SELECT Id, Name, Email, Account.Name FROM Contact WHERE Account.Name != null LIMIT 5";
346
347    let contacts: Vec<serde_json::Value> = client.query_all(query).await?;
348
349    println!("✓ Found {} contacts with accounts", contacts.len());
350    for contact in &contacts {
351        let name = contact
352            .get("Name")
353            .and_then(|v| v.as_str())
354            .unwrap_or("Unknown");
355        let id = contact
356            .get("Id")
357            .and_then(|v| v.as_str())
358            .unwrap_or("Unknown");
359        if let Some(account) = contact.get("Account") {
360            if let Some(account_name) = account.get("Name").and_then(|v| v.as_str()) {
361                println!("  - {} ({}) @ {}", name, id, account_name);
362            }
363        }
364    }
365    println!();
366
367    Ok(())
368}
369
370/// Example 8: Aggregate query
371async fn example_aggregate_query(
372    client: &SalesforceRestClient,
373) -> Result<(), Box<dyn std::error::Error>> {
374    println!("Example 8: Aggregate Query");
375    println!("--------------------------");
376
377    let query = "SELECT Industry, COUNT(Id) total FROM Account WHERE Industry != null GROUP BY Industry ORDER BY COUNT(Id) DESC LIMIT 5";
378
379    let results: Vec<serde_json::Value> = client.query_all(query).await?;
380
381    println!("✓ Top {} industries:", results.len());
382    for result in &results {
383        let industry = result
384            .get("Industry")
385            .and_then(|v| v.as_str())
386            .unwrap_or("Unknown");
387        let total = result.get("total").and_then(|v| v.as_i64()).unwrap_or(0);
388        println!("  - {}: {} accounts", industry, total);
389    }
390    println!();
391
392    Ok(())
393}
More examples
Hide additional examples
examples/error_handling.rs (line 81)
68async fn example_retry_logic(client: &SalesforceRestClient) {
69    println!("Example 2: Retry Logic");
70    println!("----------------------");
71
72    let max_retries = 3;
73    let mut attempt = 0;
74
75    loop {
76        attempt += 1;
77        println!("Attempt {}/{}", attempt, max_retries);
78
79        // Try to query accounts
80        let result: Result<Vec<serde_json::Value>, _> = client
81            .query_all("SELECT Id, Name FROM Account LIMIT 10")
82            .await;
83
84        match result {
85            Ok(accounts) => {
86                println!("✓ Successfully retrieved {} accounts", accounts.len());
87                break;
88            }
89            Err(e) => {
90                println!("✗ Error: {}", e);
91
92                // For demonstration, retry on any error (in production, check error type)
93                if attempt < max_retries {
94                    let backoff = Duration::from_secs(2u64.pow(attempt - 1));
95                    println!("  Retrying after {:?}...", backoff);
96                    sleep(backoff).await;
97                } else {
98                    println!("  Max retries reached");
99                    break;
100                }
101            }
102        }
103    }
104
105    println!();
106}
107
108/// Example 3: Rate limit handling
109async fn example_rate_limit_handling(client: &SalesforceRestClient) {
110    println!("Example 3: Rate Limit Handling");
111    println!("-------------------------------");
112
113    // Simulate checking org limits
114    match client.limits().await {
115        Ok(limits) => {
116            println!("✓ Retrieved org limits");
117
118            // Check API usage
119            if let Some(daily_api) = limits.get("DailyApiRequests") {
120                if let (Some(max), Some(remaining)) = (
121                    daily_api.get("Max").and_then(|v| v.as_i64()),
122                    daily_api.get("Remaining").and_then(|v| v.as_i64()),
123                ) {
124                    let usage_percent = ((max - remaining) as f64 / max as f64) * 100.0;
125                    println!(
126                        "  Daily API Usage: {:.1}% ({}/{})",
127                        usage_percent,
128                        max - remaining,
129                        max
130                    );
131
132                    if usage_percent > 80.0 {
133                        println!("  ⚠ Warning: API usage is above 80%!");
134                    }
135                }
136            }
137        }
138        Err(e) => {
139            println!("✗ Error retrieving limits: {}", e);
140            println!("  Note: Check if rate limited or other error occurred");
141        }
142    }
143
144    println!();
145}
146
147/// Example 4: Authentication error detection
148async fn example_auth_error_detection(client: &SalesforceRestClient) {
149    println!("Example 4: Authentication Error Detection");
150    println!("------------------------------------------");
151
152    // Try an operation (this will likely succeed with valid credentials)
153    let result: Result<Vec<serde_json::Value>, _> =
154        client.query_all("SELECT Id FROM Account LIMIT 1").await;
155
156    match result {
157        Ok(_) => {
158            println!("✓ Authentication is valid");
159        }
160        Err(e) => {
161            println!("✗ Error: {}", e);
162            // Check error kind
163            if matches!(e.kind, busbar_sf_rest::ErrorKind::Auth(_)) {
164                println!("  This is an authentication error!");
165                println!("  Action: Refresh access token or re-authenticate");
166            }
167        }
168    }
169
170    println!();
171}
172
173/// Example 5: Error categorization
174async fn example_error_categorization() {
175    println!("Example 5: Error Categorization");
176    println!("--------------------------------");
177
178    // Create different error types for demonstration
179    let errors = vec![
180        (
181            "Rate Limited",
182            ErrorKind::RateLimited {
183                retry_after: Some(Duration::from_secs(30)),
184            },
185        ),
186        ("Timeout", ErrorKind::Timeout),
187        (
188            "Authentication",
189            ErrorKind::Authentication("Invalid token".to_string()),
190        ),
191        ("Not Found", ErrorKind::NotFound("Account".to_string())),
192        (
193            "Connection",
194            ErrorKind::Connection("Network error".to_string()),
195        ),
196    ];
197
198    for (name, error_kind) in errors {
199        println!("\n{} Error:", name);
200        println!("  Retryable: {}", error_kind.is_retryable());
201
202        if let ErrorKind::RateLimited {
203            retry_after: Some(duration),
204        } = error_kind
205        {
206            println!("  Retry after: {:?}", duration);
207        }
208    }
209
210    println!();
211}
212
213/// Example: Custom error handling with context
214#[allow(dead_code)]
215async fn create_account_with_context(
216    client: &SalesforceRestClient,
217    name: &str,
218) -> Result<String, String> {
219    let account = serde_json::json!({
220        "Name": name
221    });
222
223    client.create("Account", &account).await.map_err(|e| {
224        // Add context to the error
225        format!("Failed to create account '{}': {}", name, e)
226    })
227}
228
229/// Example: Error recovery strategies
230#[allow(dead_code)]
231async fn query_with_fallback(
232    client: &SalesforceRestClient,
233) -> Result<Vec<serde_json::Value>, Box<dyn std::error::Error>> {
234    // Try primary query
235    let result = client
236        .query_all("SELECT Id, Name, CustomField__c FROM Account")
237        .await;
238
239    match result {
240        Ok(accounts) => Ok(accounts),
241        Err(e) => {
242            // If field doesn't exist, try without it
243            if let busbar_sf_rest::ErrorKind::Salesforce { error_code, .. } = &e.kind {
244                if error_code == "INVALID_FIELD" {
245                    println!("  CustomField__c doesn't exist, trying without it...");
246                    return client
247                        .query_all("SELECT Id, Name FROM Account")
248                        .await
249                        .map_err(Into::into);
250                }
251            }
252            Err(e.into())
253        }
254    }
255}
Source

pub async fn query_all_including_deleted<T>( &self, soql: &str, ) -> Result<QueryResult<T>, Error>

Execute a SOQL query including deleted/archived records.

§Security

IMPORTANT: Escape user-provided values with busbar_sf_client::security::soql::escape_string() to prevent SOQL injection attacks. See query() for examples.

Source

pub async fn query_more<T>( &self, next_records_url: &str, ) -> Result<QueryResult<T>, Error>

Fetch the next page of query results.

Source

pub async fn search<T>(&self, sosl: &str) -> Result<SearchResult<T>, Error>

Execute a SOSL search.

§Security

IMPORTANT: If you are including user-provided values in the search term, you MUST escape them. Use busbar_sf_client::security::soql::escape_string() for string values in SOSL queries.

Source

pub async fn composite( &self, request: &CompositeRequest, ) -> Result<CompositeResponse, Error>

Execute a composite request with multiple subrequests.

The composite API allows up to 25 subrequests in a single API call.

Source

pub async fn create_multiple<T>( &self, sobject: &str, records: &[T], all_or_none: bool, ) -> Result<Vec<CollectionResult>, Error>
where T: Serialize,

Create multiple records in a single request (up to 200).

Examples found in repository?
examples/rest_crud.rs (line 270)
238async fn example_create_multiple(
239    client: &SalesforceRestClient,
240) -> Result<(), Box<dyn std::error::Error>> {
241    println!("Example 6: Create Multiple Records");
242    println!("-----------------------------------");
243
244    let accounts = vec![
245        Account {
246            id: None,
247            name: "Tech Startup Inc".to_string(),
248            industry: Some("Technology".to_string()),
249            phone: None,
250            website: Some("https://techstartup.example".to_string()),
251        },
252        Account {
253            id: None,
254            name: "Retail Giant LLC".to_string(),
255            industry: Some("Retail".to_string()),
256            phone: None,
257            website: Some("https://retailgiant.example".to_string()),
258        },
259        Account {
260            id: None,
261            name: "Finance Corp".to_string(),
262            industry: Some("Finance".to_string()),
263            phone: None,
264            website: None,
265        },
266    ];
267
268    // Create up to 200 records at once
269    // all_or_none: true means either all succeed or all fail
270    let results = client.create_multiple("Account", &accounts, true).await?;
271
272    println!("✓ Created {} accounts", results.len());
273    for (i, result) in results.iter().enumerate() {
274        if result.success {
275            let id = result.id.as_deref().unwrap_or("Unknown");
276            println!("  Account {}: {} - ID: {}", i + 1, accounts[i].name, id);
277        } else {
278            println!("  Account {}: Failed - {:?}", i + 1, result.errors);
279        }
280    }
281
282    // Clean up test data
283    let ids: Vec<&str> = results.iter().filter_map(|r| r.id.as_deref()).collect();
284    if !ids.is_empty() {
285        let _ = client.delete_multiple(&ids, false).await;
286        println!("✓ Cleaned up {} test accounts", ids.len());
287    }
288
289    println!();
290
291    Ok(())
292}
Source

pub async fn update_multiple<T>( &self, sobject: &str, records: &[(String, T)], all_or_none: bool, ) -> Result<Vec<CollectionResult>, Error>
where T: Serialize,

Update multiple records in a single request (up to 200).

Source

pub async fn delete_multiple( &self, ids: &[&str], all_or_none: bool, ) -> Result<Vec<CollectionResult>, Error>

Delete multiple records in a single request (up to 200).

Examples found in repository?
examples/rest_crud.rs (line 285)
238async fn example_create_multiple(
239    client: &SalesforceRestClient,
240) -> Result<(), Box<dyn std::error::Error>> {
241    println!("Example 6: Create Multiple Records");
242    println!("-----------------------------------");
243
244    let accounts = vec![
245        Account {
246            id: None,
247            name: "Tech Startup Inc".to_string(),
248            industry: Some("Technology".to_string()),
249            phone: None,
250            website: Some("https://techstartup.example".to_string()),
251        },
252        Account {
253            id: None,
254            name: "Retail Giant LLC".to_string(),
255            industry: Some("Retail".to_string()),
256            phone: None,
257            website: Some("https://retailgiant.example".to_string()),
258        },
259        Account {
260            id: None,
261            name: "Finance Corp".to_string(),
262            industry: Some("Finance".to_string()),
263            phone: None,
264            website: None,
265        },
266    ];
267
268    // Create up to 200 records at once
269    // all_or_none: true means either all succeed or all fail
270    let results = client.create_multiple("Account", &accounts, true).await?;
271
272    println!("✓ Created {} accounts", results.len());
273    for (i, result) in results.iter().enumerate() {
274        if result.success {
275            let id = result.id.as_deref().unwrap_or("Unknown");
276            println!("  Account {}: {} - ID: {}", i + 1, accounts[i].name, id);
277        } else {
278            println!("  Account {}: Failed - {:?}", i + 1, result.errors);
279        }
280    }
281
282    // Clean up test data
283    let ids: Vec<&str> = results.iter().filter_map(|r| r.id.as_deref()).collect();
284    if !ids.is_empty() {
285        let _ = client.delete_multiple(&ids, false).await;
286        println!("✓ Cleaned up {} test accounts", ids.len());
287    }
288
289    println!();
290
291    Ok(())
292}
Source

pub async fn get_multiple<T>( &self, sobject: &str, ids: &[&str], fields: &[&str], ) -> Result<Vec<T>, Error>

Get multiple records by ID in a single request (up to 2000).

Source

pub async fn limits(&self) -> Result<Value, Error>

Get API limits for the org.

Examples found in repository?
examples/error_handling.rs (line 114)
109async fn example_rate_limit_handling(client: &SalesforceRestClient) {
110    println!("Example 3: Rate Limit Handling");
111    println!("-------------------------------");
112
113    // Simulate checking org limits
114    match client.limits().await {
115        Ok(limits) => {
116            println!("✓ Retrieved org limits");
117
118            // Check API usage
119            if let Some(daily_api) = limits.get("DailyApiRequests") {
120                if let (Some(max), Some(remaining)) = (
121                    daily_api.get("Max").and_then(|v| v.as_i64()),
122                    daily_api.get("Remaining").and_then(|v| v.as_i64()),
123                ) {
124                    let usage_percent = ((max - remaining) as f64 / max as f64) * 100.0;
125                    println!(
126                        "  Daily API Usage: {:.1}% ({}/{})",
127                        usage_percent,
128                        max - remaining,
129                        max
130                    );
131
132                    if usage_percent > 80.0 {
133                        println!("  ⚠ Warning: API usage is above 80%!");
134                    }
135                }
136            }
137        }
138        Err(e) => {
139            println!("✗ Error retrieving limits: {}", e);
140            println!("  Note: Check if rate limited or other error occurred");
141        }
142    }
143
144    println!();
145}
Source

pub async fn versions(&self) -> Result<Vec<ApiVersion>, Error>

Get available API versions.

Trait Implementations§

Source§

impl Clone for SalesforceRestClient

Source§

fn clone(&self) -> SalesforceRestClient

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SalesforceRestClient

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more