busbar_sf_rest/client/query.rs
1use serde::de::DeserializeOwned;
2use tracing::instrument;
3
4use crate::error::Result;
5use crate::query::QueryResult;
6
7impl super::SalesforceRestClient {
8 /// Execute a SOQL query.
9 ///
10 /// Returns the first page of results. Use `query_all` for automatic pagination.
11 ///
12 /// # Security
13 ///
14 /// **IMPORTANT**: If you are including user-provided values in the WHERE clause,
15 /// you MUST escape them to prevent SOQL injection attacks. Use the security utilities:
16 ///
17 /// ```rust,ignore
18 /// use busbar_sf_client::security::soql;
19 ///
20 /// // WRONG - vulnerable to injection:
21 /// let query = format!("SELECT Id FROM Account WHERE Name = '{}'", user_input);
22 ///
23 /// // CORRECT - properly escaped:
24 /// let safe_value = soql::escape_string(user_input);
25 /// let query = format!("SELECT Id FROM Account WHERE Name = '{}'", safe_value);
26 /// ```
27 #[instrument(skip(self))]
28 pub async fn query<T: DeserializeOwned>(&self, soql: &str) -> Result<QueryResult<T>> {
29 self.client.query(soql).await.map_err(Into::into)
30 }
31
32 /// Execute a SOQL query and return all results (automatic pagination).
33 ///
34 /// # Security
35 ///
36 /// **IMPORTANT**: Escape user-provided values with `busbar_sf_client::security::soql::escape_string()`
37 /// to prevent SOQL injection attacks. See `query()` for examples.
38 #[instrument(skip(self))]
39 pub async fn query_all<T: DeserializeOwned + Clone>(&self, soql: &str) -> Result<Vec<T>> {
40 self.client.query_all(soql).await.map_err(Into::into)
41 }
42
43 /// Execute a SOQL query including deleted/archived records.
44 ///
45 /// # Security
46 ///
47 /// **IMPORTANT**: Escape user-provided values with `busbar_sf_client::security::soql::escape_string()`
48 /// to prevent SOQL injection attacks. See `query()` for examples.
49 #[instrument(skip(self))]
50 pub async fn query_all_including_deleted<T: DeserializeOwned>(
51 &self,
52 soql: &str,
53 ) -> Result<QueryResult<T>> {
54 let encoded = urlencoding::encode(soql);
55 let url = format!(
56 "{}/services/data/v{}/queryAll?q={}",
57 self.client.instance_url(),
58 self.client.api_version(),
59 encoded
60 );
61 self.client.get_json(&url).await.map_err(Into::into)
62 }
63
64 /// Fetch the next page of query results.
65 #[instrument(skip(self))]
66 pub async fn query_more<T: DeserializeOwned>(
67 &self,
68 next_records_url: &str,
69 ) -> Result<QueryResult<T>> {
70 self.client
71 .get_json(next_records_url)
72 .await
73 .map_err(Into::into)
74 }
75}