use std::time::Duration;
#[cfg(test)]
use tonic::transport::Uri;
use tonic::transport::{Certificate, Channel, ClientTlsConfig};
use tracing::{debug, instrument};
use crate::data::{DamlError, DamlResult};
use crate::service::DamlTimeService;
use crate::service::{
DamlCommandCompletionService, DamlCommandService, DamlCommandSubmissionService, DamlContractService,
DamlEventQueryService, DamlPackageService, DamlStateService, DamlUpdateService, DamlVersionService,
};
#[cfg(feature = "admin")]
use crate::service::{
DamlCommandInspectionService, DamlIdentityProviderConfigService, DamlPackageManagementService,
DamlParticipantPruningService, DamlPartyManagementService, DamlUserManagementService,
};
const DEFAULT_TIMEOUT_SECS: u64 = 5;
const DEFAULT_CONNECT_TIMEOUT_SECS: u64 = 5;
#[derive(Debug, Default)]
pub struct DamlGrpcClientConfig {
uri: String,
timeout: Duration,
connect_timeout: Option<Duration>,
concurrency_limit: Option<usize>,
rate_limit: Option<(u64, Duration)>,
initial_stream_window_size: Option<u32>,
initial_connection_window_size: Option<u32>,
tcp_keepalive: Option<Duration>,
tcp_nodelay: bool,
tls_config: Option<DamlGrpcTlsConfig>,
auth_token: Option<String>,
}
#[derive(Debug)]
pub struct DamlGrpcTlsConfig {
ca_cert: Option<Vec<u8>>,
}
pub struct DamlGrpcClientBuilder {
config: DamlGrpcClientConfig,
}
impl DamlGrpcClientBuilder {
pub fn uri(uri: impl Into<String>) -> Self {
Self {
config: DamlGrpcClientConfig {
uri: uri.into(),
timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECS),
connect_timeout: Some(Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS)),
..DamlGrpcClientConfig::default()
},
}
}
pub fn timeout(self, timeout: Duration) -> Self {
Self {
config: DamlGrpcClientConfig {
timeout,
..self.config
},
}
}
pub fn connect_timeout(self, connect_timeout: Option<Duration>) -> Self {
Self {
config: DamlGrpcClientConfig {
connect_timeout,
..self.config
},
}
}
pub fn concurrency_limit(self, concurrency_limit: usize) -> Self {
Self {
config: DamlGrpcClientConfig {
concurrency_limit: Some(concurrency_limit),
..self.config
},
}
}
pub fn rate_limit(self, rate_limit: (u64, Duration)) -> Self {
Self {
config: DamlGrpcClientConfig {
rate_limit: Some(rate_limit),
..self.config
},
}
}
pub fn initial_stream_window_size(self, initial_stream_window_size: u32) -> Self {
Self {
config: DamlGrpcClientConfig {
initial_stream_window_size: Some(initial_stream_window_size),
..self.config
},
}
}
pub fn initial_connection_window_size(self, initial_connection_window_size: u32) -> Self {
Self {
config: DamlGrpcClientConfig {
initial_connection_window_size: Some(initial_connection_window_size),
..self.config
},
}
}
pub fn tcp_keepalive(self, tcp_keepalive: Duration) -> Self {
Self {
config: DamlGrpcClientConfig {
tcp_keepalive: Some(tcp_keepalive),
..self.config
},
}
}
pub fn tcp_nodelay(self, tcp_nodelay: bool) -> Self {
Self {
config: DamlGrpcClientConfig {
tcp_nodelay,
..self.config
},
}
}
pub fn with_tls(self, ca_cert: impl Into<Vec<u8>>) -> Self {
Self {
config: DamlGrpcClientConfig {
tls_config: Some(DamlGrpcTlsConfig {
ca_cert: Some(ca_cert.into()),
}),
..self.config
},
}
}
pub fn with_tls_system_roots(self) -> Self {
Self {
config: DamlGrpcClientConfig {
tls_config: Some(DamlGrpcTlsConfig {
ca_cert: None,
}),
..self.config
},
}
}
pub fn with_auth(self, auth_token: String) -> Self {
Self {
config: DamlGrpcClientConfig {
auth_token: Some(auth_token),
..self.config
},
}
}
pub async fn connect(self) -> DamlResult<DamlGrpcClient> {
DamlGrpcClient::connect(self.config).await
}
}
#[derive(Debug)]
pub struct DamlGrpcClient {
config: DamlGrpcClientConfig,
channel: Channel,
}
impl DamlGrpcClient {
#[instrument]
pub async fn connect(config: DamlGrpcClientConfig) -> DamlResult<Self> {
debug!("connecting to {}", config.uri);
let channel = Self::make_channel(&config).await?;
Ok(Self {
config,
channel,
})
}
pub const fn config(&self) -> &DamlGrpcClientConfig {
&self.config
}
pub fn package_service(&self) -> DamlPackageService<'_> {
DamlPackageService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn command_submission_service(&self) -> DamlCommandSubmissionService<'_> {
DamlCommandSubmissionService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn command_completion_service(&self) -> DamlCommandCompletionService<'_> {
DamlCommandCompletionService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn update_service(&self) -> DamlUpdateService<'_> {
DamlUpdateService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn state_service(&self) -> DamlStateService<'_> {
DamlStateService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn event_query_service(&self) -> DamlEventQueryService<'_> {
DamlEventQueryService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn contract_service(&self) -> DamlContractService<'_> {
DamlContractService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn command_service(&self) -> DamlCommandService<'_> {
DamlCommandService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn version_service(&self) -> DamlVersionService<'_> {
DamlVersionService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
#[cfg(feature = "admin")]
pub fn package_management_service(&self) -> DamlPackageManagementService<'_> {
DamlPackageManagementService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
#[cfg(feature = "admin")]
pub fn party_management_service(&self) -> DamlPartyManagementService<'_> {
DamlPartyManagementService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
#[cfg(feature = "admin")]
pub fn user_management_service(&self) -> DamlUserManagementService<'_> {
DamlUserManagementService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
#[cfg(feature = "admin")]
pub fn identity_provider_config_service(&self) -> DamlIdentityProviderConfigService<'_> {
DamlIdentityProviderConfigService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
#[cfg(feature = "admin")]
pub fn command_inspection_service(&self) -> DamlCommandInspectionService<'_> {
DamlCommandInspectionService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
#[cfg(feature = "admin")]
pub fn participant_pruning_service(&self) -> DamlParticipantPruningService<'_> {
DamlParticipantPruningService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
pub fn time_service(&self) -> DamlTimeService<'_> {
DamlTimeService::new(self.channel.clone(), self.config.auth_token.as_deref())
}
async fn make_channel(config: &DamlGrpcClientConfig) -> DamlResult<Channel> {
let mut endpoint = Channel::from_shared(config.uri.clone())?;
if let Some(limit) = config.concurrency_limit {
endpoint = endpoint.concurrency_limit(limit);
}
if let Some((limit, duration)) = config.rate_limit {
endpoint = endpoint.rate_limit(limit, duration);
}
if let Some(size) = config.initial_stream_window_size {
endpoint = endpoint.initial_stream_window_size(size);
}
if let Some(size) = config.initial_connection_window_size {
endpoint = endpoint.initial_connection_window_size(size);
}
if let Some(duration) = config.tcp_keepalive {
endpoint = endpoint.tcp_keepalive(Some(duration));
}
endpoint = endpoint.tcp_nodelay(config.tcp_nodelay);
endpoint = endpoint.timeout(config.timeout);
if let Some(duration) = config.connect_timeout {
endpoint = endpoint.connect_timeout(duration);
}
match &config.tls_config {
Some(DamlGrpcTlsConfig {
ca_cert: Some(cert),
}) => {
endpoint = endpoint.tls_config(ClientTlsConfig::new().ca_certificate(Certificate::from_pem(cert)))?;
},
Some(DamlGrpcTlsConfig {
ca_cert: None,
}) => {
endpoint = endpoint.tls_config(ClientTlsConfig::new())?;
},
_ => {},
}
endpoint.connect().await.map_err(DamlError::from)
}
#[cfg(test)]
pub(crate) fn dummy_for_testing() -> Self {
DamlGrpcClient {
config: DamlGrpcClientConfig::default(),
channel: Channel::builder(Uri::from_static("http://dummy.for.testing")).connect_lazy(),
}
}
}