use std::sync::Arc;
use crate::{
models::AccountReference,
options::{
OperationOptions, PartitionFailoverOptions, Region, ThroughputControlGroupOptions,
ThroughputControlGroupRegistry, UserAgentSuffix,
},
};
#[cfg(feature = "fault_injection")]
use crate::fault_injection::FaultInjectionRule;
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct DriverOptions {
account: AccountReference,
operation_options: Arc<OperationOptions>,
preferred_regions: Vec<Region>,
user_agent_suffix: Option<UserAgentSuffix>,
#[cfg(feature = "fault_injection")]
fault_injection_rules: Option<Vec<Arc<FaultInjectionRule>>>,
throughput_control_groups: ThroughputControlGroupRegistry,
partition_failover_options: PartitionFailoverOptions,
}
impl DriverOptions {
pub fn builder(account: AccountReference) -> DriverOptionsBuilder {
DriverOptionsBuilder::new(account)
}
pub fn account(&self) -> &AccountReference {
&self.account
}
pub fn operation_options(&self) -> &Arc<OperationOptions> {
&self.operation_options
}
pub fn preferred_regions(&self) -> &[Region] {
&self.preferred_regions
}
pub fn user_agent_suffix(&self) -> Option<&UserAgentSuffix> {
self.user_agent_suffix.as_ref()
}
#[cfg(feature = "fault_injection")]
pub fn fault_injection_rules(&self) -> Option<&[Arc<FaultInjectionRule>]> {
self.fault_injection_rules.as_deref()
}
pub(crate) fn throughput_control_groups(&self) -> &ThroughputControlGroupRegistry {
&self.throughput_control_groups
}
pub fn partition_failover_options(&self) -> &PartitionFailoverOptions {
&self.partition_failover_options
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct DriverOptionsBuilder {
account: AccountReference,
operation_options: Option<OperationOptions>,
preferred_regions: Vec<Region>,
user_agent_suffix: Option<UserAgentSuffix>,
#[cfg(feature = "fault_injection")]
fault_injection_rules: Option<Vec<Arc<FaultInjectionRule>>>,
throughput_control_groups: ThroughputControlGroupRegistry,
partition_failover_options: Option<PartitionFailoverOptions>,
}
impl DriverOptionsBuilder {
pub fn new(account: AccountReference) -> Self {
Self {
account,
operation_options: None,
preferred_regions: Vec::new(),
user_agent_suffix: None,
#[cfg(feature = "fault_injection")]
fault_injection_rules: None,
throughput_control_groups: ThroughputControlGroupRegistry::new(),
partition_failover_options: None,
}
}
pub fn with_operation_options(mut self, options: OperationOptions) -> Self {
self.operation_options = Some(options);
self
}
pub fn with_preferred_regions(mut self, regions: Vec<Region>) -> Self {
self.preferred_regions = regions;
self
}
pub fn with_user_agent_suffix(mut self, suffix: UserAgentSuffix) -> Self {
self.user_agent_suffix = Some(suffix);
self
}
#[cfg(feature = "fault_injection")]
pub fn with_fault_injection_rules(
mut self,
rules: Vec<Arc<FaultInjectionRule>>,
) -> crate::error::Result<Self> {
if rules.is_empty() {
return Ok(self);
}
let mut seen: std::collections::HashSet<String> = self
.fault_injection_rules
.as_ref()
.map(|existing| existing.iter().map(|r| r.id().to_string()).collect())
.unwrap_or_default();
for rule in &rules {
if !seen.insert(rule.id().to_string()) {
return Err(crate::error::CosmosError::builder()
.with_status(
crate::error::CosmosStatus::CLIENT_DUPLICATE_FAULT_INJECTION_RULE_ID,
)
.with_message(format!("duplicate fault injection rule id: {}", rule.id()))
.build());
}
}
match &mut self.fault_injection_rules {
Some(existing) => existing.extend(rules),
None => self.fault_injection_rules = Some(rules),
}
Ok(self)
}
pub fn register_throughput_control_group(
mut self,
group: ThroughputControlGroupOptions,
) -> crate::error::Result<Self> {
self.throughput_control_groups
.register(group)
.map_err(|e| {
crate::error::CosmosError::builder()
.with_status(crate::error::CosmosStatus::CLIENT_THROUGHPUT_CONTROL_GROUP_REGISTRATION_FAILED)
.with_message(e.to_string())
.build()
})?;
Ok(self)
}
pub fn with_partition_failover_options(mut self, options: PartitionFailoverOptions) -> Self {
self.partition_failover_options = Some(options);
self
}
pub fn build(self) -> DriverOptions {
DriverOptions {
account: self.account,
operation_options: Arc::new(self.operation_options.unwrap_or_default()),
preferred_regions: self.preferred_regions,
user_agent_suffix: self.user_agent_suffix,
#[cfg(feature = "fault_injection")]
fault_injection_rules: self.fault_injection_rules.filter(|r| !r.is_empty()),
throughput_control_groups: self.throughput_control_groups,
partition_failover_options: self.partition_failover_options.unwrap_or_default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::options::OperationOptionsBuilder;
use url::Url;
fn test_account() -> AccountReference {
AccountReference::with_master_key(
Url::parse("https://test.documents.azure.com:443/").unwrap(),
"test-key",
)
}
#[test]
fn builder_creates_options_with_account() {
let account = test_account();
let options = DriverOptionsBuilder::new(account.clone()).build();
assert_eq!(options.account(), &account);
assert!(options
.operation_options()
.read_consistency_strategy
.is_none());
assert!(options
.operation_options()
.max_failover_retry_count
.is_none());
assert!(options
.operation_options()
.max_session_retry_count
.is_none());
}
#[test]
fn builder_sets_operation_options() {
let operation = OperationOptionsBuilder::new()
.with_max_failover_retry_count(5)
.with_max_session_retry_count(3)
.build();
let options = DriverOptionsBuilder::new(test_account())
.with_operation_options(operation)
.build();
assert_eq!(
options.operation_options().max_failover_retry_count,
Some(5)
);
assert_eq!(options.operation_options().max_session_retry_count, Some(3));
}
#[test]
fn builder_sets_all_options() {
let operation = OperationOptionsBuilder::new()
.with_max_failover_retry_count(5)
.with_max_session_retry_count(2)
.build();
let options = DriverOptionsBuilder::new(test_account())
.with_operation_options(operation)
.build();
assert_eq!(
options.operation_options().max_failover_retry_count,
Some(5)
);
assert_eq!(options.operation_options().max_session_retry_count, Some(2));
assert!(options
.operation_options()
.read_consistency_strategy
.is_none());
assert!(options.preferred_regions().is_empty());
}
#[test]
fn builder_sets_preferred_regions() {
let regions = vec![Region::WEST_US_2, Region::EAST_US];
let options = DriverOptionsBuilder::new(test_account())
.with_preferred_regions(regions.clone())
.build();
assert_eq!(options.preferred_regions(), ®ions);
}
}