Skip to main content

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}