2424use async_trait:: async_trait;
2525
2626use crate :: path:: Path ;
27- use crate :: { MultipartId , PutPayload , PutResult , Result } ;
27+ use crate :: { Error , MultipartId , PutMultipartOptions , PutPayload , PutResult , Result } ;
2828
2929/// Represents a part of a file that has been successfully uploaded in a multipart upload process.
3030#[ derive( Debug , Clone ) ]
@@ -46,6 +46,30 @@ pub trait MultipartStore: Send + Sync + 'static {
4646 /// Creates a new multipart upload, returning the [`MultipartId`]
4747 async fn create_multipart ( & self , path : & Path ) -> Result < MultipartId > ;
4848
49+ /// Creates a new multipart upload with the given options, returning the [`MultipartId`]
50+ ///
51+ /// This allows callers using the low-level multipart API to provide object attributes,
52+ /// tags, or implementation-specific extensions when initiating the upload.
53+ async fn create_multipart_opts (
54+ & self ,
55+ path : & Path ,
56+ opts : PutMultipartOptions ,
57+ ) -> Result < MultipartId > {
58+ let PutMultipartOptions {
59+ tags,
60+ attributes,
61+ extensions : _,
62+ } = opts;
63+
64+ if !tags. is_empty ( ) || !attributes. is_empty ( ) {
65+ return Err ( Error :: NotSupported {
66+ source : "create_multipart_opts with non-default options" . into ( ) ,
67+ } ) ;
68+ }
69+
70+ self . create_multipart ( path) . await
71+ }
72+
4973 /// Uploads a new part with index `part_idx`
5074 ///
5175 /// `part_idx` should be an integer in the range `0..N` where `N` is the number of
@@ -82,3 +106,58 @@ pub trait MultipartStore: Send + Sync + 'static {
82106 /// Aborts a multipart upload
83107 async fn abort_multipart ( & self , path : & Path , id : & MultipartId ) -> Result < ( ) > ;
84108}
109+
110+ #[ cfg( test) ]
111+ mod tests {
112+ use super :: * ;
113+ use crate :: Extensions ;
114+
115+ struct TestMultipartStore ;
116+
117+ #[ async_trait]
118+ impl MultipartStore for TestMultipartStore {
119+ async fn create_multipart ( & self , _path : & Path ) -> Result < MultipartId > {
120+ Ok ( "test" . into ( ) )
121+ }
122+
123+ async fn put_part (
124+ & self ,
125+ _path : & Path ,
126+ _id : & MultipartId ,
127+ _part_idx : usize ,
128+ _data : PutPayload ,
129+ ) -> Result < PartId > {
130+ unreachable ! ( )
131+ }
132+
133+ async fn complete_multipart (
134+ & self ,
135+ _path : & Path ,
136+ _id : & MultipartId ,
137+ _parts : Vec < PartId > ,
138+ ) -> Result < PutResult > {
139+ unreachable ! ( )
140+ }
141+
142+ async fn abort_multipart ( & self , _path : & Path , _id : & MultipartId ) -> Result < ( ) > {
143+ unreachable ! ( )
144+ }
145+ }
146+
147+ #[ tokio:: test]
148+ async fn default_create_multipart_opts_ignores_extensions ( ) {
149+ let mut extensions = Extensions :: new ( ) ;
150+ extensions. insert ( "extension" ) ;
151+ let opts = PutMultipartOptions {
152+ extensions,
153+ ..Default :: default ( )
154+ } ;
155+
156+ let id = TestMultipartStore
157+ . create_multipart_opts ( & Path :: from ( "test" ) , opts)
158+ . await
159+ . unwrap ( ) ;
160+
161+ assert_eq ! ( id, "test" ) ;
162+ }
163+ }
0 commit comments