diff --git a/worker/src/r2/builder.rs b/worker/src/r2/builder.rs index e339695ff..e9f6f3895 100644 --- a/worker/src/r2/builder.rs +++ b/worker/src/r2/builder.rs @@ -65,7 +65,7 @@ impl GetOptionsBuilder<'_> { } } -/// You can pass an [Conditional] object to [GetOptionsBuilder]. If the condition check fails, +/// You can pass an [Conditional] object to [GetOptionsBuilder] or [PutOptionsBuilder]. If the condition check fails, /// the body will not be returned. This will make [get](crate::r2::Bucket::get) have lower latency. /// /// For more information about conditional requests, refer to [RFC 7232](https://datatracker.ietf.org/doc/html/rfc7232). @@ -174,6 +174,7 @@ pub struct PutOptionsBuilder<'bucket> { pub(crate) custom_metadata: Option>, pub(crate) checksum: Option>, pub(crate) checksum_algorithm: String, + pub(crate) only_if: Option, } impl PutOptionsBuilder<'_> { @@ -220,8 +221,17 @@ impl PutOptionsBuilder<'_> { self.checksum_set("sha512", bytes) } + /// Specifies that the object should only be returned given satisfaction of certain conditions + /// in the [Conditional]. Refer to [Conditional operations](https://developers.cloudflare.com/r2/runtime-apis/#conditional-operations). + pub fn only_if(mut self, only_if: Conditional) -> Self { + self.only_if = Some(only_if); + self + } + /// Executes the PUT operation on the R2 bucket. - pub async fn execute(self) -> Result { + /// + /// If the condition check fails, `None` will be returned instead of an [`Object`]. + pub async fn execute(self) -> Result> { let value: JsValue = self.value.into(); let name: String = self.key; @@ -229,6 +239,7 @@ impl PutOptionsBuilder<'_> { name, value, js_object! { + "onlyIf" => self.only_if.map(JsObject::from), "httpMetadata" => self.http_metadata.map(JsObject::from), "customMetadata" => match self.custom_metadata { Some(metadata) => { @@ -248,14 +259,21 @@ impl PutOptionsBuilder<'_> { } .into(), )?; - let res: EdgeR2Object = JsFuture::from(put_promise).await?.into(); + + let value = JsFuture::from(put_promise).await?; + + if value.is_null() { + return Ok(None); + } + + let res: EdgeR2Object = value.into(); let inner = if JsString::from("bodyUsed").js_in(&res) { ObjectInner::Body(res.unchecked_into()) } else { ObjectInner::NoBody(res) }; - Ok(Object { inner }) + Ok(Some(Object { inner })) } } @@ -361,6 +379,7 @@ pub struct ListOptionsBuilder<'bucket> { pub(crate) edge_bucket: &'bucket EdgeR2Bucket, pub(crate) limit: Option, pub(crate) prefix: Option, + pub(crate) start_after: Option, pub(crate) cursor: Option, pub(crate) delimiter: Option, pub(crate) include: Option>, @@ -379,6 +398,12 @@ impl ListOptionsBuilder<'_> { self } + /// Start listing after this key. + pub fn start_after(mut self, start_after: impl Into) -> Self { + self.start_after = Some(start_after.into()); + self + } + /// An opaque token that indicates where to continue listing objects from. A cursor can be /// retrieved from a previous list operation. pub fn cursor(mut self, cursor: impl Into) -> Self { @@ -419,6 +444,7 @@ impl ListOptionsBuilder<'_> { js_object! { "limit" => self.limit, "prefix" => self.prefix, + "startAfter" => self.start_after.map(JsValue::from), "cursor" => self.cursor, "delimiter" => self.delimiter, "include" => self diff --git a/worker/src/r2/mod.rs b/worker/src/r2/mod.rs index 8b1dab9ee..08ec48384 100644 --- a/worker/src/r2/mod.rs +++ b/worker/src/r2/mod.rs @@ -65,6 +65,7 @@ impl Bucket { custom_metadata: None, checksum: None, checksum_algorithm: "md5".into(), + only_if: None, } } @@ -103,6 +104,7 @@ impl Bucket { edge_bucket: &self.inner, limit: None, prefix: None, + start_after: None, cursor: None, delimiter: None, include: None,