pub(crate) mod query;
pub(crate) mod types;
pub use query::SqlQueryResponse;
pub use types::{DataCloudRecord, SqlQueryRequest};
use crate::auth::data_cloud::DataCloudAuthenticator;
use crate::error::Result;
use std::sync::Arc;
#[derive(Debug)]
pub struct DataCloudHandler<A: crate::auth::Authenticator> {
inner: Arc<crate::session::Session<DataCloudAuthenticator<A>>>,
}
impl<A: crate::auth::Authenticator> Clone for DataCloudHandler<A> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<A: crate::auth::Authenticator> DataCloudHandler<A> {
#[must_use]
pub(crate) fn new(inner: Arc<crate::session::Session<DataCloudAuthenticator<A>>>) -> Self {
Self { inner }
}
pub(crate) async fn resolve_dc_url(&self, path: &str) -> Result<String> {
let clean = path.trim_start_matches('/');
if clean.is_empty() {
self.inner.resolve_url("ssot").await
} else {
self.inner.resolve_url(&format!("ssot/{clean}")).await
}
}
pub(crate) async fn post<T: serde::de::DeserializeOwned>(
&self,
path: &str,
body: &(impl serde::Serialize + Sync),
error_msg: &str,
) -> Result<T> {
let url = self.resolve_dc_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(crate) async fn get<T: serde::de::DeserializeOwned>(
&self,
path: &str,
query: Option<&[(&str, &str)]>,
error_msg: &str,
) -> Result<T> {
let url = self.resolve_dc_url(path).await?;
let mut req = self.inner.get(&url);
if let Some(params) = query {
req = req.query(params);
}
let request = req.build().map_err(crate::error::HttpError::from)?;
self.inner.send_request_and_decode(request, error_msg).await
}
}
#[cfg(test)]
mod tests {
use crate::auth::DataCloudConfig;
use crate::client::{ForceClient, builder};
use crate::test_support::{MockAuthenticator, Must, MustMsg};
async fn test_dc_client() -> ForceClient<MockAuthenticator> {
let auth = MockAuthenticator::new("test_token", "https://test.salesforce.com");
builder()
.authenticate(auth)
.with_data_cloud(DataCloudConfig::default())
.build()
.await
.must_msg("failed to create test client with DC")
}
async fn test_client_no_dc() -> ForceClient<MockAuthenticator> {
let auth = MockAuthenticator::new("test_token", "https://test.salesforce.com");
builder()
.authenticate(auth)
.build()
.await
.must_msg("failed to create test client without DC")
}
#[tokio::test]
async fn test_data_cloud_handler_construction() {
let client = test_dc_client().await;
let _handler = client.data_cloud().must();
}
fn assert_clone<T: Clone>() {}
#[tokio::test]
async fn test_data_cloud_handler_is_cloneable() {
assert_clone::<super::DataCloudHandler<MockAuthenticator>>();
}
#[tokio::test]
async fn test_data_cloud_not_configured_returns_error() {
let client = test_client_no_dc().await;
let result = client.data_cloud();
assert!(result.is_err());
let Err(err) = result else {
panic!("Expected error")
};
let err = err.to_string();
assert!(
err.contains("Data Cloud not configured"),
"Error should mention DC not configured, got: {err}"
);
}
#[tokio::test]
async fn test_data_cloud_handler_debug() {
let client = test_dc_client().await;
let handler = client.data_cloud().must();
let debug = format!("{handler:?}");
assert!(!debug.is_empty());
}
#[tokio::test]
async fn test_client_clone_preserves_dc_session() {
let client = test_dc_client().await;
let cloned = client.clone(); std::hint::black_box(client);
assert!(cloned.data_cloud().is_ok());
}
#[tokio::test]
async fn test_builder_without_dc_still_works() {
let client = test_client_no_dc().await;
assert_eq!(client.config().api_version, "v60.0");
}
#[tokio::test]
async fn test_builder_with_dc_api_version_override() {
let auth = MockAuthenticator::new("test_token", "https://test.salesforce.com");
let client = builder()
.authenticate(auth)
.with_data_cloud(DataCloudConfig {
api_version: Some("v64.0".into()),
..Default::default()
})
.build()
.await
.must();
assert_eq!(client.config().api_version, "v60.0");
assert!(client.data_cloud().is_ok());
}
}