Skip to content

Commit 00e8c26

Browse files
committed
feat(phase-2): add transfer fidelity option models
1 parent dea2407 commit 00e8c26

8 files changed

Lines changed: 1193 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ http-body = "1.0"
7777
http-body-util = "0.1"
7878
sha2 = "0.10"
7979
hex = "0.4"
80+
base64 = "0.22"
8081
urlencoding = "2.1"
8182

8283
# Testing

crates/core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ rustls-pki-types.workspace = true
4040
x509-parser.workspace = true
4141
zeroize.workspace = true
4242
getrandom.workspace = true
43+
base64.workspace = true
44+
http.workspace = true
4345

4446
[dev-dependencies]
4547
tempfile.workspace = true

crates/core/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub mod retry;
2525
pub mod select;
2626
pub mod traits;
2727
pub mod transfer;
28+
pub mod transfer_options;
2829
pub mod undo;
2930
pub mod watch;
3031

@@ -77,6 +78,11 @@ pub use transfer::{
7778
TransferCancellation, TransferCandidate, TransferControls, TransferExecutor, TransferOutcome,
7879
TransferOutcomeState, TransferPlan, TransferReport, TransferSelection, TransferSummary,
7980
};
81+
pub use transfer_options::{
82+
ChecksumAlgorithm, ChecksumRequest, MetadataDirective, ObjectAttributes, ObjectChecksum,
83+
ObjectTransferMetadata, ObjectWriteEncryption, ObjectWriteOptions, SseCustomerKey,
84+
TaggingDirective, TransferCopyOptions, TransferReadOptions,
85+
};
8086
pub use undo::{
8187
UndoAction, UndoObjectResult, UndoOutcome, UndoPlan, UndoPlanItem, plan_object_undo,
8288
};

crates/core/src/traits.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ use crate::replication::{
2626
ReplicationResyncStatus,
2727
};
2828
use crate::select::SelectOptions;
29+
use crate::transfer_options::{
30+
ObjectTransferMetadata, ObjectWriteOptions, TransferCopyOptions, TransferReadOptions,
31+
};
2932

3033
/// Requested behavior for bucket creation.
3134
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
@@ -460,6 +463,30 @@ pub trait ObjectStore: Send + Sync {
460463
self.head_object(path).await
461464
}
462465

466+
/// Get metadata with checksum-mode and SSE-C support when implemented by the backend.
467+
///
468+
/// The default preserves legacy version selection and rejects advanced options explicitly.
469+
async fn head_object_with_transfer_options(
470+
&self,
471+
path: &RemotePath,
472+
options: &TransferReadOptions,
473+
) -> Result<ObjectInfo> {
474+
let legacy = options.legacy_read_options()?;
475+
self.head_object_with_options(path, &legacy).await
476+
}
477+
478+
/// Read complete transfer metadata without changing ObjectInfo's stable output contract.
479+
async fn head_object_transfer_metadata(
480+
&self,
481+
_path: &RemotePath,
482+
options: &TransferReadOptions,
483+
) -> Result<ObjectTransferMetadata> {
484+
options.validate()?;
485+
Err(Error::UnsupportedFeature(
486+
"Complete transfer metadata is not implemented by this object store".to_string(),
487+
))
488+
}
489+
463490
/// Check if a bucket exists
464491
async fn bucket_exists(&self, bucket: &str) -> Result<bool>;
465492

@@ -516,6 +543,18 @@ pub trait ObjectStore: Send + Sync {
516543
self.get_object(path).await
517544
}
518545

