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    pub fn with_gap(mut self, gap: usize) -> Self {
483        self.gap = Some(gap.max(1));
484        self
485    }
486
487    /// Return the gap size.
488    pub fn gap(&self) -> Option<usize> {
489        self.gap
490    }
491
492    /// Set the number of prefetch requests.
493    pub fn with_prefetch(mut self, prefetch: usize) -> Self {
494        self.prefetch = prefetch;
495        self
496    }
497
498    /// Return the number of prefetch requests.
499    pub fn prefetch(&self) -> usize {
500        self.prefetch
501    }
502}
503
504impl From<options::ReadOptions> for (BytesRange, OpRead, OpReader) {
505    fn from(value: options::ReadOptions) -> Self {
506        (
507            value.range,
508            OpRead {
509                if_match: value.if_match,
510                if_none_match: value.if_none_match,
511                if_modified_since: value.if_modified_since,
512                if_unmodified_since: value.if_unmodified_since,
513                override_content_type: value.override_content_type,
514                override_cache_control: value.override_cache_control,
515                override_content_disposition: value.override_content_disposition,
516                version: value.version,
517                content_length_hint: value.content_length_hint,
518            },
519            OpReader {
520                // Ensure concurrent is at least 1
521                concurrent: value.concurrent.max(1),
522                chunk: value.chunk,
523                gap: value.gap,
524                prefetch: 0,
525            },
526        )
527    }
528}
529
530impl From<options::ReaderOptions> for (OpRead, OpReader) {
531    fn from(value: options::ReaderOptions) -> Self {
532        (
533            OpRead {
534                if_match: value.if_match,
535                if_none_match: value.if_none_match,
536                if_modified_since: value.if_modified_since,
537                if_unmodified_since: value.if_unmodified_since,
538                override_content_type: None,
539                override_cache_control: None,
540                override_content_disposition: None,
541                version: value.version,
542                content_length_hint: value.content_length_hint,
543            },
544            OpReader {
545                // Ensure concurrent is at least 1
546                concurrent: value.concurrent.max(1),
547                chunk: value.chunk,
548                gap: value.gap,
549                prefetch: value.prefetch,
550            },
551        )
552    }
553}
554
555/// Arguments for `stat` operation.
556#[derive(Debug, Clone, Default)]
557pub struct OpStat {
558    if_match: Option<String>,
559    if_none_match: Option<String>,
560    if_modified_since: Option<Timestamp>,
561    if_unmodified_since: Option<Timestamp>,
562    override_content_type: Option<String>,
563    override_cache_control: Option<String>,
564    override_content_disposition: Option<String>,
565    version: Option<String>,
566}
567
568impl OpStat {
569    /// Create a new `OpStat`.
570    pub fn new() -> Self {
571        Self::default()
572    }
573
574    /// Set the If-Match of the option
575    pub fn with_if_match(mut self, if_match: &str) -> Self {
576        self.if_match = Some(if_match.to_string());
577        self
578    }
579
580    /// Get If-Match from option
581    pub fn if_match(&self) -> Option<&str> {
582        self.if_match.as_deref()
583    }
584
585    /// Set the If-None-Match of the option
586    pub fn with_if_none_match(mut self, if_none_match: &str) -> Self {
587        self.if_none_match = Some(if_none_match.to_string());
588        self
589    }
590
591    /// Get If-None-Match from option
592    pub fn if_none_match(&self) -> Option<&str> {
593        self.if_none_match.as_deref()
594    }
595
596    /// Set the If-Modified-Since of the option
597    pub fn with_if_modified_since(mut self, v: Timestamp) -> Self {
598        self.if_modified_since = Some(v);
599        self
600    }
601
602    /// Get If-Modified-Since from option
603    pub fn if_modified_since(&self) -> Option<Timestamp> {
604        self.if_modified_since
605    }
606
607    /// Set the If-Unmodified-Since of the option
608    pub fn with_if_unmodified_since(mut self, v: Timestamp) -> Self {
609        self.if_unmodified_since = Some(v);
610        self
611    }
612
613    /// Get If-Unmodified-Since from option
614    pub fn if_unmodified_since(&self) -> Option<Timestamp> {
615        self.if_unmodified_since
616    }
617
618    /// Sets the content-disposition header that should be sent back by the remote read operation.
619    pub fn with_override_content_disposition(mut self, content_disposition: &str) -> Self {
620        self.override_content_disposition = Some(content_disposition.into());
621        self
622    }
623
624    /// Returns the content-disposition header that should be sent back by the remote read
625    /// operation.
626    pub fn override_content_disposition(&self) -> Option<&str> {
627        self.override_content_disposition.as_deref()
628    }
629
630    /// Sets the cache-control header that should be sent back by the remote read operation.
631    pub fn with_override_cache_control(mut self, cache_control: &str) -> Self {
632        self.override_cache_control = Some(cache_control.into());
633        self
634    }
635
636    /// Returns the cache-control header that should be sent back by the remote read operation.
637    pub fn override_cache_control(&self) -> Option<&str> {
638        self.override_cache_control.as_deref()
639    }
640
641    /// Sets the content-type header that should be sent back by the remote read operation.
642    pub fn with_override_content_type(mut self, content_type: &str) -> Self {
643        self.override_content_type = Some(content_type.into());
644        self
645    }
646
647    /// Returns the content-type header that should be sent back by the remote read operation.
648    pub fn override_content_type(&self) -> Option<&str> {
649        self.override_content_type.as_deref()
650    }
651
652    /// Set the version of the option
653    pub fn with_version(mut self, version: &str) -> Self {
654        self.version = Some(version.to_string());
655        self
656    }
657
658    /// Get version from option
659    pub fn version(&self) -> Option<&str> {
660        self.version.as_deref()
661    }
662}
663
664impl From<options::StatOptions> for OpStat {
665    fn from(value: options::StatOptions) -> Self {
666        Self {
667            if_match: value.if_match,
668            if_none_match: value.if_none_match,
669            if_modified_since: value.if_modified_since,
670            if_unmodified_since: value.if_unmodified_since,
671            override_content_type: value.override_content_type,
672            override_cache_control: value.override_cache_control,
673            override_content_disposition: value.override_content_disposition,
674            version: value.version,
675        }
676    }
677}
678
679/// Arguments for `write` operation.
680#[derive(Debug, Clone, Default)]
681pub struct OpWrite {
682    append: bool,
683    concurrent: usize,
684    content_type: Option<String>,
685    content_disposition: Option<String>,
686    content_encoding: Option<String>,
687    cache_control: Option<String>,
688    if_match: Option<String>,
689    if_none_match: Option<String>,
690    if_not_exists: bool,
691    user_metadata: Option<HashMap<String, String>>,
692}
693
694impl OpWrite {
695    /// Create a new `OpWrite`.
696    ///
697    /// If input path is not a file path, an error will be returned.
698    pub fn new() -> Self {
699        Self::default()
700    }
701
702    /// Get the append from op.
703    ///
704    /// The append is the flag to indicate that this write operation is an append operation.
705    pub fn append(&self) -> bool {
706        self.append
707    }
708
709    /// Set the append mode of op.
710    ///
711    /// If the append mode is set, the data will be appended to the end of the file.
712    ///
713    /// # Notes
714    ///
715    /// Service could return `Unsupported` if the storage does not support append.
716    pub fn with_append(mut self, append: bool) -> Self {
717        self.append = append;
718        self
719    }
720
721    /// Get the content type from option
722    pub fn content_type(&self) -> Option<&str> {
723        self.content_type.as_deref()
724    }
725
726    /// Set the content type of option
727    pub fn with_content_type(mut self, content_type: &str) -> Self {
728        self.content_type = Some(content_type.to_string());
729        self
730    }
731
732    /// Get the content disposition from option
733    pub fn content_disposition(&self) -> Option<&str> {
734        self.content_disposition.as_deref()
735    }
736
737    /// Set the content disposition of option
738    pub fn with_content_disposition(mut self, content_disposition: &str) -> Self {
739        self.content_disposition = Some(content_disposition.to_string());
740        self
741    }
742
743    /// Get the content encoding from option
744    pub fn content_encoding(&self) -> Option<&str> {
745        self.content_encoding.as_deref()
746    }
747
748    /// Set the content encoding of option
749    pub fn with_content_encoding(mut self, content_encoding: &str) -> Self {
750        self.content_encoding = Some(content_encoding.to_string());
751        self
752    }
753
754    /// Get the cache control from option
755    pub fn cache_control(&self) -> Option<&str> {
756        self.cache_control.as_deref()
757    }
758
759    /// Set the content type of option
760    pub fn with_cache_control(mut self, cache_control: &str) -> Self {
761        self.cache_control = Some(cache_control.to_string());
762        self
763    }
764
765    /// Get the concurrent.
766    pub fn concurrent(&self) -> usize {
767        self.concurrent
768    }
769
770    /// Set the maximum concurrent write task amount.
771    pub fn with_concurrent(mut self, concurrent: usize) -> Self {
772        self.concurrent = concurrent;
773        self
774    }
775
776    /// Set the If-Match of the option
777    pub fn with_if_match(mut self, s: &str) -> Self {
778        self.if_match = Some(s.to_string());
779        self
780    }
781
782    /// Get If-Match from option
783    pub fn if_match(&self) -> Option<&str> {
784        self.if_match.as_deref()
785    }
786
787    /// Set the If-None-Match of the option
788    pub fn with_if_none_match(mut self, s: &str) -> Self {
789        self.if_none_match = Some(s.to_string());
790        self
791    }
792
793    /// Get If-None-Match from option
794    pub fn if_none_match(&self) -> Option<&str> {
795        self.if_none_match.as_deref()
796    }
797
798    /// Set the If-Not-Exist of the option
799    pub fn with_if_not_exists(mut self, b: bool) -> Self {
800        self.if_not_exists = b;
801        self
802    }
803
804    /// Get If-Not-Exist from option
805    pub fn if_not_exists(&self) -> bool {
806        self.if_not_exists
807    }
808
809    /// Set the user defined metadata of the op
810    pub fn with_user_metadata(mut self, metadata: HashMap<String, String>) -> Self {
811        self.user_metadata = Some(metadata);
812        self
813    }
814
815    /// Get the user defined metadata from the op
816    pub fn user_metadata(&self) -> Option<&HashMap<String, String>> {
817        self.user_metadata.as_ref()
818    }
819}
820
821/// Arguments for `writer` operation.
822#[derive(Debug, Clone, Default)]
823pub struct OpWriter {
824    chunk: Option<usize>,
825}
826
827impl OpWriter {
828    /// Create a new `OpWriter`.
829    pub fn new() -> Self {
830        Self::default()
831    }
832
833    /// Get the chunk from op.
834    ///
835    /// The chunk is used by service to decide the chunk size of the underlying writer.
836    pub fn chunk(&self) -> Option<usize> {
837        self.chunk
838    }
839
840    /// Set the chunk of op.
841    ///
842    /// If chunk is set, the data will be chunked by the underlying writer.
843    ///
844    /// ## NOTE
845    ///
846    /// Service could have their own minimum chunk size while perform write
847    /// operations like multipart uploads. So the chunk size may be larger than
848    /// the given buffer size.
849    pub fn with_chunk(mut self, chunk: usize) -> Self {
850        self.chunk = Some(chunk);
851        self
852    }
853}
854
855impl From<options::WriteOptions> for (OpWrite, OpWriter) {
856    fn from(value: options::WriteOptions) -> Self {
857        (
858            OpWrite {
859                append: value.append,
860                // Ensure concurrent is at least 1
861                concurrent: value.concurrent.max(1),
862                content_type: value.content_type,
863                content_disposition: value.content_disposition,
864                content_encoding: value.content_encoding,
865                cache_control: value.cache_control,
866                if_match: value.if_match,
867                if_none_match: value.if_none_match,
868                if_not_exists: value.if_not_exists,
869                user_metadata: value.user_metadata,
870            },
871            OpWriter { chunk: value.chunk },
872        )
873    }
874}
875
876/// Arguments for `copy` operation.
877#[derive(Debug, Clone, Default)]
878pub struct OpCopy {
879    if_not_exists: bool,
880    if_match: Option<String>,
881    source_version: Option<String>,
882}
883
884impl OpCopy {
885    /// Create a new `OpCopy`.
886    pub fn new() -> Self {
887        Self::default()
888    }
889
890    /// Set the if_not_exists flag for the operation.
891    ///
892    /// When set to true, the copy operation will only proceed if the destination
893    /// doesn't already exist.
894    pub fn with_if_not_exists(mut self, if_not_exists: bool) -> Self {
895        self.if_not_exists = if_not_exists;
896        self
897    }
898
899    /// Get if_not_exists flag.
900    pub fn if_not_exists(&self) -> bool {
901        self.if_not_exists
902    }
903
904    /// Set the if_match condition for the operation.
905    ///
906    /// When set, the copy operation will only proceed if the existing destination
907    /// object's ETag matches the given value.
908    pub fn with_if_match(mut self, if_match: impl Into<String>) -> Self {
909        self.if_match = Some(if_match.into());
910        self
911    }
912
913    /// Get if_match condition.
914    pub fn if_match(&self) -> Option<&str> {
915        self.if_match.as_deref()
916    }
917
918    /// Set source version for the operation.
919    ///
920    /// When set, the copy operation will copy from the specified source version.
921    pub fn with_source_version(mut self, version: impl Into<String>) -> Self {
922        self.source_version = Some(version.into());
923        self
924    }
925
926    /// Get source version from the operation.
927    pub fn source_version(&self) -> Option<&str> {
928        self.source_version.as_deref()
929    }
930}
931
932/// Arguments for `copier` operation.
933#[derive(Debug, Clone, Default)]
934pub struct OpCopier {
935    concurrent: usize,
936    chunk: Option<usize>,
937    source_content_length_hint: Option<u64>,
938}
939
940impl OpCopier {
941    /// Create a new `OpCopier`.
942    pub fn new() -> Self {
943        Self::default()
944    }
945
946    /// Set the concurrent tasks for the copier.
947    pub fn with_concurrent(mut self, concurrent: usize) -> Self {
948        self.concurrent = concurrent.max(1);
949        self
950    }
951
952    /// Get the concurrent tasks for the copier.
953    pub fn concurrent(&self) -> usize {
954        self.concurrent.max(1)
955    }
956
957    /// Set the chunk size for the copier.
958    pub fn with_chunk(mut self, chunk: usize) -> Self {
959        self.chunk = Some(chunk);
960        self
961    }
962
963    /// Get the chunk size for the copier.
964    pub fn chunk(&self) -> Option<usize> {
965        self.chunk
966    }
967
968    /// Set source content length hint for the copier.
969    pub fn with_source_content_length_hint(mut self, content_length: u64) -> Self {
970        self.source_content_length_hint = Some(content_length);
971        self
972    }
973
974    /// Get source content length hint from the copier.
975    pub fn source_content_length_hint(&self) -> Option<u64> {
976        self.source_content_length_hint
977    }
978}
979
980impl From<options::CopyOptions> for (OpCopy, OpCopier) {
981    fn from(value: options::CopyOptions) -> Self {
982        (
983            OpCopy {
984                if_not_exists: value.if_not_exists,
985                if_match: value.if_match,
986                source_version: value.source_version,
987            },
988            OpCopier {
989                concurrent: value.concurrent.max(1),
990                chunk: value.chunk,
991                source_content_length_hint: value.source_content_length_hint,
992            },
993        )
994    }
995}
996
997/// Arguments for `rename` operation.
998#[derive(Debug, Clone, Default)]
999pub struct OpRename {
1000    /// Whether the rename should fail when the destination already exists.
1001    ///
1002    /// If `true`, the rename succeeds only when the destination does not exist.
1003    /// If `false`, the rename uses OpenDAL's default overwrite behavior.
1004    if_not_exists: bool,
1005}
1006
1007impl OpRename {
1008    /// Create a new `OpRename`.
1009    pub fn new() -> Self {
1010        Self::default()
1011    }
1012
1013    /// Set whether the rename should fail when the destination already exists.
1014    ///
1015    /// If `true`, the rename succeeds only when the destination does not exist.
1016    /// If `false`, the rename uses OpenDAL's default overwrite behavior.
1017    ///
1018    /// ## Service Implementation
1019    ///
1020    /// Check [`crate::Capability::rename_with_if_not_exists`] before setting this to
1021    /// `true`. A service might return `ErrorKind::Unsupported` if it cannot
1022    /// enforce the condition.
1023    pub fn with_if_not_exists(mut self, if_not_exists: bool) -> Self {
1024        self.if_not_exists = if_not_exists;
1025        self
1026    }
1027
1028    /// Return whether the rename should fail when the destination already exists.
1029    pub fn if_not_exists(&self) -> bool {
1030        self.if_not_exists
1031    }
1032}
1033
1034impl From<options::RenameOptions> for OpRename {
1035    fn from(value: options::RenameOptions) -> Self {
1036        Self {
1037            if_not_exists: value.if_not_exists,
1038        }
1039    }
1040}