Skip to content

Commit 082ada2

Browse files
committed
add conditional deletes
1 parent a673ed6 commit 082ada2

File tree

14 files changed

+383
-33
lines changed

14 files changed

+383
-33
lines changed

src/aws/mod.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
use async_trait::async_trait;
3232
use futures::stream::BoxStream;
3333
use futures::{StreamExt, TryStreamExt};
34-
use reqwest::header::{HeaderName, IF_MATCH, IF_NONE_MATCH};
34+
use reqwest::header::{HeaderName, IF_MATCH, IF_NONE_MATCH, IF_UNMODIFIED_SINCE};
3535
use reqwest::{Method, StatusCode};
3636
use std::{sync::Arc, time::Duration};
3737
use url::Url;
@@ -44,9 +44,9 @@ use crate::multipart::{MultipartStore, PartId};
4444
use crate::signer::Signer;
4545
use crate::util::STRICT_ENCODE_SET;
4646
use crate::{
47-
Error, GetOptions, GetResult, ListResult, MultipartId, MultipartUpload, ObjectMeta,
48-
ObjectStore, Path, PutMode, PutMultipartOptions, PutOptions, PutPayload, PutResult, Result,
49-
UploadPart,
47+
DeleteOptions, Error, GetOptions, GetResult, ListResult, MultipartId, MultipartUpload,
48+
ObjectMeta, ObjectStore, Path, PutMode, PutMultipartOptions, PutOptions, PutPayload,
49+
PutResult, Result, UploadPart,
5050
};
5151

5252
static TAGS_HEADER: HeaderName = HeaderName::from_static("x-amz-tagging");
@@ -255,6 +255,33 @@ impl ObjectStore for AmazonS3 {
255255
Ok(())
256256
}
257257

258+
async fn delete_opts(&self, location: &Path, opts: DeleteOptions) -> Result<()> {
259+
let request = self.client.request(Method::DELETE, location);
260+
261+
// Add conditional headers if specified
262+
let request = if let Some(if_match) = &opts.if_match {
263+
request.header(&IF_MATCH, if_match)
264+
} else {
265+
request
266+
};
267+
268+
let request = if let Some(if_unmodified_since) = opts.if_unmodified_since {
269+
request.header(&IF_UNMODIFIED_SINCE, &if_unmodified_since.to_rfc2822())
270+
} else {
271+
request
272+
};
273+
274+
// AWS S3 supports versioned deletes
275+
let request = if let Some(version) = &opts.version {
276+
request.query(&[("versionId", version)])
277+
} else {
278+
request
279+
};
280+
281+
request.with_extensions(opts.extensions).send().await?;
282+
Ok(())
283+
}
284+
258285
fn delete_stream<'a>(
259286
&'a self,
260287
locations: BoxStream<'a, Result<Path>>,

src/azure/client.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ use crate::list::{PaginatedListOptions, PaginatedListResult};
2828
use crate::multipart::PartId;
2929
use crate::util::{deserialize_rfc1123, GetRange};
3030
use crate::{
31-
Attribute, Attributes, ClientOptions, GetOptions, ListResult, ObjectMeta, Path, PutMode,
32-
PutMultipartOptions, PutOptions, PutPayload, PutResult, Result, RetryConfig, TagSet,
31+
Attribute, Attributes, ClientOptions, DeleteOptions, GetOptions, ListResult, ObjectMeta, Path,
32+
PutMode, PutMultipartOptions, PutOptions, PutPayload, PutResult, Result, RetryConfig, TagSet,
3333
};
3434
use async_trait::async_trait;
3535
use base64::prelude::{BASE64_STANDARD, BASE64_STANDARD_NO_PAD};
3636
use base64::Engine;
3737
use bytes::{Buf, Bytes};
3838
use chrono::{DateTime, Utc};
3939
use http::{
40-
header::{HeaderMap, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE, IF_MATCH, IF_NONE_MATCH},
40+
header::{HeaderMap, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE, IF_MATCH, IF_NONE_MATCH, IF_UNMODIFIED_SINCE},
4141
HeaderName, Method,
4242
};
4343
use rand::Rng as _;
@@ -655,6 +655,53 @@ impl AzureClient {
655655
Ok(())
656656
}
657657

