Skip to main content

busbar_sf_rest/client/
mod.rs

1//! Salesforce REST API client.
2//!
3//! This client wraps `SalesforceClient` from `sf-client` and provides
4//! typed methods for REST API operations including CRUD, Query, Describe,
5//! Composite, and Collections.
6
7use busbar_sf_client::{ClientConfig, SalesforceClient};
8
9use crate::error::Result;
10
11mod binary;
12mod collections;
13mod composite;
14mod consent;
15mod crud;
16mod describe;
17mod embedded_service;
18mod invocable_actions;
19mod knowledge;
20mod layout;
21mod limits;
22mod list_views;
23mod process;
24mod query;
25mod quick_actions;
26mod scheduler;
27mod search;
28mod standalone;
29mod sync;
30mod user_password;
31
32/// Salesforce REST API client.
33///
34/// Provides typed methods for all REST API operations:
35/// - CRUD operations on SObjects
36/// - SOQL queries with automatic pagination
37/// - SOSL search
38/// - Describe operations
39/// - Composite API
40/// - SObject Collections
41///
42/// # Example
43///
44/// ```rust,ignore
45/// use sf_rest::SalesforceRestClient;
46///
47/// let client = SalesforceRestClient::new(
48///     "https://myorg.my.salesforce.com",
49///     "access_token_here",
50/// )?;
51///
52/// // Query
53/// let accounts: Vec<Account> = client.query_all("SELECT Id, Name FROM Account").await?;
54///
55/// // Create
56/// let id = client.create("Account", &json!({"Name": "New Account"})).await?;
57///
58/// // Update
59/// client.update("Account", &id, &json!({"Name": "Updated"})).await?;
60///
61/// // Delete
62/// client.delete("Account", &id).await?;
63/// ```
64#[derive(Debug, Clone)]
65pub struct SalesforceRestClient {
66    client: SalesforceClient,
67}
68
69impl SalesforceRestClient {
70    /// Create a new REST client with the given instance URL and access token.
71    pub fn new(instance_url: impl Into<String>, access_token: impl Into<String>) -> Result<Self> {
72        let client = SalesforceClient::new(instance_url, access_token)?;
73        Ok(Self { client })
74    }
75
76    /// Create a new REST client with custom HTTP configuration.
77    pub fn with_config(
78        instance_url: impl Into<String>,
79        access_token: impl Into<String>,
80        config: ClientConfig,
81    ) -> Result<Self> {
82        let client = SalesforceClient::with_config(instance_url, access_token, config)?;
83        Ok(Self { client })
84    }
85
86    /// Create a REST client from an existing SalesforceClient.
87    pub fn from_client(client: SalesforceClient) -> Self {
88        Self { client }
89    }
90
91    /// Get the underlying SalesforceClient.
92    pub fn inner(&self) -> &SalesforceClient {
93        &self.client
94    }
95
96    /// Get the instance URL.
97    pub fn instance_url(&self) -> &str {
98        self.client.instance_url()
99    }
100
101    /// Get the API version.
102    pub fn api_version(&self) -> &str {
103        self.client.api_version()
104    }
105
106    /// Set the API version.
107    pub fn with_api_version(mut self, version: impl Into<String>) -> Self {
108        self.client = self.client.with_api_version(version);
109        self
110    }
111}
112
113/// Result of a SOSL search.
114#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
115pub struct SearchResult<T> {
116    #[serde(rename = "searchRecords")]
117    pub search_records: Vec<T>,
118}
119
120/// API version information.
121#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
122pub struct ApiVersion {
123    pub version: String,
124    pub label: String,
125    pub url: String,
126}
127
128/// Result of a getDeleted request.
129#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
130pub struct GetDeletedResult {
131    #[serde(rename = "deletedRecords")]
132    pub deleted_records: Vec<DeletedRecord>,
133    #[serde(rename = "earliestDateAvailable")]
134    pub earliest_date_available: String,
135    #[serde(rename = "latestDateCovered")]
136    pub latest_date_covered: String,
137}
138
139/// A deleted record.
140#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
141pub struct DeletedRecord {
142    pub id: String,
143    #[serde(rename = "deletedDate")]
144    pub deleted_date: String,
145}
146
147/// Result of a getUpdated request.
148#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
149pub struct GetUpdatedResult {
150    pub ids: Vec<String>,
151    #[serde(rename = "latestDateCovered")]
152    pub latest_date_covered: String,
153}
154
155/// Basic information about an SObject.
156#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
157pub struct SObjectInfo {
158    #[serde(rename = "objectDescribe")]
159    pub object_describe: SObjectInfoDescribe,
160    #[serde(rename = "recentItems")]
161    pub recent_items: Vec<serde_json::Value>,
162}
163
164/// Basic describe information from SObject info endpoint.
165#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
166pub struct SObjectInfoDescribe {
167    pub name: String,
168    pub label: String,
169    #[serde(rename = "keyPrefix")]
170    pub key_prefix: Option<String>,
171    pub urls: std::collections::HashMap<String, String>,
172    pub custom: bool,
173    pub createable: bool,
174    pub updateable: bool,
175    pub deletable: bool,
176    pub queryable: bool,
177    pub searchable: bool,
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_client_creation() {
186        let client = SalesforceRestClient::new("https://na1.salesforce.com", "token123").unwrap();
187
188        assert_eq!(client.instance_url(), "https://na1.salesforce.com");
189        assert_eq!(client.api_version(), "62.0");
190    }
191
192    #[test]
193    fn test_api_version_override() {
194        let client = SalesforceRestClient::new("https://na1.salesforce.com", "token")
195            .unwrap()
196            .with_api_version("60.0");
197
198        assert_eq!(client.api_version(), "60.0");
199    }
200
201    #[test]
202    fn test_get_deleted_result_deserialize() {
203        let json = serde_json::json!({
204            "deletedRecords": [
205                {"id": "001xx000003DgAAAS", "deletedDate": "2024-01-15T10:30:00.000Z"}
206            ],
207            "earliestDateAvailable": "2024-01-01T00:00:00.000Z",
208            "latestDateCovered": "2024-01-15T23:59:59.000Z"
209        });
210        let result: GetDeletedResult = serde_json::from_value(json).unwrap();
211        assert_eq!(result.deleted_records.len(), 1);
212        assert_eq!(result.deleted_records[0].id, "001xx000003DgAAAS");
213        assert_eq!(result.earliest_date_available, "2024-01-01T00:00:00.000Z");
214    }
215
216    #[test]
217    fn test_get_updated_result_deserialize() {
218        let json = serde_json::json!({
219            "ids": ["001xx000003DgAAAS", "001xx000003DgBBAS"],
220            "latestDateCovered": "2024-01-15T23:59:59.000Z"
221        });
222        let result: GetUpdatedResult = serde_json::from_value(json).unwrap();
223        assert_eq!(result.ids.len(), 2);
224        assert_eq!(result.latest_date_covered, "2024-01-15T23:59:59.000Z");
225    }
226
227    #[test]
228    fn test_sobject_info_deserialize() {
229        let json = serde_json::json!({
230            "objectDescribe": {
231                "name": "Account",
232                "label": "Account",
233                "keyPrefix": "001",
234                "urls": {
235                    "sobject": "/services/data/v62.0/sobjects/Account",
236                    "describe": "/services/data/v62.0/sobjects/Account/describe"
237                },
238                "custom": false,
239                "createable": true,
240                "updateable": true,
241                "deletable": true,
242                "queryable": true,
243                "searchable": true
244            },
245            "recentItems": [
246                {"Id": "001xx000003DgAAAS", "Name": "Acme Corp"}
247            ]
248        });
249        let info: SObjectInfo = serde_json::from_value(json).unwrap();
250        assert_eq!(info.object_describe.name, "Account");
251        assert_eq!(info.object_describe.key_prefix, Some("001".to_string()));
252        assert!(!info.object_describe.custom);
253        assert!(info.object_describe.createable);
254        assert_eq!(info.recent_items.len(), 1);
255    }
256
257    #[test]
258    fn test_sobject_info_describe_custom() {
259        let json = serde_json::json!({
260            "objectDescribe": {
261                "name": "MyObject__c",
262                "label": "My Object",
263                "keyPrefix": "a00",
264                "urls": {},
265                "custom": true,
266                "createable": true,
267                "updateable": true,
268                "deletable": true,
269                "queryable": true,
270                "searchable": false
271            },
272            "recentItems": []
273        });
274        let info: SObjectInfo = serde_json::from_value(json).unwrap();
275        assert!(info.object_describe.custom);
276        assert!(!info.object_describe.searchable);
277        assert!(info.recent_items.is_empty());
278    }
279}