use crate::constants;
use crate::models::ThroughputProperties;
use azure_core::http::headers::{self, Headers};
use std::fmt;
use std::fmt::Display;
#[doc(inline)]
pub use azure_data_cosmos_driver::models::{ETag, Precondition, SessionToken};
#[doc(inline)]
pub use azure_data_cosmos_driver::options::{
ContentResponseOnWrite, EndToEndOperationLatencyPolicy, ExcludedRegions, OperationOptions,
OperationOptionsBuilder, OperationOptionsView, ReadConsistencyStrategy, Region,
};
fn apply_precondition_headers(precondition: &Precondition, headers: &mut Headers) {
match precondition {
Precondition::IfMatch(etag) => {
headers.insert(headers::IF_MATCH, etag.to_string());
}
Precondition::IfNoneMatch(etag) => {
headers.insert(constants::IF_NONE_MATCH, etag.to_string());
}
_ => {}
}
}
fn apply_content_response_on_write_header(
content_response_on_write: Option<&ContentResponseOnWrite>,
headers: &mut Headers,
) {
match content_response_on_write {
Some(ContentResponseOnWrite::Enabled) => {}
_ => {
headers.insert(headers::PREFER, constants::PREFER_MINIMAL);
}
}
}
#[derive(Clone, Default, Debug)]
#[non_exhaustive]
pub struct CosmosClientOptions {
pub(crate) operation: OperationOptions,
pub(crate) user_agent_suffix: Option<String>,
pub(crate) application_region: Option<Region>,
}
impl CosmosClientOptions {
pub fn with_user_agent_suffix(mut self, suffix: impl Into<String>) -> Self {
self.user_agent_suffix = Some(suffix.into());
self
}
pub fn with_operation_options(mut self, operation: OperationOptions) -> Self {
self.operation = operation;
self
}
pub(crate) fn apply_headers(&self, headers: &mut Headers) {
if let Some(custom_headers) = self.operation.custom_headers() {
for (header_name, header_value) in custom_headers {
if headers.get_optional_str(header_name).is_none() {
headers.insert(header_name.clone(), header_value.clone());
}
}
}
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct CreateContainerOptions {
pub(crate) throughput: Option<ThroughputProperties>,
}
impl CreateContainerOptions {
pub fn with_throughput(mut self, throughput: ThroughputProperties) -> Self {
self.throughput = Some(throughput);
self
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct ReplaceContainerOptions;
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct CreateDatabaseOptions {
pub(crate) throughput: Option<ThroughputProperties>,
}
impl CreateDatabaseOptions {
pub fn with_throughput(mut self, throughput: ThroughputProperties) -> Self {
self.throughput = Some(throughput);
self
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct DeleteContainerOptions;
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct DeleteDatabaseOptions;
#[derive(Clone, Debug)]
pub enum ConsistencyLevel {
ConsistentPrefix,
Eventual,
Session,
BoundedStaleness,
Strong,
}
impl Display for ConsistencyLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self {
ConsistencyLevel::ConsistentPrefix => "ConsistentPrefix",
ConsistencyLevel::Eventual => "Eventual",
ConsistencyLevel::Session => "Session",
ConsistencyLevel::BoundedStaleness => "BoundedStaleness",
ConsistencyLevel::Strong => "Strong",
};
write!(f, "{}", value)
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct ItemReadOptions {
pub operation: OperationOptions,
pub session_token: Option<SessionToken>,
pub precondition: Option<Precondition>,
}
impl ItemReadOptions {
pub fn with_session_token(mut self, session_token: impl Into<SessionToken>) -> Self {
self.session_token = Some(session_token.into());
self
}
pub fn with_precondition(mut self, precondition: Precondition) -> Self {
self.precondition = Some(precondition);
self
}
pub fn with_operation_options(mut self, operation: OperationOptions) -> Self {
self.operation = operation;
self
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct ItemWriteOptions {
pub operation: OperationOptions,
pub session_token: Option<SessionToken>,
pub precondition: Option<Precondition>,
}
impl ItemWriteOptions {
pub fn with_session_token(mut self, session_token: impl Into<SessionToken>) -> Self {
self.session_token = Some(session_token.into());
self
}
pub fn with_precondition(mut self, precondition: Precondition) -> Self {
self.precondition = Some(precondition);
self
}
pub fn with_operation_options(mut self, operation: OperationOptions) -> Self {
self.operation = operation;
self
}
}
impl ItemWriteOptions {
pub(crate) fn apply_headers(&self, headers: &mut Headers) {
if let Some(custom_headers) = self.operation.custom_headers() {
for (name, value) in custom_headers {
if headers.get_optional_str(name).is_none() {
headers.insert(name.clone(), value.clone());
}
}
}
if let Some(session_token) = &self.session_token {
headers.insert(constants::SESSION_TOKEN, session_token.to_string());
}
if let Some(precondition) = &self.precondition {
apply_precondition_headers(precondition, headers);
}
apply_content_response_on_write_header(
self.operation.content_response_on_write.as_ref(),
headers,
);
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct BatchOptions {
pub operation: OperationOptions,
pub session_token: Option<SessionToken>,
}
impl BatchOptions {
pub fn with_session_token(mut self, session_token: impl Into<SessionToken>) -> Self {
self.session_token = Some(session_token.into());
self
}
pub fn with_operation_options(mut self, operation: OperationOptions) -> Self {
self.operation = operation;
self
}
}
impl BatchOptions {
pub(crate) fn apply_headers(&self, headers: &mut Headers) {
if let Some(custom_headers) = self.operation.custom_headers() {
for (name, value) in custom_headers {
if headers.get_optional_str(name).is_none() {
headers.insert(name.clone(), value.clone());
}
}
}
if let Some(session_token) = &self.session_token {
headers.insert(constants::SESSION_TOKEN, session_token.to_string());
}
apply_content_response_on_write_header(
self.operation.content_response_on_write.as_ref(),
headers,
);
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct QueryContainersOptions;
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct QueryDatabasesOptions;
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct QueryOptions {
pub operation: OperationOptions,
pub session_token: Option<SessionToken>,
}
impl QueryOptions {
pub fn with_session_token(mut self, session_token: impl Into<SessionToken>) -> Self {
self.session_token = Some(session_token.into());
self
}
pub fn with_operation_options(mut self, operation: OperationOptions) -> Self {
self.operation = operation;
self
}
}
impl QueryOptions {
pub(crate) fn apply_headers(&self, headers: &mut Headers) {
if let Some(custom_headers) = self.operation.custom_headers() {
for (name, value) in custom_headers {
if headers.get_optional_str(name).is_none() {
headers.insert(name.clone(), value.clone());
}
}
}
if let Some(session_token) = &self.session_token {
headers.insert(constants::SESSION_TOKEN, session_token.to_string());
}
}
}
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct ReadContainerOptions;
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct ReadDatabaseOptions;
#[derive(Clone, Default)]
#[non_exhaustive]
pub struct ThroughputOptions;
#[cfg(test)]
mod tests {
use super::*;
use azure_core::http::headers::{HeaderName, HeaderValue};
use std::collections::HashMap;
fn headers_to_map<I>(headers: I) -> HashMap<HeaderName, HeaderValue>
where
I: IntoIterator<Item = (HeaderName, HeaderValue)>,
{
headers.into_iter().collect()
}
#[test]
fn item_write_options_as_headers() {
let mut custom_headers = HashMap::new();
custom_headers.insert(
HeaderName::from_static("x-custom-header"),
HeaderValue::from_static("custom_value"),
);
let operation = OperationOptions::default().with_custom_headers(custom_headers);
let options = ItemWriteOptions {
operation,
..Default::default()
}
.with_session_token("SessionToken".to_string())
.with_precondition(Precondition::IfMatch(ETag::from("etag_value")));
let mut headers_result = Headers::new();
options.apply_headers(&mut headers_result);
let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![
("x-custom-header".into(), "custom_value".into()),
(constants::SESSION_TOKEN, "SessionToken".into()),
(headers::IF_MATCH, "etag_value".into()),
(headers::PREFER, constants::PREFER_MINIMAL),
];
assert_eq!(
headers_to_map(headers_result),
headers_to_map(headers_expected)
);
}
#[test]
fn custom_headers_should_not_override_sdk_set_headers() {
let mut custom_headers = HashMap::new();
custom_headers.insert(
constants::SESSION_TOKEN,
HeaderValue::from_static("CustomSession"),
);
let operation = OperationOptions::default().with_custom_headers(custom_headers);
let options = ItemWriteOptions {
operation,
..Default::default()
}
.with_session_token("RealSessionToken".to_string());
let mut headers_result = Headers::new();
options.apply_headers(&mut headers_result);
let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![
(constants::SESSION_TOKEN, "RealSessionToken".into()),
(headers::PREFER, constants::PREFER_MINIMAL),
];
assert_eq!(
headers_to_map(headers_result),
headers_to_map(headers_expected)
);
}
#[test]
fn client_options_as_headers() {
let mut custom_headers = HashMap::new();
custom_headers.insert(
HeaderName::from_static("x-custom-header"),
HeaderValue::from_static("custom_value"),
);
let operation = OperationOptions::default().with_custom_headers(custom_headers);
let client_options = CosmosClientOptions {
operation,
..Default::default()
};
let mut headers_result = Headers::new();
client_options.apply_headers(&mut headers_result);
let headers_expected: Vec<(HeaderName, HeaderValue)> =
vec![("x-custom-header".into(), "custom_value".into())];
assert_eq!(
headers_to_map(headers_result),
headers_to_map(headers_expected)
);
}
#[test]
fn query_options_as_headers() {
let mut custom_headers = HashMap::new();
custom_headers.insert(
HeaderName::from_static("x-custom-header"),
HeaderValue::from_static("custom_value"),
);
let operation = OperationOptions::default().with_custom_headers(custom_headers);
let query_options = QueryOptions {
operation,
..Default::default()
}
.with_session_token("QuerySessionToken".to_string());
let mut headers_result = Headers::new();
query_options.apply_headers(&mut headers_result);
let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![
("x-custom-header".into(), "custom_value".into()),
(constants::SESSION_TOKEN, "QuerySessionToken".into()),
];
assert_eq!(
headers_to_map(headers_result),
headers_to_map(headers_expected)
);
}
#[test]
fn item_write_options_default_as_headers() {
let options = ItemWriteOptions::default();
let mut headers_result = Headers::new();
options.apply_headers(&mut headers_result);
let headers_result: Vec<(HeaderName, HeaderValue)> = headers_result.into_iter().collect();
let headers_expected: Vec<(HeaderName, HeaderValue)> =
vec![(headers::PREFER, constants::PREFER_MINIMAL)];
assert_eq!(headers_result, headers_expected);
}
#[test]
fn item_write_options_with_content_response_enabled() {
let mut operation = OperationOptions::default();
operation.content_response_on_write = Some(ContentResponseOnWrite::Enabled);
let options = ItemWriteOptions {
operation,
..Default::default()
};
let mut headers_result = Headers::new();
options.apply_headers(&mut headers_result);
let headers_result: Vec<(HeaderName, HeaderValue)> = headers_result.into_iter().collect();
let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![];
assert_eq!(headers_result, headers_expected);
}
#[test]
fn batch_options_as_headers() {
let mut custom_headers = HashMap::new();
custom_headers.insert(
HeaderName::from_static("x-custom-header"),
HeaderValue::from_static("custom_value"),
);
let mut operation = OperationOptions::default().with_custom_headers(custom_headers);
operation.content_response_on_write = Some(ContentResponseOnWrite::Enabled);
let batch_options = BatchOptions {
operation,
..Default::default()
}
.with_session_token("BatchSessionToken".to_string());
let mut headers_result = Headers::new();
batch_options.apply_headers(&mut headers_result);
let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![
("x-custom-header".into(), "custom_value".into()),
(constants::SESSION_TOKEN, "BatchSessionToken".into()),
];
assert_eq!(
headers_to_map(headers_result),
headers_to_map(headers_expected)
);
}
#[test]
fn batch_options_custom_headers_should_not_override_sdk_set_headers() {
let mut custom_headers = HashMap::new();
custom_headers.insert(
constants::SESSION_TOKEN,
HeaderValue::from_static("CustomSession"),
);
let operation = OperationOptions::default().with_custom_headers(custom_headers);
let batch_options = BatchOptions {
operation,
..Default::default()
}
.with_session_token("RealSessionToken".to_string());
let mut headers_result = Headers::new();
batch_options.apply_headers(&mut headers_result);
let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![
(constants::SESSION_TOKEN, "RealSessionToken".into()),
(headers::PREFER, constants::PREFER_MINIMAL),
];
assert_eq!(
headers_to_map(headers_result),
headers_to_map(headers_expected)
);
}
#[test]
fn batch_options_default_as_headers() {
let batch_options = BatchOptions::default();
let mut headers_result = Headers::new();
batch_options.apply_headers(&mut headers_result);
let headers_result: Vec<(HeaderName, HeaderValue)> = headers_result.into_iter().collect();
let headers_expected: Vec<(HeaderName, HeaderValue)> =
vec![(headers::PREFER, constants::PREFER_MINIMAL)];
assert_eq!(headers_result, headers_expected);
}
#[test]
fn batch_options_with_content_response_enabled() {
let mut operation = OperationOptions::default();
operation.content_response_on_write = Some(ContentResponseOnWrite::Enabled);
let batch_options = BatchOptions {
operation,
..Default::default()
};
let mut headers_result = Headers::new();
batch_options.apply_headers(&mut headers_result);
let headers_result: Vec<(HeaderName, HeaderValue)> = headers_result.into_iter().collect();
let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![];
assert_eq!(headers_result, headers_expected);
}
}