658+
/// Make an Azure Delete request with conditional options
659+
pub(crate) async fn delete_request_with_opts(
660+
&self,
661+
path: &Path,
662+
opts: DeleteOptions,
663+
) -> Result<()> {
664+
let credential = self.get_credential().await?;
665+
let url = self.config.path_url(path);
666+
667+
let sensitive = credential
668+
.as_deref()
669+
.map(|c| c.sensitive_request())
670+
.unwrap_or_default();
671+
672+
let mut builder = self.client
673+
.delete(url.as_str())
674+
.header(&DELETE_SNAPSHOTS, "include");
675+
676+
// Add conditional headers if specified
677+
if let Some(if_match) = &opts.if_match {
678+
builder = builder.header(IF_MATCH, if_match);
679+
}
680+
681+
if let Some(if_unmodified_since) = opts.if_unmodified_since {
682+
builder = builder.header(IF_UNMODIFIED_SINCE, &if_unmodified_since.to_rfc2822());
683+
}
684+
685+
// Azure supports versioned deletes via x-ms-version-id header
686+
if let Some(version) = &opts.version {
687+
builder = builder.header("x-ms-version-id", version);
688+
}
689+
690+
builder
691+
.extensions(opts.extensions)
692+
.with_azure_authorization(&credential, &self.config.account)
693+
.retryable(&self.config.retry_config)
694+
.sensitive(sensitive)
695+
.send()
696+
.await
697+
.map_err(|source| {
698+
let path = path.as_ref().into();
699+
Error::DeleteRequest { source, path }
700+
})?;
701+
702+
Ok(())
703+
}
704+
658705
fn build_bulk_delete_body(
659706
&self,
660707
boundary: &str,

src/azure/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ use crate::{
2626
multipart::{MultipartStore, PartId},
2727
path::Path,
2828
signer::Signer,
29-
GetOptions, GetResult, ListResult, MultipartId, MultipartUpload, ObjectMeta, ObjectStore,
30-
PutMultipartOptions, PutOptions, PutPayload, PutResult, Result, UploadPart,
29+
DeleteOptions, GetOptions, GetResult, ListResult, MultipartId, MultipartUpload, ObjectMeta,
30+
ObjectStore, PutMultipartOptions, PutOptions, PutPayload, PutResult, Result, UploadPart,
3131
};
3232
use async_trait::async_trait;
3333
use futures::stream::{BoxStream, StreamExt, TryStreamExt};
@@ -120,6 +120,12 @@ impl ObjectStore for MicrosoftAzure {
120120
self.client.delete_request(location, &()).await
121121
}
122122

123+
async fn delete_opts(&self, location: &Path, opts: DeleteOptions) -> Result<()> {
124+
// Azure delete_request accepts a query parameter, but we need to handle DeleteOptions differently
125+
// We'll need to update the client to have a specific method for conditional deletes
126+
self.client.delete_request_with_opts(location, opts).await
127+
}
128+
123129
fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result<ObjectMeta>> {
124130
self.client.list(prefix)
125131
}

src/chunked.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ use futures::StreamExt;
2828

2929
use crate::path::Path;
3030
use crate::{
31-
GetOptions, GetResult, GetResultPayload, ListResult, MultipartUpload, ObjectMeta, ObjectStore,
32-
PutMultipartOptions, PutOptions, PutResult,
31+
DeleteOptions, GetOptions, GetResult, GetResultPayload, ListResult, MultipartUpload,
32+
ObjectMeta, ObjectStore, PutMultipartOptions, PutOptions, PutResult,
3333
};
3434
use crate::{PutPayload, Result};
3535

@@ -150,6 +150,10 @@ impl ObjectStore for ChunkedStore {
150150
self.inner.delete(location).await
151151
}
152152

153+
async fn delete_opts(&self, location: &Path, opts: DeleteOptions) -> Result<()> {
154+
self.inner.delete_opts(location, opts).await
155+
}
156+
153157
fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result<ObjectMeta>> {
154158
self.inner.list(prefix)
155159
}

src/gcp/client.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ use crate::multipart::PartId;
3232
use crate::path::Path;
3333
use crate::util::hex_encode;
3434
use crate::{
35-
Attribute, Attributes, ClientOptions, GetOptions, MultipartId, PutMode, PutMultipartOptions,
36-
PutOptions, PutPayload, PutResult, Result, RetryConfig,
35+
Attribute, Attributes, ClientOptions, DeleteOptions, GetOptions, MultipartId, PutMode,
36+
PutMultipartOptions, PutOptions, PutPayload, PutResult, Result, RetryConfig,
3737
};
3838
use async_trait::async_trait;
3939
use base64::prelude::BASE64_STANDARD;
@@ -563,6 +563,27 @@ impl GoogleCloudStorageClient {
563563
Ok(())
564564
}
565565

566+
pub(crate) async fn delete_request_with_opts(&self, path: &Path, opts: DeleteOptions) -> Result<()> {
567+
let mut request = self.request(Method::DELETE, path);
568+
569+
// Add conditional headers if specified
570+
if let Some(if_match) = &opts.if_match {
571+
request = request.header(&HeaderName::from_static("if-match"), if_match);
572+
}
573+
574+
if let Some(if_unmodified_since) = opts.if_unmodified_since {
575+
request = request.header(&HeaderName::from_static("if-unmodified-since"), &if_unmodified_since.to_rfc2822());
576+
}
577+
578+
// GCS supports versioned deletes via generation parameter
579+
if let Some(version) = &opts.version {
580+
request = request.query(&[("generation", version)]);
581+
}
582+
583+
request.with_extensions(opts.extensions).send().await?;
584+
Ok(())
585+
}
586+
566587
/// Perform a copy request <https://cloud.google.com/storage/docs/xml-api/put-object-copy>
567588
pub(crate) async fn copy_request(
568589
&self,

src/gcp/mod.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ use crate::client::CredentialProvider;
4141
use crate::gcp::credential::GCSAuthorizer;
4242
use crate::signer::Signer;
4343
use crate::{
44-
multipart::PartId, path::Path, GetOptions, GetResult, ListResult, MultipartId, MultipartUpload,
45-
ObjectMeta, ObjectStore, PutMultipartOptions, PutOptions, PutPayload, PutResult, Result,
46-
UploadPart,
44+
multipart::PartId, path::Path, DeleteOptions, GetOptions, GetResult, ListResult, MultipartId,
45+
MultipartUpload, ObjectMeta, ObjectStore, PutMultipartOptions, PutOptions, PutPayload,
46+
PutResult, Result, UploadPart,
4747
};
4848
use async_trait::async_trait;
4949
use client::GoogleCloudStorageClient;
@@ -184,6 +184,10 @@ impl ObjectStore for GoogleCloudStorage {
184184
self.client.delete_request(location).await
185185
}
186186

187+
async fn delete_opts(&self, location: &Path, opts: DeleteOptions) -> Result<()> {
188+
self.client.delete_request_with_opts(location, opts).await
189+
}
190+
187191
fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result<ObjectMeta>> {
188192
self.client.list(prefix)
189193
}

src/http/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ use crate::client::{http_connector, HttpConnector};
4545
use crate::http::client::Client;
4646
use crate::path::Path;
4747
use crate::{
48-
ClientConfigKey, ClientOptions, GetOptions, GetResult, ListResult, MultipartUpload, ObjectMeta,
49-
ObjectStore, PutMode, PutMultipartOptions, PutOptions, PutPayload, PutResult, Result,
50-
RetryConfig,
48+
ClientConfigKey, ClientOptions, DeleteOptions, GetOptions, GetResult, ListResult,
49+
MultipartUpload, ObjectMeta, ObjectStore, PutMode, PutMultipartOptions, PutOptions, PutPayload,
50+
PutResult, Result, RetryConfig,
5151
};
5252

5353
mod client;
@@ -136,6 +136,11 @@ impl ObjectStore for HttpStore {
136136
self.client.delete(location).await
137137
}
138138

139+
async fn delete_opts(&self, _location: &Path, _opts: DeleteOptions) -> Result<()> {
140+
// HTTP/WebDAV protocol doesn't support conditional deletes
141+
Err(crate::Error::NotImplemented)
142+
}
143+
139144
fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result<ObjectMeta>> {
140145
let prefix_len = prefix.map(|p| p.as_ref().len()).unwrap_or_default();
141146
let prefix = prefix.cloned();

0 commit comments

Comments
 (0)