Skip to main content

worker/r2/
mod.rs

1use std::{collections::HashMap, convert::TryInto, ops::Deref};
2
3pub use builder::*;
4
5use js_sys::futures::JsFuture;
6use js_sys::{JsString, Reflect, Uint8Array};
7use wasm_bindgen::{JsCast, JsValue};
8use worker_sys::{
9    FixedLengthStream as EdgeFixedLengthStream, R2Bucket as EdgeR2Bucket, R2Checksums,
10    R2MultipartUpload as EdgeR2MultipartUpload, R2Object as EdgeR2Object,
11    R2ObjectBody as EdgeR2ObjectBody, R2Objects as EdgeR2Objects,
12    R2UploadedPart as EdgeR2UploadedPart,
13};
14
15use crate::{
16    env::EnvBinding, ByteStream, Date, Error, FixedLengthStream, Headers, ResponseBody, Result,
17};
18
19mod builder;
20
21/// An instance of the R2 bucket binding.
22#[derive(Debug, Clone)]
23pub struct Bucket {
24    inner: EdgeR2Bucket,
25}
26
27impl Bucket {
28    /// Retrieves the [Object] for the given key containing only object metadata, if the key exists.
29    pub async fn head(&self, key: impl Into<String>) -> Result<Option<Object>> {
30        let head_promise = self.inner.head(key.into())?;
31        let value = JsFuture::from(head_promise).await?;
32
33        if value.is_null() {
34            return Ok(None);
35        }
36
37        Ok(Some(Object {
38            inner: ObjectInner::NoBody(value.into()),
39        }))
40    }
41
42    /// Retrieves the [Object] for the given key containing object metadata and the object body if
43    /// the key exists. In the event that a precondition specified in options fails, get() returns
44    /// an [Object] with no body.
45    pub fn get(&self, key: impl Into<String>) -> GetOptionsBuilder<'_> {
46        GetOptionsBuilder {
47            edge_bucket: &self.inner,
48            key: key.into(),
49            only_if: None,
50            range: None,
51        }
52    }
53
54    /// Stores the given `value` and metadata under the associated `key`. Once the write succeeds,
55    /// returns an [Object] containing metadata about the stored Object.
56    ///
57    /// R2 writes are strongly consistent. Once the future resolves, all subsequent read operations
58    /// will see this key value pair globally.
59    pub fn put(&self, key: impl Into<String>, value: impl Into<Data>) -> PutOptionsBuilder<'_> {
60        PutOptionsBuilder {
61            edge_bucket: &self.inner,
62            key: key.into(),
63            value: value.into(),
64            http_metadata: None,
65            custom_metadata: None,
66            checksum: None,
67            checksum_algorithm: "md5".into(),
68            only_if: None,
69        }
70    }
71
72    /// Deletes the given value and metadata under the associated key. Once the delete succeeds,
73    /// returns void.
74    ///
75    /// R2 deletes are strongly consistent. Once the Promise resolves, all subsequent read
76    /// operations will no longer see this key value pair globally.
77    pub async fn delete(&self, key: impl Into<String>) -> Result<()> {
78        let delete_promise = self.inner.delete(key.into())?;
79        JsFuture::from(delete_promise).await?;
80        Ok(())
81    }
82
83    /// Deletes the given values and metadata under the associated keys. Once
84    /// the delete succeeds, returns void.
85    ///
86    /// R2 deletes are strongly consistent. Once the Promise resolves, all
87    /// subsequent read operations will no longer see the provided key value
88    /// pairs globally.
89    ///
90    /// Up to 1000 keys may be deleted per call.
91    pub async fn delete_multiple(&self, keys: Vec<impl Deref<Target = str>>) -> Result<()> {
92        let fut: JsFuture = self
93            .inner
94            .delete_multiple(keys.into_iter().map(|key| JsValue::from(&*key)).collect())?
95            .into();
96        fut.await?;
97        Ok(())
98    }
99
100    /// Returns an [Objects] containing a list of [Objects]s contained within the bucket. By
101    /// default, returns the first 1000 entries.
102    pub fn list(&self) -> ListOptionsBuilder<'_> {
103        ListOptionsBuilder {
104            edge_bucket: &self.inner,
105            limit: None,
106            prefix: None,
107            start_after: None,
108            cursor: None,
109            delimiter: None,
110            include: None,
111        }
112    }
113
114    /// Creates a multipart upload.
115    ///
116    /// Returns a [MultipartUpload] value representing the newly created multipart upload.
117    /// Once the multipart upload has been created, the multipart upload can be immediately
118    /// interacted with globally, either through the Workers API, or through the S3 API.
119    pub fn create_multipart_upload(
120        &self,
121        key: impl Into<String>,
122    ) -> CreateMultipartUploadOptionsBuilder<'_> {
123        CreateMultipartUploadOptionsBuilder {
124            edge_bucket: &self.inner,
125            key: key.into(),
126            http_metadata: None,
127            custom_metadata: None,
128        }
129    }
130
131    /// Returns an object representing a multipart upload with the given `key` and `uploadId`.
132    ///
133    /// The operation does not perform any checks to ensure the validity of the `uploadId`,
134    /// nor does it verify the existence of a corresponding active multipart upload.
135    /// This is done to minimize latency before being able to call subsequent operations on the returned object.
136    pub fn resume_multipart_upload(
137        &self,
138        key: impl Into<String>,
139        upload_id: impl Into<String>,
140    ) -> Result<MultipartUpload> {
141        Ok(MultipartUpload {
142            inner: self
143                .inner
144                .resume_multipart_upload(key.into(), upload_id.into())?
145                .into(),
146        })
147    }
148}
149
150impl EnvBinding for Bucket {
151    const TYPE_NAME: &'static str = "R2Bucket";
152}
153
154impl JsCast for Bucket {
155    fn instanceof(val: &JsValue) -> bool {
156        val.is_instance_of::<EdgeR2Bucket>()
157    }
158
159    fn unchecked_from_js(val: JsValue) -> Self {
160        Self { inner: val.into() }
161    }
162
163    fn unchecked_from_js_ref(val: &JsValue) -> &Self {
164        unsafe { &*(val as *const JsValue as *const Self) }
165    }
166}
167
168impl From<Bucket> for JsValue {
169    fn from(bucket: Bucket) -> Self {
170        JsValue::from(bucket.inner)
171    }
172}
173
174impl AsRef<JsValue> for Bucket {
175    fn as_ref(&self) -> &JsValue {
176        &self.inner
177    }
178}
179
180/// [Object] is created when you [put](Bucket::put) an object into a [Bucket]. [Object] represents
181/// the metadata of an object based on the information provided by the uploader. Every object that
182/// you [put](Bucket::put) into a [Bucket] will have an [Object] created.
183#[derive(Debug)]
184pub struct Object {
185    inner: ObjectInner,
186}
187
188impl Object {
189    pub fn key(&self) -> String {
190        match &self.inner {
191            ObjectInner::NoBody(inner) => inner.key().unwrap(),
192            ObjectInner::Body(inner) => inner.key().unwrap(),
193        }
194    }
195
196    pub fn version(&self) -> String {
197        match &self.inner {
198            ObjectInner::NoBody(inner) => inner.version().unwrap(),
199            ObjectInner::Body(inner) => inner.version().unwrap(),
200        }
201    }
202
203    pub fn size(&self) -> u64 {
204        let size = match &self.inner {
205            ObjectInner::NoBody(inner) => inner.size().unwrap(),
206            ObjectInner::Body(inner) => inner.size().unwrap(),
207        };
208        size.round() as u64
209    }
210
211    pub fn etag(&self) -> String {
212        match &self.inner {
213            ObjectInner::NoBody(inner) => inner.etag().unwrap(),
214            ObjectInner::Body(inner) => inner.etag().unwrap(),
215        }
216    }
217
218    pub fn http_etag(&self) -> String {
219        match &self.inner {
220            ObjectInner::NoBody(inner) => inner.http_etag().unwrap(),
221            ObjectInner::Body(inner) => inner.http_etag().unwrap(),
222        }
223    }
224
225    pub fn uploaded(&self) -> Date {
226        match &self.inner {
227            ObjectInner::NoBody(inner) => inner.uploaded().unwrap(),
228            ObjectInner::Body(inner) => inner.uploaded().unwrap(),
229        }
230        .into()
231    }
232
233    pub fn http_metadata(&self) -> HttpMetadata {
234        match &self.inner {
235            ObjectInner::NoBody(inner) => inner.http_metadata().unwrap(),
236            ObjectInner::Body(inner) => inner.http_metadata().unwrap(),
237        }
238        .into()
239    }
240
241    pub fn checksum(&self) -> R2Checksums {
242        match &self.inner {
243            ObjectInner::NoBody(inner) => inner.checksums().unwrap(),
244            ObjectInner::Body(inner) => inner.checksums().unwrap(),
245        }
246        .into()
247    }
248
249    pub fn custom_metadata(&self) -> Result<HashMap<String, String>> {
250        let metadata = match &self.inner {
251            ObjectInner::NoBody(inner) => inner.custom_metadata().unwrap(),
252            ObjectInner::Body(inner) => inner.custom_metadata().unwrap(),
253        };
254
255        let keys = js_sys::Object::keys(&metadata).to_vec();
256        let mut map = HashMap::with_capacity(keys.len());
257
258        for key in keys {
259            let key = key.unchecked_into::<JsString>();
260            let value = Reflect::get(&metadata, &key)?.dyn_into::<JsString>()?;
261            map.insert(key.into(), value.into());
262        }
263
264        Ok(map)
265    }
266
267    pub fn range(&self) -> Result<Range> {
268        match &self.inner {
269            ObjectInner::NoBody(inner) => inner.range().unwrap(),
270            ObjectInner::Body(inner) => inner.range().unwrap(),
271        }
272        .try_into()
273    }
274
275    pub fn body(&self) -> Option<ObjectBody<'_>> {
276        match &self.inner {
277            ObjectInner::NoBody(_) => None,
278            ObjectInner::Body(body) => Some(ObjectBody { inner: body }),
279        }
280    }
281
282    pub fn body_used(&self) -> Option<bool> {
283        match &self.inner {
284            ObjectInner::NoBody(_) => None,
285            ObjectInner::Body(inner) => Some(inner.body_used().unwrap()),
286        }
287    }
288
289    pub fn write_http_metadata(&self, headers: Headers) -> Result<()> {
290        match &self.inner {
291            ObjectInner::NoBody(inner) => inner.write_http_metadata(headers.0)?,
292            ObjectInner::Body(inner) => inner.write_http_metadata(headers.0)?,
293        };
294
295        Ok(())
296    }
297}
298
299/// The data contained within an [Object].
300#[derive(Debug)]
301pub struct ObjectBody<'body> {
302    inner: &'body EdgeR2ObjectBody,
303}
304
305impl ObjectBody<'_> {
306    /// Reads the data in the [Object] via a [ByteStream].
307    pub fn stream(self) -> Result<ByteStream> {
308        if self.inner.body_used()? {
309            return Err(Error::BodyUsed);
310        }
311
312        let stream = self.inner.body()?;
313        let stream = wasm_streams::ReadableStream::from_raw(stream.unchecked_into());
314        Ok(ByteStream {
315            inner: stream.into_stream(),
316        })
317    }
318
319    /// Returns a [ResponseBody] containing the data in the [Object].
320    ///
321    /// This function can be used to hand off the [Object] data to the workers runtime for streaming
322    /// to the client in a [crate::Response]. This ensures that the worker does not consume CPU time
323    /// while the streaming occurs, which can be significant if instead [ObjectBody::stream] is used.
324    pub fn response_body(self) -> Result<ResponseBody> {
325        if self.inner.body_used()? {
326            return Err(Error::BodyUsed);
327        }
328
329        Ok(ResponseBody::Stream(self.inner.body()?))
330    }
331
332    pub async fn bytes(self) -> Result<Vec<u8>> {
333        let js_buffer = JsFuture::from(self.inner.array_buffer()?).await?;
334        let js_buffer = Uint8Array::new(&js_buffer);
335        let mut bytes = vec![0; js_buffer.length() as usize];
336        js_buffer.copy_to(&mut bytes);
337
338        Ok(bytes)
339    }
340
341    pub async fn text(self) -> Result<String> {
342        String::from_utf8(self.bytes().await?).map_err(|e| Error::RustError(e.to_string()))
343    }
344}
345
346/// [UploadedPart] represents a part that has been uploaded.
347/// [UploadedPart] objects are returned from [upload_part](MultipartUpload::upload_part) operations
348/// and must be passed to the [complete](MultipartUpload::complete) operation.
349#[derive(Debug)]
350pub struct UploadedPart {
351    inner: EdgeR2UploadedPart,
352}
353
354impl UploadedPart {
355    pub fn new(part_number: u16, etag: String) -> Self {
356        let obj = js_sys::Object::new();
357        Reflect::set(
358            &obj,
359            &JsValue::from_str("partNumber"),
360            &JsValue::from_f64(part_number as f64),
361        )
362        .unwrap();
363        Reflect::set(&obj, &JsValue::from_str("etag"), &JsValue::from_str(&etag)).unwrap();
364
365        let val: JsValue = obj.into();
366        Self { inner: val.into() }
367    }
368
369    pub fn part_number(&self) -> u16 {
370        self.inner.part_number().unwrap()
371    }
372
373    pub fn etag(&self) -> String {
374        self.inner.etag().unwrap()
375    }
376}
377
378#[derive(Debug)]
379pub struct MultipartUpload {
380    inner: EdgeR2MultipartUpload,
381}
382
383impl MultipartUpload {
384    /// Uploads a single part with the specified part number to this multipart upload.
385    ///
386    /// Returns an [UploadedPart] object containing the etag and part number.
387    /// These [UploadedPart] objects are required when completing the multipart upload.
388    ///
389    /// Getting hold of a value of this type does not guarantee that there is an active
390    /// underlying multipart upload corresponding to that object.
391    ///
392    /// A multipart upload can be completed or aborted at any time, either through the S3 API,
393    /// or by a parallel invocation of your Worker.
394    /// Therefore it is important to add the necessary error handling code around each operation
395    /// on the [MultipartUpload] object in case the underlying multipart upload no longer exists.
396    pub async fn upload_part(
397        &self,
398        part_number: u16,
399        value: impl Into<Data>,
400    ) -> Result<UploadedPart> {
401        let uploaded_part =
402            JsFuture::from(self.inner.upload_part(part_number, value.into().into())?).await?;
403        Ok(UploadedPart {
404            inner: uploaded_part.into(),
405        })
406    }
407
408    /// Request the upload id.
409    pub async fn upload_id(&self) -> String {
410        self.inner.upload_id().unwrap()
411    }
412
413    /// Aborts the multipart upload.
414    pub async fn abort(&self) -> Result<()> {
415        JsFuture::from(self.inner.abort()?).await?;
416        Ok(())
417    }
418
419    /// Completes the multipart upload with the given parts.
420    /// When the future is ready, the object is immediately accessible globally by any subsequent read operation.
421    pub async fn complete(
422        self,
423        uploaded_parts: impl IntoIterator<Item = UploadedPart>,
424    ) -> Result<Object> {
425        let object = JsFuture::from(
426            self.inner.complete(
427                uploaded_parts
428                    .into_iter()
429                    .map(|part| part.inner.into())
430                    .collect(),
431            )?,
432        )
433        .await?;
434        Ok(Object {
435            inner: ObjectInner::Body(object.into()),
436        })
437    }
438}
439
440/// A series of [Object]s returned by [list](Bucket::list).
441#[derive(Debug)]
442pub struct Objects {
443    inner: EdgeR2Objects,
444}
445
446impl Objects {
447    /// An [Vec] of [Object] matching the [list](Bucket::list) request.
448    pub fn objects(&self) -> Vec<Object> {
449        self.inner
450            .objects()
451            .unwrap()
452            .into_iter()
453            .map(|raw| Object {
454                inner: ObjectInner::NoBody(raw),
455            })
456            .collect()
457    }
458
459    /// If true, indicates there are more results to be retrieved for the current
460    /// [list](Bucket::list) request.
461    pub fn truncated(&self) -> bool {
462        self.inner.truncated().unwrap()
463    }
464
465    /// A token that can be passed to future [list](Bucket::list) calls to resume listing from that
466    /// point. Only present if truncated is true.
467    pub fn cursor(&self) -> Option<String> {
468        self.inner.cursor().unwrap()
469    }
470
471    /// If a delimiter has been specified, contains all prefixes between the specified prefix and
472    /// the next occurrence of the delimiter.
473    ///
474    /// For example, if no prefix is provided and the delimiter is '/', `foo/bar/baz` would return
475    /// `foo` as a delimited prefix. If `foo/` was passed as a prefix with the same structure and
476    /// delimiter, `foo/bar` would be returned as a delimited prefix.
477    pub fn delimited_prefixes(&self) -> Vec<String> {
478        self.inner
479            .delimited_prefixes()
480            .unwrap()
481            .into_iter()
482            .map(Into::into)
483            .collect()
484    }
485}
486
487#[derive(Debug, Clone)]
488pub(crate) enum ObjectInner {
489    NoBody(EdgeR2Object),
490    Body(EdgeR2ObjectBody),
491}
492
493#[derive(Debug)]
494pub enum Data {
495    ReadableStream(web_sys::ReadableStream),
496    Stream(FixedLengthStream),
497    Text(String),
498    Bytes(Vec<u8>),
499    Empty,
500}
501
502impl From<web_sys::ReadableStream> for Data {
503    fn from(stream: web_sys::ReadableStream) -> Self {
504        Data::ReadableStream(stream)
505    }
506}
507
508impl From<FixedLengthStream> for Data {
509    fn from(stream: FixedLengthStream) -> Self {
510        Data::Stream(stream)
511    }
512}
513
514impl From<String> for Data {
515    fn from(value: String) -> Self {
516        Data::Text(value)
517    }
518}
519
520impl From<Vec<u8>> for Data {
521    fn from(value: Vec<u8>) -> Self {
522        Data::Bytes(value)
523    }
524}
525
526impl From<Data> for JsValue {
527    fn from(data: Data) -> Self {
528        match data {
529            Data::ReadableStream(stream) => stream.into(),
530            Data::Stream(stream) => {
531                let stream_sys: EdgeFixedLengthStream = stream.into();
532                stream_sys.readable().into()
533            }
534            Data::Text(text) => JsString::from(text).into(),
535            Data::Bytes(bytes) => {
536                let arr = Uint8Array::new_with_length(bytes.len() as u32);
537                arr.copy_from(&bytes);
538                arr.into()
539            }
540            Data::Empty => JsValue::NULL,
541        }
542    }
543}