Skip to main content

opendal_core/raw/
ops.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Ops provides the operation args struct like [`OpRead`] for user.
19//!
20//! By using ops, users can add more context for operation.
21
22use crate::BytesRange;
23use crate::options;
24use crate::raw::*;
25
26use std::collections::HashMap;
27
28/// Arguments for `create` operation.
29///
30/// The path must be normalized.
31#[derive(Debug, Clone, Default)]
32pub struct OpCreateDir {}
33
34impl OpCreateDir {
35    /// Create a new `OpCreateDir`.
36    pub fn new() -> Self {
37        Self::default()
38    }
39}
40
41/// Arguments for `delete` operation.
42///
43/// The path must be normalized.
44#[derive(Debug, Clone, Default, Eq, Hash, PartialEq)]
45pub struct OpDelete {
46    /// The version of the object to delete.
47    version: Option<String>,
48
49    /// Whether a `delete` is recursive.
50    recursive: bool,
51}
52
53impl OpDelete {
54    /// Create a new `OpDelete`.
55    pub fn new() -> Self {
56        Self::default()
57    }
58}
59
60impl OpDelete {
61    /// Set the version of the object to delete.
62    pub fn with_version(mut self, version: &str) -> Self {
63        self.version = Some(version.into());
64        self
65    }
66
67    /// Change the recursive flag of this delete operation.
68    pub fn with_recursive(mut self, recursive: bool) -> Self {
69        self.recursive = recursive;
70        self
71    }
72
73    /// Return the version of the object to delete.
74    pub fn version(&self) -> Option<&str> {
75        self.version.as_deref()
76    }
77
78    /// Whether this delete should remove objects recursively.
79    pub fn recursive(&self) -> bool {
80        self.recursive
81    }
82}
83
84impl From<options::DeleteOptions> for OpDelete {
85    fn from(value: options::DeleteOptions) -> Self {
86        Self {
87            version: value.version,
88            recursive: value.recursive,
89        }
90    }
91}
92
93/// Arguments for `delete` operation.
94///
95/// The path must be normalized.
96#[derive(Debug, Clone, Default)]
97pub struct OpDeleter {}
98
99impl OpDeleter {
100    /// Create a new `OpDelete`.
101    pub fn new() -> Self {
102        Self::default()
103    }
104}
105
106/// Arguments for `list` operation.
107#[derive(Debug, Clone, Default)]
108pub struct OpList {
109    /// The maximum number of results that the service should return per request.
110    ///
111    /// This can be used to control the memory consumption of a list operation.
112    limit: Option<usize>,
113
114    /// The key after which the service should start listing.
115    start_after: Option<String>,
116
117    /// Whether the list operation is recursive.
118    ///
119    /// - If `false`, the operation lists only the immediate children of the given
120    ///   path.
121    /// - If `true`, the operation lists all entries whose paths start with the
122    ///   given path.
123    ///
124    /// Defaults to `false`.
125    recursive: bool,
126
127    /// Whether to return object versions.
128    ///
129    /// - If `false`, the operation does not return object versions.
130    /// - If `true`, the operation returns object versions when the service
131    ///   supports versioning.
132    ///
133    /// Defaults to `false`.
134    versions: bool,
135
136    /// Whether to return deleted objects.
137    ///
138    /// - If `false`, the operation does not return deleted objects.
139    /// - If `true`, the operation returns deleted objects when the service
140    ///   supports versioning.
141    ///
142    /// Defaults to `false`.
143    deleted: bool,
144}
145
146impl OpList {
147    /// Create a new `OpList`.
148    pub fn new() -> Self {
149        Self::default()
150    }
151
152    /// Set the maximum number of results per request.
153    pub fn with_limit(mut self, limit: usize) -> Self {
154        self.limit = Some(limit);
155        self
156    }
157
158    /// Return the maximum number of results per request.
159    pub fn limit(&self) -> Option<usize> {
160        self.limit
161    }
162
163    /// Set the key after which listing should start.
164    pub fn with_start_after(mut self, start_after: &str) -> Self {
165        self.start_after = Some(start_after.into());
166        self
167    }
168
169    /// Return the key after which listing should start.
170    pub fn start_after(&self) -> Option<&str> {
171        self.start_after.as_deref()
172    }
173
174    /// Set whether the list operation is recursive.
175    ///
176    /// - If `false`, the operation lists only the immediate children of the given
177    ///   path.
178    /// - If `true`, the operation lists all entries whose paths start with the
179    ///   given path.
180    ///
181    /// Defaults to `false`.
182    pub fn with_recursive(mut self, recursive: bool) -> Self {
183        self.recursive = recursive;
184        self
185    }
186
187    /// Return whether the list operation is recursive.
188    pub fn recursive(&self) -> bool {
189        self.recursive
190    }
191
192    /// Change the concurrent of this list operation.
193    ///
194    /// The default concurrent is 1.
195    #[deprecated(since = "0.53.2", note = "concurrent in list is no-op")]
196    pub fn with_concurrent(self, concurrent: usize) -> Self {
197        let _ = concurrent;
198        self
199    }
200
201    /// Get the concurrent of list operation.
202    #[deprecated(since = "0.53.2", note = "concurrent in list is no-op")]
203    pub fn concurrent(&self) -> usize {
204        0
205    }
206
207    /// Set whether to return object versions.
208    pub fn with_versions(mut self, versions: bool) -> Self {
209        self.versions = versions;
210        self
211    }
212
213    /// Return whether the operation includes object versions.
214    pub fn versions(&self) -> bool {
215        self.versions
216    }
217
218    /// Set whether to return deleted objects.
219    pub fn with_deleted(mut self, deleted: bool) -> Self {
220        self.deleted = deleted;
221        self
222    }
223
224    /// Return whether the operation includes deleted objects.
225    pub fn deleted(&self) -> bool {
226        self.deleted
227    }
228}
229
230impl From<options::ListOptions> for OpList {
231    fn from(value: options::ListOptions) -> Self {
232        Self {
233            limit: value.limit,
234            start_after: value.start_after,
235            recursive: value.recursive,
236            versions: value.versions,
237            deleted: value.deleted,
238        }
239    }
240}
241
242/// Arguments for `presign` operation.
243///
244/// The path must be normalized.
245#[derive(Debug, Clone)]
246pub struct OpPresign {
247    expire: Duration,
248
249    op: PresignOperation,
250}
251
252impl OpPresign {
253    /// Create a new `OpPresign`.
254    pub fn new(op: impl Into<PresignOperation>, expire: Duration) -> Self {
255        Self {
256            op: op.into(),
257            expire,
258        }
259    }
260
261    /// Return the operation to presign.
262    pub fn operation(&self) -> &PresignOperation {
263        &self.op
264    }
265
266    /// Return the request expiration duration.
267    pub fn expire(&self) -> Duration {
268        self.expire
269    }
270
271    /// Consume OpPresign into (Duration, PresignOperation)
272    pub fn into_parts(self) -> (Duration, PresignOperation) {
273        (self.expire, self.op)
274    }
275}
276
277/// Presign operation used for presign.
278#[derive(Debug, Clone)]
279#[non_exhaustive]
280pub enum PresignOperation {
281    /// Presign a stat(head) operation.
282    Stat(OpStat),
283    /// Presign a read operation.
284    Read(BytesRange, OpRead),
285    /// Presign a write operation.
286    Write(OpWrite),
287    /// Presign a delete operation.
288    Delete(OpDelete),
289}
290
291impl From<OpStat> for PresignOperation {
292    fn from(op: OpStat) -> Self {
293        Self::Stat(op)
294    }
295}
296
297impl From<OpRead> for PresignOperation {
298    fn from(v: OpRead) -> Self {
299        Self::Read(BytesRange::default(), v)
300    }
301}
302
303impl From<OpWrite> for PresignOperation {
304    fn from(v: OpWrite) -> Self {
305        Self::Write(v)
306    }
307}
308
309impl From<OpDelete> for PresignOperation {
310    fn from(v: OpDelete) -> Self {
311        Self::Delete(v)
312    }
313}
314
315/// Arguments for `read` operation.
316#[derive(Debug, Clone, Default)]
317pub struct OpRead {
318    if_match: Option<String>,
319    if_none_match: Option<String>,
320    if_modified_since: Option<Timestamp>,
321    if_unmodified_since: Option<Timestamp>,
322    override_content_type: Option<String>,
323    override_cache_control: Option<String>,
324    override_content_disposition: Option<String>,
325    version: Option<String>,
326    content_length_hint: Option<u64>,
327}
328
329impl OpRead {
330    /// Create a default `OpRead` which will read whole content of path.
331    pub fn new() -> Self {
332        Self::default()
333    }
334
335    /// Sets the content-disposition header that should be sent back by the remote read operation.
336    pub fn with_override_content_disposition(mut self, content_disposition: &str) -> Self {
337        self.override_content_disposition = Some(content_disposition.into());
338        self
339    }
340
341    /// Returns the content-disposition header that should be sent back by the remote read
342    /// operation.
343    pub fn override_content_disposition(&self) -> Option<&str> {
344        self.override_content_disposition.as_deref()
345    }
346
347    /// Sets the cache-control header that should be sent back by the remote read operation.
348    pub fn with_override_cache_control(mut self, cache_control: &str) -> Self {
349        self.override_cache_control = Some(cache_control.into());
350        self
351    }
352
353    /// Returns the cache-control header that should be sent back by the remote read operation.
354    pub fn override_cache_control(&self) -> Option<&str> {
355        self.override_cache_control.as_deref()
356    }
357
358    /// Sets the content-type header that should be sent back by the remote read operation.
359    pub fn with_override_content_type(mut self, content_type: &str) -> Self {
360        self.override_content_type = Some(content_type.into());
361        self
362    }
363
364    /// Returns the content-type header that should be sent back by the remote read operation.
365    pub fn override_content_type(&self) -> Option<&str> {
366        self.override_content_type.as_deref()
367    }
368
369    /// Set the If-Match of the option
370    pub fn with_if_match(mut self, if_match: &str) -> Self {
371        self.if_match = Some(if_match.to_string());
372        self
373    }
374
375    /// Get If-Match from option
376    pub fn if_match(&self) -> Option<&str> {
377        self.if_match.as_deref()
378    }
379
380    /// Set the If-None-Match of the option
381    pub fn with_if_none_match(mut self, if_none_match: &str) -> Self {
382        self.if_none_match = Some(if_none_match.to_string());
383        self
384    }
385
386    /// Get If-None-Match from option
387    pub fn if_none_match(&self) -> Option<&str> {
388        self.if_none_match.as_deref()
389    }
390
391    /// Set the If-Modified-Since of the option
392    pub fn with_if_modified_since(mut self, v: Timestamp) -> Self {
393        self.if_modified_since = Some(v);
394        self
395    }
396
397    /// Return the If-Modified-Since condition.
398    pub fn if_modified_since(&self) -> Option<Timestamp> {
399        self.if_modified_since
400    }
401
402    /// Set the If-Unmodified-Since of the option
403    pub fn with_if_unmodified_since(mut self, v: Timestamp) -> Self {
404        self.if_unmodified_since = Some(v);
405        self
406    }
407
408    /// Get If-Unmodified-Since from option
409    pub fn if_unmodified_since(&self) -> Option<Timestamp> {
410        self.if_unmodified_since
411    }
412
413    /// Set the version of the option
414    pub fn with_version(mut self, version: &str) -> Self {
415        self.version = Some(version.to_string());
416        self
417    }
418
419    /// Get version from option
420    pub fn version(&self) -> Option<&str> {
421        self.version.as_deref()
422    }
423
424    pub(crate) fn content_length_hint(&self) -> Option<u64> {
425        self.content_length_hint
426    }
427}
428
429/// Arguments for reader operation.
430#[derive(Debug, Clone)]
431pub struct OpReader {
432    /// The number of concurrent requests that reader can send.
433    concurrent: usize,
434    /// Request chunk size.
435    chunk: Option<usize>,
436    /// The gap size of each request.
437    gap: Option<usize>,
438    /// The maximum number of buffers that can be prefetched.
439    prefetch: usize,
440}
441
442impl Default for OpReader {
443    fn default() -> Self {
444        Self {
445            concurrent: 1,
446            chunk: None,
447            gap: None,
448            prefetch: 0,
449        }
450    }
451}
452
453impl OpReader {
454    /// Create a new `OpReader`.
455    pub fn new() -> Self {
456        Self::default()
457    }
458
459    /// Set the number of concurrent requests the reader can send.
460    pub fn with_concurrent(mut self, concurrent: usize) -> Self {
461        self.concurrent = concurrent.max(1);
462        self
463    }
464
465    /// Return the number of concurrent requests.
466    pub fn concurrent(&self) -> usize {
467        self.concurrent
468    }
469
470    /// Set the request chunk size.
471    pub fn with_chunk(mut self, chunk: usize) -> Self {
472        self.chunk = Some(chunk.max(1));
473        self
474    }
475
476    /// Return the request chunk size.
477    pub fn chunk(&self) -> Option<usize> {
478        self.chunk
479    }
480
481    /// Set the gap size.
482    ///
483    /// Set to `0` to disable merging ranges separated by a gap. Overlapping or
484    /// adjacent ranges are still merged.
485    pub fn with_gap(mut self, gap: usize) -> Self {
486        self.gap = Some(gap);
487        self
488    }
489
490    /// Return the gap size.
491    pub fn gap(&self) -> Option<usize> {
492        self.gap
493    }
494
495    /// Set the number of prefetch requests.
496    pub fn with_prefetch(mut self, prefetch: usize) -> Self {
497        self.prefetch = prefetch;
498        self
499    }
500
501    /// Return the number of prefetch requests.
502    pub fn prefetch(&self) -> usize {
503        self.prefetch
504    }
505}
506
507impl From<options::ReadOptions> for (BytesRange, OpRead, OpReader) {
508    fn from(value: options::ReadOptions) -> Self {
509        (
510            value.range,
511            OpRead {
512                if_match: value.if_match,
513                if_none_match: value.if_none_match,
514                if_modified_since: value.if_modified_since,
515                if_unmodified_since: value.if_unmodified_since,
516                override_content_type: value.override_content_type,
517                override_cache_control: value.override_cache_control,
518                override_content_disposition: value.override_content_disposition,
519                version: value.version,
520                content_length_hint: value.content_length_hint,
521            },
522            OpReader {
523                // Ensure concurrent is at least 1
524                concurrent: value.concurrent.max(1),
525                chunk: value.chunk,
526                gap: value.gap,
527                prefetch: 0,
528            },
529        )
530    }
531}
532
533impl From<options::ReaderOptions> for (OpRead, OpReader) {
534    fn from(value: options::ReaderOptions) -> Self {
535        (
536            OpRead {
537                if_match: value.if_match,
538                if_none_match: value.if_none_match,
539                if_modified_since: value.if_modified_since,
540                if_unmodified_since: value.if_unmodified_since,
541                override_content_type: None,
542                override_cache_control: None,
543                override_content_disposition: None,
544                version: value.version,
545                content_length_hint: value.content_length_hint,
546            },
547            OpReader {
548                // Ensure concurrent is at least 1
549                concurrent: value.concurrent.max(1),
550                chunk: value.chunk,
551                gap: value.gap,
552                prefetch: value.prefetch,
553            },
554        )
555    }
556}
557
558/// Arguments for `stat` operation.
559#[derive(Debug, Clone, Default)]
560pub struct OpStat {
561    if_match: Option<String>,
562    if_none_match: Option<String>,
563    if_modified_since: Option<Timestamp>,
564    if_unmodified_since: Option<Timestamp>,
565    override_content_type: Option<String>,
566    override_cache_control: Option<String>,
567    override_content_disposition: Option<String>,
568    version: Option<String>,
569}
570
571impl OpStat {
572    /// Create a new `OpStat`.
573    pub fn new() -> Self {
574        Self::default()
575    }
576
577    /// Set the If-Match of the option
578    pub fn with_if_match(mut self, if_match: &str) -> Self {
579        self.if_match = Some(if_match.to_string());
580        self
581    }
582
583    /// Get If-Match from option
584    pub fn if_match(&self) -> Option<&str> {
585        self.if_match.as_deref()
586    }
587
588    /// Set the If-None-Match of the option
589    pub fn with_if_none_match(mut self, if_none_match: &str) -> Self {
590        self.if_none_match = Some(if_none_match.to_string());
591        self
592    }
593
594    /// Get If-None-Match from option
595    pub fn if_none_match(&self) -> Option<&str> {
596        self.if_none_match.as_deref()
597    }
598
599    /// Set the If-Modified-Since of the option
600    pub fn with_if_modified_since(mut self, v: Timestamp) -> Self {
601        self.if_modified_since = Some(v);
602        self
603    }
604
605    /// Get If-Modified-Since from option
606    pub fn if_modified_since(&self) -> Option<Timestamp> {
607        self.if_modified_since
608    }
609
610    /// Set the If-Unmodified-Since of the option
611    pub fn with_if_unmodified_since(mut self, v: Timestamp) -> Self {
612        self.if_unmodified_since = Some(v);
613        self
614    }
615
616    /// Get If-Unmodified-Since from option
617    pub fn if_unmodified_since(&self) -> Option<Timestamp> {
618        self.if_unmodified_since
619    }
620
621    /// Sets the content-disposition header that should be sent back by the remote read operation.
622    pub fn with_override_content_disposition(mut self, content_disposition: &str) -> Self {
623        self.override_content_disposition = Some(content_disposition.into());
624        self
625    }
626
627    /// Returns the content-disposition header that should be sent back by the remote read
628    /// operation.
629    pub fn override_content_disposition(&self) -> Option<&str> {
630        self.override_content_disposition.as_deref()
631    }
632
633    /// Sets the cache-control header that should be sent back by the remote read operation.
634    pub fn with_override_cache_control(mut self, cache_control: &str) -> Self {
635        self.override_cache_control = Some(cache_control.into());
636        self
637    }
638
639    /// Returns the cache-control header that should be sent back by the remote read operation.
640    pub fn override_cache_control(&self) -> Option<&str> {
641        self.override_cache_control.as_deref()
642    }
643
644    /// Sets the content-type header that should be sent back by the remote read operation.
645    pub fn with_override_content_type(mut self, content_type: &str) -> Self {
646        self.override_content_type = Some(content_type.into());
647        self
648    }
649
650    /// Returns the content-type header that should be sent back by the remote read operation.
651    pub fn override_content_type(&self) -> Option<&str> {
652        self.override_content_type.as_deref()
653    }
654
655    /// Set the version of the option
656    pub fn with_version(mut self, version: &str) -> Self {
657        self.version = Some(version.to_string());
658        self
659    }
660
661    /// Get version from option
662    pub fn version(&self) -> Option<&str> {
663        self.version.as_deref()
664    }
665}
666
667impl From<options::StatOptions> for OpStat {
668    fn from(value: options::StatOptions) -> Self {
669        Self {
670            if_match: value.if_match,
671            if_none_match: value.if_none_match,
672            if_modified_since: value.if_modified_since,
673            if_unmodified_since: value.if_unmodified_since,
674            override_content_type: value.override_content_type,
675            override_cache_control: value.override_cache_control,
676            override_content_disposition: value.override_content_disposition,
677            version: value.version,
678        }
679    }
680}
681
682/// Arguments for `write` operation.
683#[derive(Debug, Clone, Default)]
684pub struct OpWrite {
685    append: bool,
686    concurrent: usize,
687    content_type: Option<String>,
688    content_disposition: Option<String>,
689    content_encoding: Option<String>,
690    cache_control: Option<String>,
691    if_match: Option<String>,
692    if_none_match: Option<String>,
693    if_not_exists: bool,
694    user_metadata: Option<HashMap<String, String>>,
695}
696
697impl OpWrite {
698    /// Create a new `OpWrite`.
699    ///
700    /// If input path is not a file path, an error will be returned.
701    pub fn new() -> Self {
702        Self::default()
703    }
704
705    /// Get the append from op.
706    ///
707    /// The append is the flag to indicate that this write operation is an append operation.
708    pub fn append(&self) -> bool {
709        self.append
710    }
711
712    /// Set the append mode of op.
713    ///
714    /// If the append mode is set, the data will be appended to the end of the file.
715    ///
716    /// # Notes
717    ///
718    /// Service could return `Unsupported` if the storage does not support append.
719    pub fn with_append(mut self, append: bool) -> Self {
720        self.append = append;
721        self
722    }
723
724    /// Get the content type from option
725    pub fn content_type(&self) -> Option<&str> {
726        self.content_type.as_deref()
727    }
728
729    /// Set the content type of option
730    pub fn with_content_type(mut self, content_type: &str) -> Self {
731        self.content_type = Some(content_type.to_string());
732        self
733    }
734
735    /// Get the content disposition from option
736    pub fn content_disposition(&self) -> Option<&str> {
737        self.content_disposition.as_deref()
738    }
739
740    /// Set the content disposition of option
741    pub fn with_content_disposition(mut self, content_disposition: &str) -> Self {
742        self.content_disposition = Some(content_disposition.to_string());
743        self
744    }
745
746    /// Get the content encoding from option
747    pub fn content_encoding(&self) -> Option<&str> {
748        self.content_encoding.as_deref()
749    }
750
751    /// Set the content encoding of option
752    pub fn with_content_encoding(mut self, content_encoding: &str) -> Self {
753        self.content_encoding = Some(content_encoding.to_string());
754        self
755    }
756
757    /// Get the cache control from option
758    pub fn cache_control(&self) -> Option<&str> {
759        self.cache_control.as_deref()
760    }
761
762    /// Set the content type of option
763    pub fn with_cache_control(mut self, cache_control: &str) -> Self {
764        self.cache_control = Some(cache_control.to_string());
765        self
766    }
767
768    /// Get the concurrent.
769    pub fn concurrent(&self) -> usize {
770        self.concurrent
771    }
772
773    /// Set the maximum concurrent write task amount.
774    pub fn with_concurrent(mut self, concurrent: usize) -> Self {
775        self.concurrent = concurrent;
776        self
777    }
778
779    /// Set the If-Match of the option
780    pub fn with_if_match(mut self, s: &str) -> Self {
781        self.if_match = Some(s.to_string());
782        self
783    }
784
785    /// Get If-Match from option
786    pub fn if_match(&self) -> Option<&str> {
787        self.if_match.as_deref()
788    }
789
790    /// Set the If-None-Match of the option
791    pub fn with_if_none_match(mut self, s: &str) -> Self {
792        self.if_none_match = Some(s.to_string());
793        self
794    }
795
796    /// Get If-None-Match from option
797    pub fn if_none_match(&self) -> Option<&str> {
798        self.if_none_match.as_deref()
799    }
800
801    /// Set the If-Not-Exist of the option
802    pub fn with_if_not_exists(mut self, b: bool) -> Self {
803        self.if_not_exists = b;
804        self
805    }
806
807    /// Get If-Not-Exist from option
808    pub fn if_not_exists(&self) -> bool {
809        self.if_not_exists
810    }
811
812    /// Set the user defined metadata of the op
813    pub fn with_user_metadata(mut self, metadata: HashMap<String, String>) -> Self {
814        self.user_metadata = Some(metadata);
815        self
816    }
817
818    /// Get the user defined metadata from the op
819    pub fn user_metadata(&self) -> Option<&HashMap<String, String>> {
820        self.user_metadata.as_ref()
821    }
822}
823
824/// Arguments for `writer` operation.
825#[derive(Debug, Clone, Default)]
826pub struct OpWriter {
827    chunk: Option<usize>,
828}
829
830impl OpWriter {
831    /// Create a new `OpWriter`.
832    pub fn new() -> Self {
833        Self::default()
834    }
835
836    /// Get the chunk from op.
837    ///
838    /// The chunk is used by service to decide the chunk size of the underlying writer.
839    pub fn chunk(&self) -> Option<usize> {
840        self.chunk
841    }
842
843    /// Set the chunk of op.
844    ///
845    /// If chunk is set, the data will be chunked by the underlying writer.
846    ///
847    /// ## NOTE
848    ///
849    /// Service could have their own minimum chunk size while perform write
850    /// operations like multipart uploads. So the chunk size may be larger than
851    /// the given buffer size.
852    pub fn with_chunk(mut self, chunk: usize) -> Self {
853        self.chunk = Some(chunk);
854        self
855    }
856}
857
858impl From<options::WriteOptions> for (OpWrite, OpWriter) {
859    fn from(value: options::WriteOptions) -> Self {
860        (
861            OpWrite {
862                append: value.append,
863                // Ensure concurrent is at least 1
864                concurrent: value.concurrent.max(1),
865                content_type: value.content_type,
866                content_disposition: value.content_disposition,
867                content_encoding: value.content_encoding,
868                cache_control: value.cache_control,
869                if_match: value.if_match,
870                if_none_match: value.if_none_match,
871                if_not_exists: value.if_not_exists,
872                user_metadata: value.user_metadata,
873            },
874            OpWriter { chunk: value.chunk },
875        )
876    }
877}
878
879/// Arguments for `copy` operation.
880#[derive(Debug, Clone, Default)]
881pub struct OpCopy {
882    if_not_exists: bool,
883    if_match: Option<String>,
884    source_version: Option<String>,
885}
886
887impl OpCopy {
888    /// Create a new `OpCopy`.
889    pub fn new() -> Self {
890        Self::default()
891    }
892
893    /// Set the if_not_exists flag for the operation.
894    ///
895    /// When set to true, the copy operation will only proceed if the destination
896    /// doesn't already exist.
897    pub fn with_if_not_exists(mut self, if_not_exists: bool) -> Self {
898        self.if_not_exists = if_not_exists;
899        self
900    }
901
902    /// Get if_not_exists flag.
903    pub fn if_not_exists(&self) -> bool {
904        self.if_not_exists
905    }
906
907    /// Set the if_match condition for the operation.
908    ///
909    /// When set, the copy operation will only proceed if the existing destination
910    /// object's ETag matches the given value.
911    pub fn with_if_match(mut self, if_match: impl Into<String>) -> Self {
912        self.if_match = Some(if_match.into());
913        self
914    }
915
916    /// Get if_match condition.
917    pub fn if_match(&self) -> Option<&str> {
918        self.if_match.as_deref()
919    }
920
921    /// Set source version for the operation.
922    ///
923    /// When set, the copy operation will copy from the specified source version.
924    pub fn with_source_version(mut self, version: impl Into<String>) -> Self {
925        self.source_version = Some(version.into());
926        self
927    }
928
929    /// Get source version from the operation.
930    pub fn source_version(&self) -> Option<&str> {
931        self.source_version.as_deref()
932    }
933}
934
935/// Arguments for `copier` operation.
936#[derive(Debug, Clone, Default)]
937pub struct OpCopier {
938    concurrent: usize,
939    chunk: Option<usize>,
940    source_content_length_hint: Option<u64>,
941}
942
943impl OpCopier {
944    /// Create a new `OpCopier`.
945    pub fn new() -> Self {
946        Self::default()
947    }
948
949    /// Set the concurrent tasks for the copier.
950    pub fn with_concurrent(mut self, concurrent: usize) -> Self {
951        self.concurrent = concurrent.max(1);
952        self
953    }
954
955    /// Get the concurrent tasks for the copier.
956    pub fn concurrent(&self) -> usize {
957        self.concurrent.max(1)
958    }
959
960    /// Set the chunk size for the copier.
961    pub fn with_chunk(mut self, chunk: usize) -> Self {
962        self.chunk = Some(chunk);
963        self
964    }
965
966    /// Get the chunk size for the copier.
967    pub fn chunk(&self) -> Option<usize> {
968        self.chunk
969    }
970
971    /// Set source content length hint for the copier.
972    pub fn with_source_content_length_hint(mut self, content_length: u64) -> Self {
973        self.source_content_length_hint = Some(content_length);
974        self
975    }
976
977    /// Get source content length hint from the copier.
978    pub fn source_content_length_hint(&self) -> Option<u64> {
979        self.source_content_length_hint
980    }
981}
982
983impl From<options::CopyOptions> for (OpCopy, OpCopier) {
984    fn from(value: options::CopyOptions) -> Self {
985        (
986            OpCopy {
987                if_not_exists: value.if_not_exists,
988                if_match: value.if_match,
989                source_version: value.source_version,
990            },
991            OpCopier {
992                concurrent: value.concurrent.max(1),
993                chunk: value.chunk,
994                source_content_length_hint: value.source_content_length_hint,
995            },
996        )
997    }
998}
999
1000/// Arguments for `rename` operation.
1001#[derive(Debug, Clone, Default)]
1002pub struct OpRename {
1003    /// Whether the rename should fail when the destination already exists.
1004    ///
1005    /// If `true`, the rename succeeds only when the destination does not exist.
1006    /// If `false`, the rename uses OpenDAL's default overwrite behavior.
1007    if_not_exists: bool,
1008}
1009
1010impl OpRename {
1011    /// Create a new `OpRename`.
1012    pub fn new() -> Self {
1013        Self::default()
1014    }
1015
1016    /// Set whether the rename should fail when the destination already exists.
1017    ///
1018    /// If `true`, the rename succeeds only when the destination does not exist.
1019    /// If `false`, the rename uses OpenDAL's default overwrite behavior.
1020    ///
1021    /// ## Service Implementation
1022    ///
1023    /// Check [`crate::Capability::rename_with_if_not_exists`] before setting this to
1024    /// `true`. A service might return `ErrorKind::Unsupported` if it cannot
1025    /// enforce the condition.
1026    pub fn with_if_not_exists(mut self, if_not_exists: bool) -> Self {
1027        self.if_not_exists = if_not_exists;
1028        self
1029    }
1030
1031    /// Return whether the rename should fail when the destination already exists.
1032    pub fn if_not_exists(&self) -> bool {
1033        self.if_not_exists
1034    }
1035}
1036
1037impl From<options::RenameOptions> for OpRename {
1038    fn from(value: options::RenameOptions) -> Self {
1039        Self {
1040            if_not_exists: value.if_not_exists,
1041        }
1042    }
1043}