Skip to main content

rest_crud/
rest_crud.rs

1//! REST API CRUD operations example
2//!
3//! This example demonstrates TWO approaches to working with Salesforce data:
4//! 1. Type-safe structs (recommended for production)
5//! 2. Dynamic serde_json::Value (useful for exploration/prototyping)
6//!
7//! Run with: cargo run --example rest_crud
8
9use busbar_sf_auth::{Credentials, SalesforceCredentials};
10use busbar_sf_rest::SalesforceRestClient;
11use serde::{Deserialize, Serialize};
12
13/// Account record with proper type safety
14///
15/// Use this approach when:
16/// - Building production applications
17/// - You know the schema ahead of time
18/// - You want compile-time safety and IDE support
19#[derive(Debug, Serialize, Deserialize)]
20struct Account {
21    #[serde(rename = "Id", skip_serializing_if = "Option::is_none")]
22    id: Option<String>,
23    #[serde(rename = "Name")]
24    name: String,
25    #[serde(rename = "Industry", skip_serializing_if = "Option::is_none")]
26    industry: Option<String>,
27    #[serde(rename = "Phone", skip_serializing_if = "Option::is_none")]
28    phone: Option<String>,
29    #[serde(rename = "Website", skip_serializing_if = "Option::is_none")]
30    website: Option<String>,
31}
32
33#[tokio::main]
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}
68
69/// Example 1a: Create with type-safe struct (RECOMMENDED)
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}
112
113/// Example 2a: Read with type-safe deserialization
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}
165
166/// Example 3: Update with partial data (works with either pattern)
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}
186
187/// Example 4: Upsert (create or update based on external ID)
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}
221
222/// Example 5: Delete a record
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}
236
237/// Example 6: Create multiple records at once with type safety
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}
293
294/// Helper function to get credentials
295async fn get_credentials() -> Result<SalesforceCredentials, Box<dyn std::error::Error>> {
296    // Try SFDX first
297    if let Ok(creds) = SalesforceCredentials::from_sfdx_alias("default").await {
298        println!("✓ Using credentials from Salesforce CLI\n");
299        return Ok(creds);
300    }
301
302    // Fall back to environment variables
303    match SalesforceCredentials::from_env() {
304        Ok(creds) => {
305            println!("✓ Using credentials from environment variables\n");
306            Ok(creds)
307        }
308        Err(e) => {
309            eprintln!("✗ Failed to load credentials: {}", e);
310            eprintln!("\nPlease either:");
311            eprintln!("  1. Authenticate with Salesforce CLI: sf org login web");
312            eprintln!("  2. Set environment variables: SF_INSTANCE_URL, SF_ACCESS_TOKEN");
313            Err(e.into())
314        }
315    }
316}