#![allow(clippy::doc_markdown)]
pub(crate) mod dashboards;
pub(crate) mod reports;
pub(crate) mod types;
pub use types::{
ComponentStatus, DashboardListItem, DashboardResults, DashboardStatus, DataCell, FactMapEntry,
FilterOperator, Grouping, GroupingInfo, GroupingsContainer, InstanceStatus, ReportAttributes,
ReportDescribe, ReportExtendedMetadata, ReportFilter, ReportFormat, ReportInstance,
ReportListItem, ReportMetadata, ReportMetadataRequest, ReportResults, ReportRow,
ReportTypeCategory, ReportTypeInfo, ReportTypeRef, StandardDateFilter, SummaryValue,
};
use crate::error::Result;
use std::sync::Arc;
#[derive(Debug)]
pub struct AnalyticsHandler<A: crate::auth::Authenticator> {
inner: Arc<crate::session::Session<A>>,
}
impl<A: crate::auth::Authenticator> Clone for AnalyticsHandler<A> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<A: crate::auth::Authenticator> AnalyticsHandler<A> {
#[must_use]
pub(crate) fn new(inner: Arc<crate::session::Session<A>>) -> Self {
Self { inner }
}
pub(crate) async fn resolve_analytics_url(&self, path: &str) -> Result<String> {
let clean = path.trim_start_matches('/');
if clean.is_empty() {
self.inner.resolve_url("analytics").await
} else {
self.inner.resolve_url(&format!("analytics/{clean}")).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_analytics_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
}
pub(crate) async fn post<T: serde::de::DeserializeOwned>(
&self,
path: &str,
query: Option<&[(&str, &str)]>,
body: &(impl serde::Serialize + Sync),
error_msg: &str,
) -> Result<T> {
let url = self.resolve_analytics_url(path).await?;
let mut req = self.inner.post(&url).json(body);
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
}
pub(crate) async fn post_empty<T: serde::de::DeserializeOwned>(
&self,
path: &str,
query: Option<&[(&str, &str)]>,
error_msg: &str,
) -> Result<T> {
let url = self.resolve_analytics_url(path).await?;
let mut req = self.inner.post(&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
}
pub(crate) async fn put_empty<T: serde::de::DeserializeOwned>(
&self,
path: &str,
error_msg: &str,
) -> Result<T> {
let url = self.resolve_analytics_url(path).await?;
let request = self
.inner
.put(&url)
.build()
.map_err(crate::error::HttpError::from)?;
self.inner.send_request_and_decode(request, error_msg).await
}
pub(crate) async fn delete_empty(&self, path: &str, error_msg: &str) -> Result<()> {
let url = self.resolve_analytics_url(path).await?;
let request = self
.inner
.delete(&url)
.build()
.map_err(crate::error::HttpError::from)?;
self.inner
.execute_and_check_success(request, error_msg)
.await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::client::{ForceClient, builder};
use crate::test_utils::mock_auth::MockAuthenticator;
use crate::test_utils::must::{Must, MustMsg};
async fn 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_analytics_handler_construction() {
let client = test_client().await;
let _handler = client.analytics();
}
#[tokio::test]
async fn test_analytics_handler_is_cloneable() {
let client = test_client().await;
let h1 = client.analytics();
let h2 = h1.clone();
let url1 = h1.resolve_analytics_url("").await.must();
let url2 = h2.resolve_analytics_url("").await.must();
assert_eq!(url1, url2);
}
#[tokio::test]
async fn test_resolve_analytics_url_base() {
let client = test_client().await;
let handler = client.analytics();
let url = handler.resolve_analytics_url("").await.must();
assert!(url.contains("/services/data/"));
assert!(url.ends_with("analytics"));
}
#[tokio::test]
async fn test_resolve_analytics_url_with_path() {
let client = test_client().await;
let handler = client.analytics();
let url = handler
.resolve_analytics_url("reports/00O3000000B5Yn2")
.await
.must();
assert!(url.contains("analytics/reports/00O3000000B5Yn2"));
}
#[tokio::test]
async fn test_resolve_analytics_url_leading_slash() {
let client = test_client().await;
let handler = client.analytics();
let url1 = handler.resolve_analytics_url("dashboards").await.must();
let url2 = handler.resolve_analytics_url("/dashboards").await.must();
assert_eq!(url1, url2);
}
#[tokio::test]
async fn test_analytics_url_includes_api_version() {
let client = test_client().await;
let handler = client.analytics();
let url = handler.resolve_analytics_url("reports").await.must();
assert!(url.contains("v67.0"), "URL should contain API version");
}
#[tokio::test]
async fn test_analytics_handler_debug() {
let client = test_client().await;
let handler = client.analytics();
let debug = format!("{handler:?}");
assert!(debug.contains("AnalyticsHandler"));
}
}