Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,9 @@ let view = client.views().create(&CreateView {
s3_locks_retention_mode: None,
}).await?;

client.views().delete(view.id).await?;
// `force` is the endpoint's optional `force` query parameter; `None` omits it.
client.views().delete(view.id, None).await?;
client.views().delete(view.id, Some(true)).await?;
```

### View Policies
Expand Down
32 changes: 31 additions & 1 deletion src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,25 @@ macro_rules! crud {
}
}
};
// Full CRUD minus `delete`: for resources whose DELETE takes extra
// parameters and so spells out its own `delete` in a separate `impl`.
(nodelete $Handle:ident, $Resource:ty, $Create:ty, $Update:ty, $path:expr
$(, filters = $Params:ty)?) => {
pub struct $Handle<'c>(pub(crate) &'c VastClient);
impl<'c> $Handle<'c> {
list_methods!($Resource, $path);
$( filtered_list_methods!($Resource, $Params, $path); )?
pub async fn get(&self, id: u64) -> Result<$Resource> {
self.0.get(&format!("{}{id}/", $path)).await
}
pub async fn create(&self, body: &$Create) -> Result<$Resource> {
self.0.post($path, body).await
}
pub async fn update(&self, id: u64, body: &$Update) -> Result<$Resource> {
self.0.patch(&format!("{}{id}/", $path), body).await
}
}
};
// Create-only (no update): used for snapshots-style resources that don't update.
(cd $Handle:ident, $Resource:ty, $Create:ty, $path:expr
$(, filters = $Params:ty)?) => {
Expand Down Expand Up @@ -802,14 +821,25 @@ list_params! {
}

crud!(
Views,
nodelete Views,
View,
CreateView,
UpdateView,
"views/",
filters = ListViewsParams
);

impl<'c> Views<'c> {
/// `DELETE /views/{id}/` — remove a view.
pub async fn delete(&self, id: u64, force: Option<bool>) -> Result<()> {
let path = format!("views/{id}/");
match force {
Some(force) => self.0.delete_with_query(&path, &[("force", force)]).await,
None => self.0.delete(&path).await,
}
}
}

// ===========================================================================
// View policies
// ===========================================================================
Expand Down
26 changes: 22 additions & 4 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,26 @@ impl VastClient {
}

pub(crate) async fn delete(&self, path: &str) -> Result<()> {
self.send_no_body(Method::DELETE, path, None::<&()>).await
self.send_no_body(Method::DELETE, path, None::<&()>, None::<&()>)
.await
}

pub(crate) async fn delete_with_query<Q: Serialize + ?Sized>(
&self,
path: &str,
query: &Q,
) -> Result<()> {
self.send_no_body(Method::DELETE, path, Some(query), None::<&()>)
.await
}

pub(crate) async fn delete_with_body<B: Serialize + ?Sized>(
&self,
path: &str,
body: &B,
) -> Result<()> {
self.send_no_body(Method::DELETE, path, Some(body)).await
self.send_no_body(Method::DELETE, path, None::<&()>, Some(body))
.await
}

async fn send<T, Q, B>(
Expand Down Expand Up @@ -253,11 +264,18 @@ impl VastClient {
}
}

async fn send_no_body<B>(&self, method: Method, path: &str, body: Option<&B>) -> Result<()>
async fn send_no_body<Q, B>(
&self,
method: Method,
path: &str,
query: Option<&Q>,
body: Option<&B>,
) -> Result<()>
where
Q: Serialize + ?Sized,
B: Serialize + ?Sized,
{
let resp = self.request(method, path, None::<&()>, body).await?;
let resp = self.request(method, path, query, body).await?;
if resp.status().is_success() {
Ok(())
} else {
Expand Down
33 changes: 32 additions & 1 deletion tests/client_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ async fn expired_jwt_403_refreshes_on_non_idempotent_methods() {
setup_credentials_with_retry(&server, 3)
.await
.views()
.delete(7)
.delete(7, None)
.await
.expect("DELETE should refresh and replay after a token-rejection 403");

Expand Down Expand Up @@ -849,6 +849,37 @@ async fn views_list_and_create() {
assert_eq!(created.bucket, "x");
}

#[tokio::test]
async fn views_delete_sends_force_query_param_only_when_set() {
let (server, client) = setup("t").await;
Mock::given(method("DELETE"))
.and(path("/api/views/1/"))
.and(query_param_is_missing("force"))
.respond_with(ResponseTemplate::new(204))
.expect(1)
.mount(&server)
.await;
Mock::given(method("DELETE"))
.and(path("/api/views/2/"))
.and(query_param("force", "true"))
.respond_with(ResponseTemplate::new(204))
.expect(1)
.mount(&server)
.await;
Mock::given(method("DELETE"))
.and(path("/api/views/3/"))
.and(query_param("force", "false"))
.respond_with(ResponseTemplate::new(204))
.expect(1)
.mount(&server)
.await;

client.views().delete(1, None).await.unwrap();
client.views().delete(2, Some(true)).await.unwrap();
client.views().delete(3, Some(false)).await.unwrap();
server.verify().await;
}

#[tokio::test]
async fn quotas_list_handles_nullable_limits() {
let (server, client) = setup("t").await;
Expand Down
2 changes: 1 addition & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ async fn views_crud() {
// DELETE view record (does not remove the backing directory).
client
.views()
.delete(view_id)
.delete(view_id, None)
.await
.expect("views().delete() failed");

Expand Down
Loading