azure_data_cosmos/options/
mod.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4use crate::constants;
5use crate::models::ThroughputProperties;
6use azure_core::http::headers::{AsHeaders, HeaderName, HeaderValue};
7use azure_core::http::{headers, ClientMethodOptions, ClientOptions, Etag};
8use std::convert::Infallible;
9use std::fmt;
10use std::fmt::Display;
11
12/// Options used when creating a [`CosmosClient`](crate::CosmosClient).
13#[derive(Clone, Default)]
14pub struct CosmosClientOptions {
15    pub client_options: ClientOptions,
16}
17
18/// Options to be passed to [`DatabaseClient::create_container()`](crate::clients::DatabaseClient::create_container()).
19#[derive(Clone, Default)]
20pub struct CreateContainerOptions<'a> {
21    pub method_options: ClientMethodOptions<'a>,
22    pub throughput: Option<ThroughputProperties>,
23}
24
25/// Options to be passed to [`ContainerClient::replace()`](crate::clients::ContainerClient::replace()).
26#[derive(Clone, Default)]
27pub struct ReplaceContainerOptions<'a> {
28    pub method_options: ClientMethodOptions<'a>,
29}
30
31/// Options to be passed to [`CosmosClient::create_database()`](crate::CosmosClient::create_database()).
32#[derive(Clone, Default)]
33pub struct CreateDatabaseOptions<'a> {
34    pub method_options: ClientMethodOptions<'a>,
35    pub throughput: Option<ThroughputProperties>,
36}
37
38/// Options to be passed to [`ContainerClient::delete()`](crate::clients::ContainerClient::delete()).
39#[derive(Clone, Default)]
40pub struct DeleteContainerOptions<'a> {
41    pub method_options: ClientMethodOptions<'a>,
42}
43
44/// Options to be passed to [`DatabaseClient::delete()`](crate::clients::DatabaseClient::delete()).
45#[derive(Clone, Default)]
46pub struct DeleteDatabaseOptions<'a> {
47    pub method_options: ClientMethodOptions<'a>,
48}
49
50/// Specifies consistency levels that can be used when working with Cosmos APIs.
51///
52/// Learn more at [Consistency Levels](https://learn.microsoft.com/azure/cosmos-db/consistency-levels)
53#[derive(Clone)]
54pub enum ConsistencyLevel {
55    ConsistentPrefix,
56    Eventual,
57    Session,
58    BoundedStaleness,
59    Strong,
60}
61
62impl Display for ConsistencyLevel {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        let value = match self {
65            ConsistencyLevel::ConsistentPrefix => "ConsistentPrefix",
66            ConsistencyLevel::Eventual => "Eventual",
67            ConsistencyLevel::Session => "Session",
68            ConsistencyLevel::BoundedStaleness => "BoundedStaleness",
69            ConsistencyLevel::Strong => "Strong",
70        };
71        write!(f, "{}", value)
72    }
73}
74
75/// Specifies indexing directives that can be used when working with Cosmos APIs.
76#[derive(Clone)]
77pub enum IndexingDirective {
78    Default,
79    Include,
80    Exclude,
81}
82
83impl Display for IndexingDirective {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        let value = match self {
86            IndexingDirective::Default => "Default",
87            IndexingDirective::Include => "Include",
88            IndexingDirective::Exclude => "Exclude",
89        };
90        write!(f, "{}", value)
91    }
92}
93
94/// Options to be passed to APIs that manipulate items.
95#[derive(Clone, Default)]
96pub struct ItemOptions<'a> {
97    pub method_options: ClientMethodOptions<'a>,
98    /// Triggers executed before the operation.
99    ///
100    /// See [Triggers](https://learn.microsoft.com/rest/api/cosmos-db/triggers) for more.
101    pub pre_triggers: Option<Vec<String>>,
102    /// Triggers executed after the operation.
103    ///
104    /// See [Triggers](https://learn.microsoft.com/rest/api/cosmos-db/triggers) for more.
105    pub post_triggers: Option<Vec<String>>,
106    /// Applies when working with Session consistency.
107    /// Each new write request to Azure Cosmos DB is assigned a new Session Token.
108    /// The client instance will use this token internally with each read/query request to ensure that the set consistency level is maintained.
109    ///
110    /// See [Session Tokens](https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-manage-consistency?tabs=portal%2Cdotnetv2%2Capi-async#utilize-session-tokens) for more.
111    pub session_token: Option<String>,
112    /// Used to specify the consistency level for the operation.
113    ///
114    /// The default value is the consistency level set on the Cosmos DB account.
115    /// See [Consistency Levels](https://learn.microsoft.com/azure/cosmos-db/consistency-levels)
116    pub consistency_level: Option<ConsistencyLevel>,
117    /// Sets indexing directive for the operation.
118    pub indexing_directive: Option<IndexingDirective>,
119    /// If specified, the operation will only be performed if the item matches the provided Etag.
120    ///
121    /// See [Optimistic Concurrency Control](https://learn.microsoft.com/azure/cosmos-db/nosql/database-transactions-optimistic-concurrency#optimistic-concurrency-control) for more.
122    pub if_match_etag: Option<Etag>,
123    /// When this value is true, write operations will respond with the new value of the resource being written.
124    ///
125    /// The default for this is `false`, which reduces the network and CPU burden that comes from serializing and deserializing the response.
126    pub enable_content_response_on_write: bool,
127}
128
129impl AsHeaders for ItemOptions<'_> {
130    type Error = Infallible;
131    type Iter = std::vec::IntoIter<(HeaderName, HeaderValue)>;
132
133    fn as_headers(&self) -> Result<Self::Iter, Self::Error> {
134        let mut headers = Vec::new();
135
136        if let Some(pre_triggers) = &self.pre_triggers {
137            headers.push((
138                constants::PRE_TRIGGER_INCLUDE,
139                pre_triggers.join(",").into(),
140            ));
141        }
142
143        if let Some(post_triggers) = &self.post_triggers {
144            headers.push((
145                constants::POST_TRIGGER_INCLUDE,
146                post_triggers.join(",").into(),
147            ));
148        }
149
150        if let Some(session_token) = &self.session_token {
151            headers.push((constants::SESSION_TOKEN, session_token.into()));
152        }
153
154        if let Some(consistency_level) = &self.consistency_level {
155            headers.push((
156                constants::CONSISTENCY_LEVEL,
157                consistency_level.to_string().into(),
158            ));
159        }
160
161        if let Some(indexing_directive) = &self.indexing_directive {
162            headers.push((
163                constants::INDEXING_DIRECTIVE,
164                indexing_directive.to_string().into(),
165            ));
166        }
167
168        if let Some(etag) = &self.if_match_etag {
169            headers.push((headers::IF_MATCH, etag.to_string().into()));
170        }
171
172        if !self.enable_content_response_on_write {
173            headers.push((headers::PREFER, constants::PREFER_MINIMAL));
174        }
175
176        Ok(headers.into_iter())
177    }
178}
179
180/// Options to be passed to [`DatabaseClient::query_containers()`](crate::clients::DatabaseClient::query_containers())
181#[derive(Clone, Default)]
182pub struct QueryContainersOptions<'a> {
183    pub method_options: ClientMethodOptions<'a>,
184}
185
186/// Options to be passed to [`CosmosClient::query_databases()`](crate::CosmosClient::query_databases())
187#[derive(Clone, Default)]
188pub struct QueryDatabasesOptions<'a> {
189    pub method_options: ClientMethodOptions<'a>,
190}
191
192/// Options to be passed to [`ContainerClient::query_items()`](crate::clients::ContainerClient::query_items()).
193#[derive(Clone, Default)]
194pub struct QueryOptions<'a> {
195    pub method_options: ClientMethodOptions<'a>,
196
197    /// An external query engine to use for executing the query.
198    ///
199    /// NOTE: This is an unstable feature and may change in the future.
200    /// Specifically, the query engine may be built-in to the SDK in the future, and this option may be removed entirely.
201    #[cfg(feature = "preview_query_engine")]
202    pub query_engine: Option<crate::query::QueryEngineRef>,
203}
204
205impl QueryOptions<'_> {
206    pub fn into_owned(self) -> QueryOptions<'static> {
207        QueryOptions {
208            method_options: ClientMethodOptions {
209                context: self.method_options.context.into_owned(),
210            },
211            #[cfg(feature = "preview_query_engine")]
212            query_engine: self.query_engine,
213        }
214    }
215}
216
217/// Options to be passed to [`ContainerClient::read()`](crate::clients::ContainerClient::read()).
218#[derive(Clone, Default)]
219pub struct ReadContainerOptions<'a> {
220    pub method_options: ClientMethodOptions<'a>,
221}
222
223/// Options to be passed to [`DatabaseClient::read()`](crate::clients::DatabaseClient::read()).
224#[derive(Clone, Default)]
225pub struct ReadDatabaseOptions<'a> {
226    pub method_options: ClientMethodOptions<'a>,
227}
228
229/// Options to be passed to operations related to Throughput offers.
230#[derive(Clone, Default)]
231pub struct ThroughputOptions<'a> {
232    pub method_options: ClientMethodOptions<'a>,
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn item_options_as_headers() {
241        let item_options = ItemOptions {
242            pre_triggers: Some(vec!["PreTrigger1".to_string(), "PreTrigger2".to_string()]),
243            post_triggers: Some(vec!["PostTrigger1".to_string(), "PostTrigger2".to_string()]),
244            session_token: Some("SessionToken".to_string()),
245            consistency_level: Some(ConsistencyLevel::Session),
246            indexing_directive: Some(IndexingDirective::Include),
247            if_match_etag: Some(Etag::from("etag_value")),
248            enable_content_response_on_write: false,
249            ..Default::default()
250        };
251
252        let headers_result: Vec<(HeaderName, HeaderValue)> =
253            item_options.as_headers().unwrap().collect();
254
255        let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![
256            (
257                constants::PRE_TRIGGER_INCLUDE,
258                "PreTrigger1,PreTrigger2".into(),
259            ),
260            (
261                constants::POST_TRIGGER_INCLUDE,
262                "PostTrigger1,PostTrigger2".into(),
263            ),
264            (constants::SESSION_TOKEN, "SessionToken".into()),
265            (constants::CONSISTENCY_LEVEL, "Session".into()),
266            (constants::INDEXING_DIRECTIVE, "Include".into()),
267            (headers::IF_MATCH, "etag_value".into()),
268            (headers::PREFER, constants::PREFER_MINIMAL),
269        ];
270
271        assert_eq!(headers_result, headers_expected);
272    }
273
274    #[test]
275    fn item_options_empty_as_headers_with_content_response() {
276        let item_options = ItemOptions::default();
277
278        let headers_result: Vec<(HeaderName, HeaderValue)> =
279            item_options.as_headers().unwrap().collect();
280
281        let headers_expected: Vec<(HeaderName, HeaderValue)> =
282            vec![(headers::PREFER, constants::PREFER_MINIMAL)];
283
284        assert_eq!(headers_result, headers_expected);
285    }
286
287    #[test]
288    fn item_options_empty_as_headers() {
289        let item_options = ItemOptions {
290            enable_content_response_on_write: true,
291            ..Default::default()
292        };
293
294        let headers_result: Vec<(HeaderName, HeaderValue)> =
295            item_options.as_headers().unwrap().collect();
296
297        let headers_expected: Vec<(HeaderName, HeaderValue)> = vec![];
298
299        assert_eq!(headers_result, headers_expected);
300    }
301}