1use busbar_sf_auth::{Credentials, SalesforceCredentials};
11use busbar_sf_client::security::soql;
12use busbar_sf_rest::{QueryBuilder, SalesforceRestClient};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16#[derive(Debug, Serialize, Deserialize, Clone)]
23struct Account {
24 #[serde(rename = "Id")]
25 id: String,
26 #[serde(rename = "Name")]
27 name: String,
28 #[serde(rename = "Industry", skip_serializing_if = "Option::is_none")]
29 industry: Option<String>,
30}
31
32#[derive(Debug, Deserialize)]
34#[allow(dead_code)]
35struct Contact {
36 #[serde(rename = "Id")]
37 id: String,
38 #[serde(rename = "Name")]
39 name: String,
40 #[serde(rename = "Email")]
41 email: Option<String>,
42 #[serde(rename = "Account")]
43 account: Option<AccountRef>,
44}
45
46#[derive(Debug, Deserialize)]
48#[allow(dead_code)]
49struct AccountRef {
50 #[serde(rename = "Name")]
51 name: String,
52}
53
54#[derive(Debug, Deserialize)]
56#[allow(dead_code)]
57struct IndustryCount {
58 #[serde(rename = "Industry")]
59 industry: String,
60 #[serde(rename = "total")]
61 total: i32,
62}
63
64#[tokio::main]
65async fn main() -> Result<(), Box<dyn std::error::Error>> {
66 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 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 println!("\n--- Raw Query Patterns ---\n");
82 example_basic_query_typed(&client).await?;
83 example_basic_query_dynamic(&client).await?;
84
85 println!("\n--- Manual Escaping (NOT RECOMMENDED) ---\n");
87 example_manual_escaping(&client).await?;
88
89 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}
98
99async fn example_query_builder_typed(
101 client: &SalesforceRestClient,
102) -> Result<(), Box<dyn std::error::Error>> {
103 println!("Example 1a: QueryBuilder with Type Safety");
104 println!("------------------------------------------");
105
106 let user_name = "O'Brien's Company"; let accounts: Vec<Account> = QueryBuilder::new("Account")?
111 .select(&["Id", "Name", "Industry"])
112 .where_eq("Name", user_name)? .limit(10)
114 .execute(client)
115 .await?;
116
117 println!("✓ Found {} accounts", accounts.len());
118 println!(" Benefits: Type-safe results, automatic escaping, fluent API");
119 println!();
120
121 Ok(())
122}
123
124async fn example_query_builder_dynamic(
126 client: &SalesforceRestClient,
127) -> Result<(), Box<dyn std::error::Error>> {
128 println!("Example 1b: QueryBuilder with Dynamic JSON");
129 println!("-------------------------------------------");
130
131 let user_pattern = "tech%value"; let accounts: Vec<HashMap<String, serde_json::Value>> = QueryBuilder::new("Account")?
135 .select(&["Id", "Name", "Industry"])
136 .where_like("Name", user_pattern)? .limit(5)
138 .execute(client)
139 .await?;
140
141 println!("✓ Found {} accounts", accounts.len());
142 for account in accounts.iter().take(3) {
143 let name = account
144 .get("Name")
145 .and_then(|v| v.as_str())
146 .unwrap_or("Unknown");
147 println!(" - {}", name);
148 }
149 println!(" Benefits: Flexible, ergonomic HashMap access, no struct needed");
150 println!();
151
152 Ok(())
153}
154
155async fn example_query_builder_advanced(
157 client: &SalesforceRestClient,
158) -> Result<(), Box<dyn std::error::Error>> {
159 println!("Example 1c: QueryBuilder Advanced Features");
160 println!("-------------------------------------------");
161
162 let industries = vec!["Technology", "Finance"];
163
164 let accounts: Vec<Account> = QueryBuilder::new("Account")?
165 .select(&["Id", "Name", "Industry"])
166 .where_in("Industry", &industries)? .order_by("Name", true)? .limit(20)
169 .execute(client)
170 .await?;
171
172 println!(
173 "✓ Found {} accounts in specified industries",
174 accounts.len()
175 );
176 println!(" Features: WHERE IN, ORDER BY, fluent chaining");
177 println!();
178
179 Ok(())
180}
181
182async 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
206async 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 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 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#[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 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
259async 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 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 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#[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 let user_fields = vec![
307 "Id",
308 "Name",
309 "Industry",
310 "Bad'; DROP TABLE--", "CustomField__c",
312 ];
313
314 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 if let Some(select_clause) = soql::build_safe_select(&safe_fields) {
322 let query = format!("SELECT {} FROM Account LIMIT 5", select_clause);
323
324 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
337async 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
370async 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}
394
395#[allow(dead_code)]
397fn build_secure_query(
398 sobject: &str,
399 fields: &[&str],
400 where_field: &str,
401 user_value: &str,
402) -> Option<String> {
403 if !soql::is_safe_sobject_name(sobject) {
405 return None;
406 }
407
408 let select_clause = soql::build_safe_select(fields)?;
410
411 if !soql::is_safe_field_name(where_field) {
413 return None;
414 }
415
416 let safe_value = soql::escape_string(user_value);
418
419 Some(format!(
420 "SELECT {} FROM {} WHERE {} = '{}'",
421 select_clause, sobject, where_field, safe_value
422 ))
423}
424
425async fn get_credentials() -> Result<SalesforceCredentials, Box<dyn std::error::Error>> {
427 if let Ok(creds) = SalesforceCredentials::from_sfdx_alias("default").await {
428 println!("✓ Using credentials from Salesforce CLI\n");
429 return Ok(creds);
430 }
431
432 match SalesforceCredentials::from_env() {
433 Ok(creds) => {
434 println!("✓ Using credentials from environment variables\n");
435 Ok(creds)
436 }
437 Err(e) => {
438 eprintln!("✗ Failed to load credentials: {}", e);
439 eprintln!("\nPlease either:");
440 eprintln!(" 1. Authenticate with Salesforce CLI: sf org login web");
441 eprintln!(" 2. Set environment variables: SF_INSTANCE_URL, SF_ACCESS_TOKEN");
442 Err(e.into())
443 }
444 }
445}