1use std::borrow::Cow;
2use std::collections::BTreeMap;
3
4use base64::Engine as _;
5use bytes::Bytes;
6use futures_util::StreamExt as _;
7use objectstore_types::metadata::Metadata;
8use objectstore_types::multipart::{
9 CompleteErrorDetail, CompleteRequest, CompleteSuccessResponse, InitiateResponse,
10 ListPartsResponse, UploadPartResponse,
11};
12use reqwest::Body;
13use serde::Deserialize;
14use tokio::io::AsyncRead;
15use tokio_util::io::ReaderStream;
16
17use crate::{ClientStream, ObjectKey, Session};
18
19pub use objectstore_types::multipart::CompletePart;
20pub use objectstore_types::multipart::ETag;
21pub use objectstore_types::multipart::PartInfo;
22pub use objectstore_types::multipart::PartNumber;
23pub use objectstore_types::multipart::UploadId;
24
25#[derive(Deserialize)]
26#[serde(untagged)]
27enum CompleteResponse {
28 Error { error: CompleteErrorDetail },
29 Success(CompleteSuccessResponse),
30}
31
32impl Session {
33 pub fn initiate_multipart_upload(&self) -> InitiateMultipartBuilder {
47 let metadata = Metadata {
48 expiration_policy: self.scope.usecase().expiration_policy(),
49 compression: self.scope.usecase().compression(),
50 ..Default::default()
51 };
52
53 InitiateMultipartBuilder {
54 session: self.clone(),
55 metadata,
56 key: None,
57 }
58 }
59
60 pub fn resume_multipart_upload(
66 &self,
67 key: impl Into<ObjectKey>,
68 upload_id: impl Into<String>,
69 ) -> crate::Result<MultipartUpload> {
70 Ok(MultipartUpload {
71 session: self.clone(),
72 key: key.into(),
73 upload_id: UploadId::new(upload_id.into())?,
74 })
75 }
76}
77
78#[derive(Debug)]
80pub struct InitiateMultipartBuilder {
81 session: Session,
82 metadata: Metadata,
83 key: Option<ObjectKey>,
84}
85
86impl InitiateMultipartBuilder {
87 pub fn key(mut self, key: impl Into<ObjectKey>) -> Self {
92 self.key = Some(key.into()).filter(|k| !k.is_empty());
93 self
94 }
95
96 pub fn compression(mut self, compression: impl Into<Option<crate::Compression>>) -> Self {
106 self.metadata.compression = compression.into();
107 self
108 }
109
110 pub fn expiration_policy(mut self, expiration_policy: crate::ExpirationPolicy) -> Self {
114 self.metadata.expiration_policy = expiration_policy;
115 self
116 }
117
118 pub fn content_type(mut self, content_type: impl Into<Cow<'static, str>>) -> Self {
123 self.metadata.content_type = content_type.into();
124 self
125 }
126
127 pub fn origin(mut self, origin: impl Into<String>) -> Self {
145 self.metadata.origin = Some(origin.into());
146 self
147 }
148
149 pub fn filename(mut self, filename: impl Into<String>) -> Self {
154 self.metadata.filename = Some(filename.into());
155 self
156 }
157
158 pub fn set_metadata(mut self, metadata: impl Into<BTreeMap<String, String>>) -> Self {
162 self.metadata.custom = metadata.into();
163 self
164 }
165
166 pub fn append_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
168 self.metadata.custom.insert(key.into(), value.into());
169 self
170 }
171
172 pub async fn send(self) -> crate::Result<MultipartUpload> {
174 let method = match self.key {
175 Some(_) => reqwest::Method::PUT,
176 None => reqwest::Method::POST,
177 };
178
179 let mut builder =
180 self.session
181 .multipart_request(method, None, self.key.as_deref(), None)?;
182
183 builder = builder.headers(self.metadata.to_headers("")?);
184
185 let response: InitiateResponse = builder.send().await?.error_for_status()?.json().await?;
186
187 Ok(MultipartUpload {
188 session: self.session,
189 key: response.key,
190 upload_id: response.upload_id,
191 })
192 }
193}
194
195#[derive(Debug)]
199pub struct MultipartUpload {
200 session: Session,
201 key: String,
202 upload_id: UploadId,
203}
204
205impl MultipartUpload {
206 pub fn upload_id(&self) -> &UploadId {
208 &self.upload_id
209 }
210
211 pub fn key(&self) -> &ObjectKey {
213 &self.key
214 }
215
216 pub async fn put(
223 &self,
224 body: impl Into<Bytes>,
225 part_number: u32,
226 content_md5: Option<&[u8; 16]>,
227 ) -> crate::Result<CompletePart> {
228 let bytes = body.into();
229 let content_length = bytes.len() as u64;
230 self.upload_part(bytes.into(), part_number, content_length, content_md5)
231 .await
232 }
233
234 pub async fn put_stream(
241 &self,
242 stream: ClientStream,
243 part_number: u32,
244 content_length: u64,
245 content_md5: Option<&[u8; 16]>,
246 ) -> crate::Result<CompletePart> {
247 self.upload_part(
248 Body::wrap_stream(stream),
249 part_number,
250 content_length,
251 content_md5,
252 )
253 .await
254 }
255
256 pub async fn put_read<R>(
263 &self,
264 reader: R,
265 part_number: u32,
266 content_length: u64,
267 content_md5: Option<&[u8; 16]>,
268 ) -> crate::Result<CompletePart>
269 where
270 R: AsyncRead + Send + Sync + 'static,
271 {
272 let stream = ReaderStream::new(reader).boxed();
273 self.put_stream(stream, part_number, content_length, content_md5)
274 .await
275 }
276
277 async fn upload_part(
278 &self,
279 body: Body,
280 part_number: u32,
281 content_length: u64,
282 content_md5: Option<&[u8; 16]>,
283 ) -> crate::Result<CompletePart> {
284 let part_number =
285 PartNumber::new(part_number).ok_or(crate::Error::InvalidPartNumber(part_number))?;
286
287 let mut builder = self
288 .session
289 .multipart_request(
290 reqwest::Method::PUT,
291 Some("parts"),
292 Some(&self.key),
293 Some(vec![
294 ("upload_id", self.upload_id.to_string()),
295 ("part_number", part_number.to_string()),
296 ]),
297 )?
298 .header(reqwest::header::CONTENT_LENGTH, content_length)
299 .body(body);
300
301 if let Some(md5) = content_md5 {
302 let encoded = base64::engine::general_purpose::STANDARD.encode(md5);
303 builder = builder.header("content-md5", encoded);
304 }
305
306 let response: UploadPartResponse = builder.send().await?.error_for_status()?.json().await?;
307 Ok(CompletePart {
308 part_number,
309 etag: response.etag,
310 })
311 }
312
313 pub async fn list_parts(&self) -> crate::Result<Vec<PartInfo>> {
315 let mut all_parts = Vec::new();
316 let mut marker = None;
317
318 loop {
319 let page = self.list_parts_page(None, marker).await?;
320 all_parts.extend(page.parts);
321
322 if !page.is_truncated {
323 return Ok(all_parts);
324 }
325 marker = page.next_part_number_marker;
326 if marker.is_none() {
327 return Err(crate::Error::MalformedResponse(
328 "server returned is_truncated=true but no next_part_number_marker. Please report a bug.".into(),
329 ));
330 }
331 }
332 }
333
334 async fn list_parts_page(
335 &self,
336 max_parts: Option<u32>,
337 part_number_marker: Option<PartNumber>,
338 ) -> crate::Result<ListPartsResponse> {
339 let mut params: Vec<(&str, String)> = vec![("upload_id", self.upload_id.to_string())];
340 if let Some(max) = max_parts {
341 params.push(("max_parts", max.to_string()));
342 }
343 if let Some(marker) = part_number_marker {
344 params.push(("part_number_marker", marker.to_string()));
345 }
346
347 let builder = self.session.multipart_request(
348 reqwest::Method::GET,
349 Some("parts"),
350 Some(&self.key),
351 Some(params),
352 )?;
353
354 let response: ListPartsResponse = builder.send().await?.error_for_status()?.json().await?;
355 Ok(response)
356 }
357
358 pub async fn abort(self) -> crate::Result<()> {
360 let builder = self.session.multipart_request(
361 reqwest::Method::DELETE,
362 None,
363 Some(&self.key),
364 Some(vec![("upload_id", self.upload_id.to_string())]),
365 )?;
366 builder.send().await?.error_for_status()?;
367 Ok(())
368 }
369
370 pub async fn complete(
372 self,
373 parts: impl IntoIterator<Item = CompletePart>,
374 ) -> crate::Result<ObjectKey> {
375 let mut parts: Vec<_> = parts.into_iter().collect();
376 parts.sort_by_key(|p| p.part_number);
377
378 let builder = self
379 .session
380 .multipart_request(
381 reqwest::Method::POST,
382 Some("complete"),
383 Some(&self.key),
384 Some(vec![("upload_id", self.upload_id.to_string())]),
385 )?
386 .json(&CompleteRequest { parts });
387
388 let response = builder.send().await?.error_for_status()?;
389 match response.json::<CompleteResponse>().await? {
390 CompleteResponse::Success(s) => Ok(s.key),
391 CompleteResponse::Error { error } => Err(crate::Error::MultipartComplete {
392 code: error.code,
393 message: error.message,
394 }),
395 }
396 }
397}