use crate::auth::authenticator::Authenticator;
use crate::auth::token_manager::TokenManager;
use crate::client::ForceClient;
use crate::config::ClientConfig;
use crate::error::Result;
use std::marker::PhantomData;
#[derive(Debug, Clone)]
pub struct NoAuth;
#[derive(Debug, Clone)]
pub struct HasAuth;
#[derive(Debug)]
pub struct ForceClientBuilder<Auth = NoAuth> {
config: Option<ClientConfig>,
_auth: PhantomData<Auth>,
}
#[derive(Debug)]
pub struct AuthenticatedBuilder<A: Authenticator> {
config: Option<ClientConfig>,
authenticator: A,
#[cfg(feature = "data_cloud")]
dc_config: Option<crate::auth::DataCloudConfig>,
}
impl ForceClientBuilder<NoAuth> {
#[must_use]
pub const fn new() -> Self {
Self {
config: None,
_auth: PhantomData,
}
}
#[must_use]
pub fn config(mut self, config: ClientConfig) -> Self {
self.config = Some(config);
self
}
pub fn authenticate<A: Authenticator>(self, authenticator: A) -> AuthenticatedBuilder<A> {
AuthenticatedBuilder {
config: self.config,
authenticator,
#[cfg(feature = "data_cloud")]
dc_config: None,
}
}
}
impl Default for ForceClientBuilder<NoAuth> {
fn default() -> Self {
Self::new()
}
}
impl<A: Authenticator> AuthenticatedBuilder<A> {
#[must_use]
pub fn config(mut self, config: ClientConfig) -> Self {
self.config = Some(config);
self
}
#[cfg(feature = "data_cloud")]
#[must_use]
pub fn with_data_cloud(mut self, config: crate::auth::DataCloudConfig) -> Self {
self.dc_config = Some(config);
self
}
#[allow(clippy::unused_async)] pub async fn build(self) -> Result<ForceClient<A>> {
use crate::session::Session;
use std::sync::Arc;
let config = self.config.unwrap_or_default();
let http_client = reqwest::Client::builder()
.timeout(config.timeout)
.build()
.map_err(crate::error::HttpError::from)?;
let http_executor = crate::http::HttpExecutor::with_client(
http_client.clone(),
config.max_retries,
config.timeout,
);
let token_manager = Arc::new(TokenManager::new(self.authenticator));
#[cfg(feature = "data_cloud")]
let dc_session = self.dc_config.map(|dc_config| {
let dc_api_version = dc_config
.api_version
.clone()
.unwrap_or_else(|| config.api_version.clone());
let dc_client_config = ClientConfig {
api_version: dc_api_version,
..config.clone()
};
let dc_auth = crate::auth::DataCloudAuthenticator::new(
Arc::clone(&token_manager),
http_client.clone(),
dc_config,
);
let dc_token_manager = Arc::new(TokenManager::new(dc_auth));
let dc_http_executor = crate::http::HttpExecutor::with_client(
http_client.clone(),
dc_client_config.max_retries,
dc_client_config.timeout,
);
Arc::new(Session {
config: dc_client_config,
http_client: http_client.clone(),
http_executor: dc_http_executor,
token_manager: dc_token_manager,
})
});
let session = Session {
config,
http_client,
http_executor,
token_manager,
};
Ok(ForceClient {
inner: Arc::new(session),
#[cfg(feature = "data_cloud")]
dc_session,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{ClientConfig, Environment};
use crate::test_support::{MockAuthenticator, Must};
#[test]
fn test_builder_new_creates_noauth() {
let _builder: ForceClientBuilder<NoAuth> = ForceClientBuilder::new();
}
#[test]
fn test_builder_config_chainable() {
let config = ClientConfig::default();
let builder = ForceClientBuilder::new().config(config);
let _: ForceClientBuilder<NoAuth> = builder;
}
#[test]
fn test_builder_authenticate_transitions_state() {
let builder = ForceClientBuilder::new();
let auth_builder = builder.authenticate(MockAuthenticator::new(
"mock_token",
"https://test.salesforce.com",
));
let _: AuthenticatedBuilder<MockAuthenticator> = auth_builder;
}
#[tokio::test]
async fn test_builder_builds_client() {
let client = ForceClientBuilder::new()
.authenticate(MockAuthenticator::new(
"mock_token",
"https://test.salesforce.com",
))
.build()
.await
.must();
let config = client.config();
assert_eq!(config.api_version, "v60.0");
assert_eq!(config.environment, Environment::Production);
assert_eq!(config.timeout, std::time::Duration::from_secs(30));
assert_eq!(config.max_retries, 3);
}
#[tokio::test]
async fn test_full_builder_flow() {
let config = ClientConfig {
api_version: "v60.0".to_string(),
environment: Environment::Sandbox,
timeout: std::time::Duration::from_secs(30),
max_retries: 3,
};
let client = ForceClientBuilder::new()
.config(config.clone())
.authenticate(MockAuthenticator::new(
"mock_token",
"https://test.salesforce.com",
))
.build()
.await
.must();
assert_eq!(client.config().api_version, "v60.0");
assert_eq!(client.config().environment, Environment::Sandbox);
}
#[tokio::test]
async fn test_builder_config_after_auth() {
let config = ClientConfig {
api_version: "v61.0".to_string(),
environment: Environment::Production,
timeout: std::time::Duration::from_secs(45),
max_retries: 5,
};
let client = ForceClientBuilder::new()
.authenticate(MockAuthenticator::new(
"mock_token",
"https://test.salesforce.com",
))
.config(config)
.build()
.await
.must();
assert_eq!(client.config().api_version, "v61.0");
}
}