onedrive_api/
option.rs

1//! Configurable options which can be used to customize API behaviors or responses.
2//!
3//! # Note
4//! Some requests do not support all of these parameters,
5//! and using them will cause an error.
6//!
7//! Be careful and read the documentation of API from Microsoft before
8//! applying any options.
9//!
10//! # See also
11//! [Microsoft Docs](https://docs.microsoft.com/en-us/graph/query-parameters)
12#![allow(clippy::module_name_repetitions)] // Ambiguous if without sufficies.
13use crate::{
14    resource::{ResourceField, Tag},
15    util::RequestBuilderTransformer,
16    ConflictBehavior,
17};
18use reqwest::{header, RequestBuilder};
19use std::{fmt::Write, marker::PhantomData};
20
21#[derive(Debug, Default, Clone, PartialEq, Eq)]
22struct AccessOption {
23    if_match: Option<String>,
24    if_none_match: Option<String>,
25}
26
27impl AccessOption {
28    fn if_match(mut self, tag: &Tag) -> Self {
29        self.if_match = Some(tag.0.clone());
30        self
31    }
32
33    fn if_none_match(mut self, tag: &Tag) -> Self {
34        self.if_none_match = Some(tag.0.clone());
35        self
36    }
37}
38
39impl RequestBuilderTransformer for AccessOption {
40    fn trans(self, mut req: RequestBuilder) -> RequestBuilder {
41        if let Some(v) = self.if_match {
42            req = req.header(header::IF_MATCH, v);
43        }
44        if let Some(v) = self.if_none_match {
45            req = req.header(header::IF_NONE_MATCH, v);
46        }
47        req
48    }
49}
50
51/// Option for GET-like requests to one resource object.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ObjectOption<Field> {
54    access_opt: AccessOption,
55    select_buf: String,
56    expand_buf: String,
57    _marker: PhantomData<dyn Fn(&Field) + Send + Sync>,
58}
59
60impl<Field: ResourceField> ObjectOption<Field> {
61    /// Create an empty (default) option.
62    #[must_use]
63    pub fn new() -> Self {
64        Self {
65            access_opt: AccessOption::default(),
66            select_buf: String::new(),
67            expand_buf: String::new(),
68            _marker: PhantomData,
69        }
70    }
71
72    /// Only response if the object matches the `tag`.
73    ///
74    /// Will cause HTTP 412 Precondition Failed otherwise.
75    ///
76    /// It is usually used for PUT-like requests to assert preconditions, but
77    /// most of GET-like requests also support it.
78    ///
79    /// It will add `If-Match` to the request header.
80    #[must_use]
81    pub fn if_match(mut self, tag: &Tag) -> Self {
82        self.access_opt = self.access_opt.if_match(tag);
83        self
84    }
85
86    /// Only response if the object does not match the `tag`.
87    ///
88    /// Will cause the relative API returns `None` otherwise.
89    ///
90    /// It is usually used for GET-like requests to reduce data transmission if
91    /// cached data can be reused.
92    ///
93    /// This will add `If-None-Match` to the request header.
94    #[must_use]
95    pub fn if_none_match(mut self, tag: &Tag) -> Self {
96        self.access_opt = self.access_opt.if_none_match(tag);
97        self
98    }
99
100    /// Select only some fields of the resource object.
101    ///
102    /// See documentation of module [`onedrive_api::resource`][resource] for more details.
103    ///
104    /// # Note
105    /// If called more than once, all fields mentioned will be selected.
106    ///
107    /// # See also
108    /// [Microsoft Docs](https://docs.microsoft.com/en-us/graph/query-parameters#select-parameter)
109    ///
110    /// [resource]: ../resource/index.html#field-descriptors
111    #[must_use]
112    pub fn select(mut self, fields: &[Field]) -> Self {
113        for sel in fields {
114            self = self.select_raw(&[sel.__raw_name()]);
115        }
116        self
117    }
118
119    fn select_raw(mut self, fields: &[&str]) -> Self {
120        for sel in fields {
121            write!(self.select_buf, ",{sel}").unwrap();
122        }
123        self
124    }
125
126    /// Expand a field of the resource object.
127    ///
128    /// See documentation of module [`onedrive_api::resource`][resource] for more details.
129    ///
130    /// # Note
131    /// If called more than once, all fields mentioned will be expanded.
132    /// `select_children` should be raw camelCase field names mentioned in Microsoft Docs below.
133    ///
134    /// # See also
135    /// [Microsoft Docs](https://docs.microsoft.com/en-us/graph/query-parameters#expand-parameter)
136    ///
137    /// [resource]: ../resource/index.html#field-descriptors
138    #[must_use]
139    pub fn expand(self, field: Field, select_children: Option<&[&str]>) -> Self {
140        self.expand_raw(field.__raw_name(), select_children)
141    }
142
143    fn expand_raw(mut self, field: &str, select_children: Option<&[&str]>) -> Self {
144        let buf = &mut self.expand_buf;
145        write!(buf, ",{field}").unwrap();
146        if let Some(children) = select_children {
147            write!(buf, "($select=").unwrap();
148            for sel in children {
149                write!(buf, "{sel},").unwrap();
150            }
151            write!(buf, ")").unwrap();
152        }
153        self
154    }
155}
156
157impl<Field: ResourceField> RequestBuilderTransformer for ObjectOption<Field> {
158    fn trans(self, mut req: RequestBuilder) -> RequestBuilder {
159        req = self.access_opt.trans(req);
160        if let Some(s) = self.select_buf.get(1..) {
161            req = req.query(&[("$select", s)]);
162        }
163        if let Some(s) = self.expand_buf.get(1..) {
164            req = req.query(&[("$expand", s)]);
165        }
166        req
167    }
168}
169
170impl<Field: ResourceField> Default for ObjectOption<Field> {
171    fn default() -> Self {
172        Self::new()
173    }
174}
175
176/// Option for GET-like requests for a collection of resource objects.
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct CollectionOption<Field> {
179    obj_option: ObjectOption<Field>,
180    order_buf: Option<String>,
181    page_size_buf: Option<String>,
182    get_count_buf: bool,
183}
184
185impl<Field: ResourceField> CollectionOption<Field> {
186    /// Create an empty (default) option.
187    #[must_use]
188    pub fn new() -> Self {
189        Self {
190            obj_option: ObjectOption::default(),
191            order_buf: None,
192            page_size_buf: None,
193            get_count_buf: false,
194        }
195    }
196
197    /// Only response if the object matches the `tag`.
198    ///
199    /// # See also
200    /// [`ObjectOption::if_match`][if_match]
201    ///
202    /// [if_match]: ./struct.ObjectOption.html#method.if_match
203    #[must_use]
204    pub fn if_match(mut self, tag: &Tag) -> Self {
205        self.obj_option = self.obj_option.if_match(tag);
206        self
207    }
208
209    /// Only response if the object does not match the `tag`.
210    ///
211    /// # See also
212    /// [`ObjectOption::if_none_match`][if_none_match]
213    ///
214    /// [if_none_match]: ./struct.ObjectOption.html#method.if_none_match
215    #[must_use]
216    pub fn if_none_match(mut self, tag: &Tag) -> Self {
217        self.obj_option = self.obj_option.if_none_match(tag);
218        self
219    }
220
221    /// Select only some fields of the resource object.
222    ///
223    /// See documentation of module [`onedrive_api::resource`][resource] for more details.
224    ///
225    /// # See also
226    /// [`ObjectOption::select`][select]
227    ///
228    /// [select]: ./struct.ObjectOption.html#method.select
229    /// [resource]: ../resource/index.html#field-descriptors
230    #[must_use]
231    pub fn select(mut self, fields: &[Field]) -> Self {
232        self.obj_option = self.obj_option.select(fields);
233        self
234    }
235
236    /// Expand a field of the resource object.
237    ///
238    /// See documentation of module [`onedrive_api::resource`][resource] for more details.
239    ///
240    /// # See also
241    /// [`ObjectOption::expand`][expand]
242    ///
243    /// [expand]: ./struct.ObjectOption.html#method.expand
244    /// [resource]: ../resource/index.html#field-descriptors
245    #[must_use]
246    pub fn expand(mut self, field: Field, select_children: Option<&[&str]>) -> Self {
247        self.obj_option = self.obj_option.expand(field, select_children);
248        self
249    }
250
251    /// Specify the sort order of the items in response.
252    ///
253    /// # Note
254    /// If called more than once, only the last call make sense.
255    ///
256    /// # See also
257    /// [Microsoft Docs](https://docs.microsoft.com/en-us/graph/query-parameters#orderby-parameter)
258    #[must_use]
259    pub fn order_by(mut self, field: Field, order: Order) -> Self {
260        let order = match order {
261            Order::Ascending => "asc",
262            Order::Descending => "desc",
263        };
264        self.order_buf = Some(format!("{} {}", field.__raw_name(), order));
265        self
266    }
267
268    /// Specify the number of items per page.
269    ///
270    /// # Note
271    /// If called more than once, only the last call make sense.
272    ///
273    /// # See also
274    /// [Microsoft Docs](https://docs.microsoft.com/en-us/graph/query-parameters#top-parameter)
275    #[must_use]
276    pub fn page_size(mut self, size: usize) -> Self {
277        self.page_size_buf = Some(size.to_string());
278        self
279    }
280
281    /// Specify to get the number of all items.
282    ///
283    /// # Note
284    /// If called more than once, only the last call make sense.
285    ///
286    /// Note that Track Changes API does not support this. Setting it in like
287    /// [`track_changes_from_initial_with_option`][track_init_opt] will cause a panic.
288    ///
289    /// # See also
290    /// [Microsoft Docs](https://docs.microsoft.com/en-us/graph/query-parameters#count-parameter)
291    ///
292    /// [track_init_opt]: ../struct.OneDrive.html#method.track_changes_from_initial_with_option
293    #[must_use]
294    pub fn get_count(mut self, get_count: bool) -> Self {
295        self.get_count_buf = get_count;
296        self
297    }
298
299    pub(crate) fn has_get_count(&self) -> bool {
300        self.get_count_buf
301    }
302}
303
304impl<Field: ResourceField> RequestBuilderTransformer for CollectionOption<Field> {
305    fn trans(self, mut req: RequestBuilder) -> RequestBuilder {
306        req = self.obj_option.trans(req);
307        if let Some(s) = &self.order_buf {
308            req = req.query(&[("$orderby", s)]);
309        }
310        if let Some(s) = &self.page_size_buf {
311            req = req.query(&[("$top", s)]);
312        }
313        if self.get_count_buf {
314            req = req.query(&[("$count", "true")]);
315        }
316        req
317    }
318}
319
320impl<Field: ResourceField> Default for CollectionOption<Field> {
321    fn default() -> Self {
322        Self::new()
323    }
324}
325
326/// Specify the sorting order.
327///
328/// Used in [`CollectionOption::order_by`][order_by].
329///
330/// [order_by]: ./struct.CollectionOption.html#method.order_by
331#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
332pub enum Order {
333    /// Ascending order.
334    Ascending,
335    /// Descending order.
336    Descending,
337}
338
339/// Option for PUT-like requests of `DriveItem`.
340#[derive(Debug, Default, Clone, PartialEq, Eq)]
341pub struct DriveItemPutOption {
342    access_opt: AccessOption,
343    conflict_behavior: Option<ConflictBehavior>,
344}
345
346impl DriveItemPutOption {
347    /// Create an empty (default) option.
348    #[must_use]
349    pub fn new() -> Self {
350        Self::default()
351    }
352
353    /// Only response if the object matches the `tag`.
354    ///
355    /// # See also
356    /// [`ObjectOption::if_match`][if_match]
357    ///
358    /// [if_match]: ./struct.ObjectOption.html#method.if_match
359    #[must_use]
360    pub fn if_match(mut self, tag: &Tag) -> Self {
361        self.access_opt = self.access_opt.if_match(tag);
362        self
363    }
364
365    // `if_none_match` is not supported in PUT-like requests.
366
367    /// Specify the behavior if the target item already exists.
368    ///
369    /// # Note
370    /// This not only available for DELETE-like requests. Read the docs first.
371    ///
372    /// # See also
373    /// `@microsoft.graph.conflictBehavior` of `DriveItem` on [Microsoft Docs](https://docs.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0#instance-attributes)
374    #[must_use]
375    pub fn conflict_behavior(mut self, conflict_behavior: ConflictBehavior) -> Self {
376        self.conflict_behavior = Some(conflict_behavior);
377        self
378    }
379
380    pub(crate) fn get_conflict_behavior(&self) -> Option<ConflictBehavior> {
381        self.conflict_behavior
382    }
383}
384
385impl RequestBuilderTransformer for DriveItemPutOption {
386    fn trans(self, req: RequestBuilder) -> RequestBuilder {
387        self.access_opt.trans(req)
388    }
389}
390
391#[cfg(test)]
392// `#[expect()]` is incompatible with our MSRV.
393#[allow(dead_code)]
394mod tests {
395    use super::*;
396    use crate::resource;
397
398    fn assert_send_sync<T: Send + Sync>() {}
399
400    fn assert_object_option_is_send_sync() {
401        assert_send_sync::<ObjectOption<resource::DriveField>>();
402        assert_send_sync::<ObjectOption<resource::DriveItemField>>();
403    }
404
405    fn assert_collection_option_is_send_sync() {
406        assert_send_sync::<CollectionOption<resource::DriveField>>();
407        assert_send_sync::<CollectionOption<resource::DriveItemField>>();
408    }
409
410    fn assert_drive_item_put_option_is_send_sync() {
411        assert_send_sync::<DriveItemPutOption>();
412    }
413}