546+
/// Read an object with checksum-mode and SSE-C support when implemented by the backend.
547+
///
548+
/// The default preserves legacy version selection and rejects advanced options explicitly.
549+
async fn get_object_with_transfer_options(
550+
&self,
551+
path: &RemotePath,
552+
options: &TransferReadOptions,
553+
) -> Result<Vec<u8>> {
554+
let legacy = options.legacy_read_options()?;
555+
self.get_object_with_options(path, &legacy).await
556+
}
557+
519558
/// Stream current object content or an exact historical version to a writer.
520559
async fn write_object_to_with_options(
521560
&self,
@@ -534,6 +573,21 @@ pub trait ObjectStore: Send + Sync {
534573
Ok(write_len as u64)
535574
}
536575

576+
/// Stream an object with checksum-mode and SSE-C support when implemented by the backend.
577+
///
578+
/// The default preserves legacy version selection and rejects advanced options explicitly.
579+
async fn write_object_to_with_transfer_options(
580+
&self,
581+
path: &RemotePath,
582+
options: &TransferReadOptions,
583+
writer: &mut (dyn AsyncWrite + Send + Unpin),
584+
max_bytes: Option<u64>,
585+
) -> Result<u64> {
586+
let legacy = options.legacy_read_options()?;
587+
self.write_object_to_with_options(path, &legacy, writer, max_bytes)
588+
.await
589+
}
590+
537591
/// Upload object from bytes
538592
async fn put_object(
539593
&self,
@@ -543,6 +597,20 @@ pub trait ObjectStore: Send + Sync {
543597
encryption: Option<&ObjectEncryptionRequest>,
544598
) -> Result<ObjectInfo>;
545599

600+
/// Upload an object with complete transfer-fidelity options.
601+
///
602+
/// The default delegates Content-Type and managed encryption to the legacy API and rejects
603+
/// every option that cannot be represented there.
604+
async fn put_object_with_options(
605+
&self,
606+
path: &RemotePath,
607+
data: Vec<u8>,
608+
options: &ObjectWriteOptions,
609+
) -> Result<ObjectInfo> {
610+
let (content_type, encryption) = options.legacy_put_arguments()?;
611+
self.put_object(path, data, content_type, encryption).await
612+
}
613+
546614
/// Delete an object
547615
async fn delete_object(&self, path: &RemotePath) -> Result<()>;
548616

@@ -606,6 +674,21 @@ pub trait ObjectStore: Send + Sync {
606674
self.copy_object(src, dst, encryption).await
607675
}
608676

677+
/// Copy an object with explicit metadata, tags, checksum, SSE-C, and lock intent.
678+
///
679+
/// The default preserves legacy source-version and managed-encryption behavior while
680+
/// rejecting every advanced option that the original API cannot represent.
681+
async fn copy_object_with_transfer_options(
682+
&self,
683+
src: &RemotePath,
684+
dst: &RemotePath,
685+
options: &TransferCopyOptions,
686+
) -> Result<ObjectInfo> {
687+
let (legacy, encryption) = options.legacy_copy_arguments()?;
688+
self.copy_object_with_options(src, dst, &legacy, encryption)
689+
.await
690+
}
691+
609692
/// Copy an object with S3's multipart server-side copy lifecycle.
610693
///
611694
/// Backends that do not implement multipart copy fail explicitly. The
@@ -624,6 +707,25 @@ pub trait ObjectStore: Send + Sync {
624707
))
625708
}
626709

710+
/// Copy an object through the multipart lifecycle with transfer-fidelity options.
711+
///
712+
/// The default delegates only when all requested fidelity can be represented by the legacy
713+
/// multipart API. Implementations must override this method before accepting advanced fields.
714+
async fn multipart_copy_with_transfer_options(
715+
&self,
716+
src: &RemotePath,
717+
dst: &RemotePath,
718+
multipart: &MultipartCopyOptions,
719+
transfer: &TransferCopyOptions,
720+
cancellation: &MultipartCopyCancellation,
721+
on_progress: &MultipartCopyProgress<'_>,
722+
) -> Result<MultipartCopyResult> {
723+
transfer.validate_multipart_source_version(multipart.source_version_id.as_deref())?;
724+
let (_, encryption) = transfer.legacy_copy_arguments()?;
725+
self.multipart_copy(src, dst, multipart, cancellation, encryption, on_progress)
726+
.await
727+
}
728+
627729
/// Generate a presigned URL for an object
628730
async fn presign_get(&self, path: &RemotePath, expires_secs: u64) -> Result<String>;
629731

0 commit comments

Comments
 (0)