pub(crate) mod crud;
pub(crate) mod describe;
pub(crate) mod limits;
pub(crate) mod query;
pub(crate) mod query_plan_analyzer;
pub(crate) mod search;
pub use crate::api::soql::{SoqlQueryBuilder, escape_soql};
pub use limits::{LimitInfo, OrgLimits};
pub use query_plan_analyzer::{InsightSeverity, QueryInsight, QueryInsights, analyze_query_plan};
pub use search::{SearchAttributes, SearchQueryBuilder, SearchRecords, SearchResult};
use crate::api::rest_operation::RestOperation;
use crate::error::Result;
use serde::de::DeserializeOwned;
use std::sync::Arc;
#[derive(Debug)]
pub struct RestHandler<A: crate::auth::Authenticator> {
inner: Arc<crate::session::Session<A>>,
}
impl<A: crate::auth::Authenticator> Clone for RestHandler<A> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<A: crate::auth::Authenticator> RestOperation<A> for RestHandler<A> {
fn session(&self) -> &Arc<crate::session::Session<A>> {
&self.inner
}
fn path_prefix(&self) -> &'static str {
""
}
}
impl<A: crate::auth::Authenticator> RestHandler<A> {
#[must_use]
pub(crate) fn new(inner: Arc<crate::session::Session<A>>) -> Self {
Self { inner }
}
pub async fn base_url(&self) -> Result<String> {
self.inner.resolve_url("").await
}
pub(crate) async fn execute_get<T: DeserializeOwned>(
&self,
path: &str,
query: Option<&[(&str, &str)]>,
error_msg: &str,
) -> Result<T> {
let url = self.inner.resolve_url(path).await?;
let mut request = self.inner.get(&url);
if let Some(params) = query {
request = request.query(params);
}
let request = request.build().map_err(crate::error::HttpError::from)?;
self.inner.send_request_and_decode(request, error_msg).await
}
pub(crate) async fn execute_post<T: DeserializeOwned>(
&self,
path: &str,
body: &serde_json::Value,
error_msg: &str,
) -> Result<T> {
let url = self.inner.resolve_url(path).await?;
let request = self
.inner
.post(&url)
.json(body)
.build()
.map_err(crate::error::HttpError::from)?;
self.inner.send_request_and_decode(request, error_msg).await
}
pub async fn limits(&self) -> Result<limits::OrgLimits> {
self.execute_get("limits", None, "Limits API request failed")
.await
}
pub async fn search(&self, sosl: &str) -> Result<search::SearchResult> {
self.execute_get("search", Some(&[("q", sosl)]), "SOSL search request failed")
.await
}
pub async fn explain(&self, soql: &str) -> Result<crate::types::explain::ExplainResponse> {
self.execute_get(
"query",
Some(&[("explain", soql)]),
"Query Plan API request failed",
)
.await
}
}
#[cfg(test)]
mod tests {
use crate::client::{ForceClient, builder};
use crate::config::ClientConfig;
use crate::test_support::{MockAuthenticator, Must, MustMsg};
async fn create_test_client() -> ForceClient<MockAuthenticator> {
let auth = MockAuthenticator::new("test_token", "https://test.salesforce.com");
builder()
.authenticate(auth)
.build()
.await
.must_msg("failed to create test client")
}
#[tokio::test]
async fn test_rest_handler_construction() {
let client: ForceClient<MockAuthenticator> = create_test_client().await;
let _handler = client.rest();
}
#[tokio::test]
async fn test_rest_handler_is_cloneable() {
let client: ForceClient<MockAuthenticator> = create_test_client().await;
let handler1 = client.rest();
let handler2 = handler1.clone();
let url1: String = handler1.base_url().await.must();
let url2: String = handler2.base_url().await.must();
assert_eq!(url1, url2);
}
#[tokio::test]
async fn test_base_url_construction() {
let client: ForceClient<MockAuthenticator> = create_test_client().await;
let handler = client.rest();
let base_url: String = handler.base_url().await.must();
assert!(base_url.starts_with("https://test.salesforce.com"));
assert!(base_url.contains("/services/data/"));
assert!(base_url.ends_with("v60.0")); }
#[tokio::test]
async fn test_base_url_with_custom_api_version() {
let auth = MockAuthenticator::new("test_token", "https://custom.salesforce.com");
let config = ClientConfig {
api_version: "v59.0".into(),
..Default::default()
};
let client = builder()
.authenticate(auth)
.config(config)
.build()
.await
.must();
let handler = client.rest();
let base_url = handler.base_url().await.must();
assert_eq!(
base_url,
"https://custom.salesforce.com/services/data/v59.0"
);
}
#[tokio::test]
async fn test_base_url_with_different_instance() {
let auth = MockAuthenticator::new("token", "https://na139.salesforce.com");
let client = builder().authenticate(auth).build().await.must();
let handler = client.rest();
let base_url = handler.base_url().await.must();
assert!(base_url.starts_with("https://na139.salesforce.com"));
}
#[tokio::test]
async fn test_rest_handler_shares_client_config() {
let auth = MockAuthenticator::new("token", "https://shared.salesforce.com");
let config = ClientConfig {
api_version: "v58.0".into(),
..Default::default()
};
let client = builder()
.authenticate(auth)
.config(config)
.build()
.await
.must();
let handler = client.rest();
let base_url = handler.base_url().await.must();
assert!(base_url.contains("shared.salesforce.com"));
assert!(base_url.contains("v58.0"));
}
#[tokio::test]
async fn test_multiple_handlers_from_same_client() {
let client: ForceClient<MockAuthenticator> = create_test_client().await;
let handler1 = client.rest();
let handler2 = client.rest();
let url1: String = handler1.base_url().await.must();
let url2: String = handler2.base_url().await.must();
assert_eq!(url1, url2);
}
#[tokio::test]
async fn test_handler_debug_impl() {
let client: ForceClient<MockAuthenticator> = create_test_client().await;
let handler = client.rest();
let debug_str = format!("{:?}", handler);
assert!(!debug_str.is_empty());
}
}