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
impl SalesforceRestClient
Sourcepub fn new(
instance_url: impl Into<String>,
access_token: impl Into<String>,
) -> Result<SalesforceRestClient, Error>
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?
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
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}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}Sourcepub fn with_config(
instance_url: impl Into<String>,
access_token: impl Into<String>,
config: ClientConfig,
) -> Result<SalesforceRestClient, Error>
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.
Sourcepub fn from_client(client: SalesforceClient) -> SalesforceRestClient
pub fn from_client(client: SalesforceClient) -> SalesforceRestClient
Create a REST client from an existing SalesforceClient.
Sourcepub fn inner(&self) -> &SalesforceClient
pub fn inner(&self) -> &SalesforceClient
Get the underlying SalesforceClient.
Sourcepub fn instance_url(&self) -> &str
pub fn instance_url(&self) -> &str
Get the instance URL.
Sourcepub fn api_version(&self) -> &str
pub fn api_version(&self) -> &str
Get the API version.
Sourcepub fn with_api_version(
self,
version: impl Into<String>,
) -> SalesforceRestClient
pub fn with_api_version( self, version: impl Into<String>, ) -> SalesforceRestClient
Set the API version.
Sourcepub async fn describe_global(&self) -> Result<DescribeGlobalResult, Error>
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/.
Sourcepub async fn describe_sobject(
&self,
sobject: &str,
) -> Result<DescribeSObjectResult, Error>
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.
Sourcepub async fn create<T>(
&self,
sobject: &str,
record: &T,
) -> Result<String, Error>where
T: Serialize,
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?
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
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}Sourcepub async fn get<T>(
&self,
sobject: &str,
id: &str,
fields: Option<&[&str]>,
) -> Result<T, Error>where
T: DeserializeOwned,
pub async fn get<T>(
&self,
sobject: &str,
id: &str,
fields: Option<&[&str]>,
) -> Result<T, Error>where
T: DeserializeOwned,
Get a record by ID.
Optionally specify which fields to retrieve.
Examples found in repository?
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
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}Sourcepub async fn update<T>(
&self,
sobject: &str,
id: &str,
record: &T,
) -> Result<(), Error>where
T: Serialize,
pub async fn update<T>(
&self,
sobject: &str,
id: &str,
record: &T,
) -> Result<(), Error>where
T: Serialize,
Update a record.
Examples found in repository?
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}Sourcepub async fn delete(&self, sobject: &str, id: &str) -> Result<(), Error>
pub async fn delete(&self, sobject: &str, id: &str) -> Result<(), Error>
Delete a record.
Examples found in repository?
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}Sourcepub async fn upsert<T>(
&self,
sobject: &str,
external_id_field: &str,
external_id_value: &str,
record: &T,
) -> Result<UpsertResult, Error>where
T: Serialize,
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?
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}Sourcepub async fn query<T>(&self, soql: &str) -> Result<QueryResult<T>, Error>where
T: DeserializeOwned,
pub async fn query<T>(&self, soql: &str) -> Result<QueryResult<T>, Error>where
T: DeserializeOwned,
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?
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}Sourcepub async fn query_all<T>(&self, soql: &str) -> Result<Vec<T>, Error>where
T: DeserializeOwned + Clone,
pub async fn query_all<T>(&self, soql: &str) -> Result<Vec<T>, Error>where
T: DeserializeOwned + Clone,
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?
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
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}Sourcepub async fn query_all_including_deleted<T>(
&self,
soql: &str,
) -> Result<QueryResult<T>, Error>where
T: DeserializeOwned,
pub async fn query_all_including_deleted<T>(
&self,
soql: &str,
) -> Result<QueryResult<T>, Error>where
T: DeserializeOwned,
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.
Sourcepub async fn query_more<T>(
&self,
next_records_url: &str,
) -> Result<QueryResult<T>, Error>where
T: DeserializeOwned,
pub async fn query_more<T>(
&self,
next_records_url: &str,
) -> Result<QueryResult<T>, Error>where
T: DeserializeOwned,
Fetch the next page of query results.
Sourcepub async fn search<T>(&self, sosl: &str) -> Result<SearchResult<T>, Error>where
T: DeserializeOwned,
pub async fn search<T>(&self, sosl: &str) -> Result<SearchResult<T>, Error>where
T: DeserializeOwned,
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.
Sourcepub async fn composite(
&self,
request: &CompositeRequest,
) -> Result<CompositeResponse, Error>
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.
Sourcepub async fn create_multiple<T>(
&self,
sobject: &str,
records: &[T],
all_or_none: bool,
) -> Result<Vec<CollectionResult>, Error>where
T: Serialize,
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?
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}Sourcepub async fn update_multiple<T>(
&self,
sobject: &str,
records: &[(String, T)],
all_or_none: bool,
) -> Result<Vec<CollectionResult>, Error>where
T: Serialize,
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).
Sourcepub async fn delete_multiple(
&self,
ids: &[&str],
all_or_none: bool,
) -> Result<Vec<CollectionResult>, Error>
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?
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}Sourcepub async fn get_multiple<T>(
&self,
sobject: &str,
ids: &[&str],
fields: &[&str],
) -> Result<Vec<T>, Error>where
T: DeserializeOwned,
pub async fn get_multiple<T>(
&self,
sobject: &str,
ids: &[&str],
fields: &[&str],
) -> Result<Vec<T>, Error>where
T: DeserializeOwned,
Get multiple records by ID in a single request (up to 2000).
Sourcepub async fn limits(&self) -> Result<Value, Error>
pub async fn limits(&self) -> Result<Value, Error>
Get API limits for the org.
Examples found in repository?
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}Trait Implementations§
Source§impl Clone for SalesforceRestClient
impl Clone for SalesforceRestClient
Source§fn clone(&self) -> SalesforceRestClient
fn clone(&self) -> SalesforceRestClient
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more