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