use serde::de::DeserializeOwned;
use tracing::instrument;
use busbar_sf_client::QueryResult;
use crate::error::Result;
use crate::types::SearchResult;
impl super::ToolingClient {
#[instrument(skip(self))]
pub async fn query<T: DeserializeOwned>(&self, soql: &str) -> Result<QueryResult<T>> {
self.client.tooling_query(soql).await.map_err(Into::into)
}
#[instrument(skip(self))]
pub async fn query_all<T: DeserializeOwned + Clone>(&self, soql: &str) -> Result<Vec<T>> {
self.client
.tooling_query_all(soql)
.await
.map_err(Into::into)
}
#[instrument(skip(self))]
pub async fn query_all_records<T: DeserializeOwned>(
&self,
soql: &str,
) -> Result<QueryResult<T>> {
let encoded = urlencoding::encode(soql);
let url = format!(
"{}/services/data/v{}/queryAll/?q={}",
self.client.instance_url(),
self.client.api_version(),
encoded
);
self.client.get_json(&url).await.map_err(Into::into)
}
#[instrument(skip(self))]
pub async fn search<T: DeserializeOwned>(&self, sosl: &str) -> Result<SearchResult<T>> {
let encoded = urlencoding::encode(sosl);
let url = format!(
"{}/services/data/v{}/tooling/search/?q={}",
self.client.instance_url(),
self.client.api_version(),
encoded
);
self.client.get_json(&url).await.map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::super::ToolingClient;
#[tokio::test]
async fn test_query_all_records_wiremock() {
use wiremock::matchers::{method, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};
let mock_server = MockServer::start().await;
let body = serde_json::json!({
"totalSize": 2,
"done": true,
"records": [
{"Id": "01p000000000001AAA", "Name": "DeletedClass", "IsDeleted": true},
{"Id": "01p000000000002AAA", "Name": "ArchivedClass", "IsDeleted": true}
]
});
Mock::given(method("GET"))
.and(path_regex(".*/queryAll/"))
.respond_with(ResponseTemplate::new(200).set_body_json(&body))
.mount(&mock_server)
.await;
let client = ToolingClient::new(mock_server.uri(), "test-token").unwrap();
let result: busbar_sf_client::QueryResult<serde_json::Value> = client
.query_all_records("SELECT Id, Name FROM ApexClass WHERE IsDeleted = true")
.await
.expect("should succeed");
assert_eq!(result.total_size, 2);
assert_eq!(result.records.len(), 2);
assert!(result.done);
}
#[tokio::test]
async fn test_search_wiremock() {
use wiremock::matchers::{method, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};
let mock_server = MockServer::start().await;
let body = serde_json::json!({
"searchRecords": [
{
"Id": "01p000000000001AAA",
"attributes": {
"type": "ApexClass",
"url": "/services/data/v62.0/tooling/sobjects/ApexClass/01p000000000001AAA"
}
}
]
});
Mock::given(method("GET"))
.and(path_regex(".*/tooling/search/"))
.respond_with(ResponseTemplate::new(200).set_body_json(&body))
.mount(&mock_server)
.await;
let client = ToolingClient::new(mock_server.uri(), "test-token").unwrap();
let result: crate::types::SearchResult<serde_json::Value> = client
.search("FIND {test} IN ALL FIELDS RETURNING ApexClass(Id, Name)")
.await
.expect("should succeed");
assert_eq!(result.search_records.len(), 1);
}
}