use crate::{driver::transport::is_emulator_host, models::AccountEndpoint, options::Region};
use std::time::Duration;
const MIN_END_TO_END_OPERATION_TIMEOUT: Duration = Duration::from_secs(1);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ContentResponseOnWrite {
Enabled,
#[default]
Disabled,
}
impl From<bool> for ContentResponseOnWrite {
fn from(value: bool) -> Self {
if value {
Self::Enabled
} else {
Self::Disabled
}
}
}
impl From<ContentResponseOnWrite> for bool {
fn from(value: ContentResponseOnWrite) -> Self {
matches!(value, ContentResponseOnWrite::Enabled)
}
}
impl std::str::FromStr for ContentResponseOnWrite {
type Err = crate::error::CosmosError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"true" | "enabled" => Ok(Self::Enabled),
"false" | "disabled" => Ok(Self::Disabled),
_ => Err(crate::error::CosmosError::builder().with_status(crate::error::CosmosStatus::new(azure_core::http::StatusCode::BadRequest)).with_message(format!(
"Unknown content response on write value: '{s}'. Expected 'true'/'false' or 'enabled'/'disabled'"
)).build()),
}
}
}
impl std::fmt::Display for ContentResponseOnWrite {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Enabled => f.write_str("Enabled"),
Self::Disabled => f.write_str("Disabled"),
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EndToEndOperationLatencyPolicy {
timeout: Duration,
}
impl EndToEndOperationLatencyPolicy {
pub fn new(timeout: Duration) -> Self {
Self {
timeout: timeout.max(MIN_END_TO_END_OPERATION_TIMEOUT),
}
}
pub fn timeout(&self) -> Duration {
self.timeout
}
}
impl From<Duration> for EndToEndOperationLatencyPolicy {
fn from(timeout: Duration) -> Self {
Self::new(timeout)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ExcludedRegions(pub Vec<Region>);
impl ExcludedRegions {
pub fn new() -> Self {
Self::default()
}
pub fn with_region(mut self, region: impl Into<Region>) -> Self {
self.0.push(region.into());
self
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Region> {
self.0.iter()
}
}
impl<T: Into<Region>> FromIterator<T> for ExcludedRegions {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self(iter.into_iter().map(Into::into).collect())
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ServerCertificateValidation {
#[default]
Required,
RequiredUnlessEmulator,
}
impl ServerCertificateValidation {
pub(crate) fn allows_insecure_connection(self, endpoint: &AccountEndpoint) -> bool {
self == ServerCertificateValidation::RequiredUnlessEmulator && is_emulator_host(endpoint)
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::{EndToEndOperationLatencyPolicy, ServerCertificateValidation};
use crate::models::AccountEndpoint;
#[test]
fn end_to_end_latency_policy_clamps_timeout_below_one_second() {
let policy = EndToEndOperationLatencyPolicy::new(Duration::from_millis(250));
assert_eq!(policy.timeout(), Duration::from_secs(1));
}
#[test]
fn end_to_end_latency_policy_keeps_timeout_at_or_above_one_second() {
let policy = EndToEndOperationLatencyPolicy::new(Duration::from_secs(2));
assert_eq!(policy.timeout(), Duration::from_secs(2));
}
#[test]
fn required_validation_does_not_allow_insecure_with_production_endpoint() {
let endpoint = AccountEndpoint::try_from("https://myaccount.documents.azure.com:443/")
.expect("Failed to create production endpoint");
let validation = ServerCertificateValidation::Required;
assert!(!validation.allows_insecure_connection(&endpoint));
}
#[test]
fn required_validation_does_not_allow_insecure_with_emulator_endpoint() {
let endpoint = AccountEndpoint::try_from("https://localhost:8081/")
.expect("Failed to create emulator endpoint");
let validation = ServerCertificateValidation::Required;
assert!(!validation.allows_insecure_connection(&endpoint));
}
#[test]
fn required_unless_emulator_does_not_allow_insecure_with_production_endpoint() {
let endpoint = AccountEndpoint::try_from("https://myaccount.documents.azure.com:443/")
.expect("Failed to create production endpoint");
let validation = ServerCertificateValidation::RequiredUnlessEmulator;
assert!(!validation.allows_insecure_connection(&endpoint));
}
#[test]
fn required_unless_emulator_allows_insecure_with_known_emulator_endpoints() {
const EMULATOR_ENDPOINTS: &[&str] = &[
"https://localhost:8081/",
"https://127.0.0.1:8081/",
"https://[::1]:8081/",
"https://[0:0:0:0:0:0:0:1]:8081/",
];
let validation = ServerCertificateValidation::RequiredUnlessEmulator;
for endpoint_url in EMULATOR_ENDPOINTS {
let endpoint = AccountEndpoint::try_from(*endpoint_url)
.expect("Failed to create emulator endpoint");
assert!(
validation.allows_insecure_connection(&endpoint),
"Expected insecure connection to be allowed for endpoint: {}",
endpoint_url
);
}
}
}