diff --git a/services/blob/src/http/handlers/holders.rs b/services/blob/src/http/handlers/holders.rs --- a/services/blob/src/http/handlers/holders.rs +++ b/services/blob/src/http/handlers/holders.rs @@ -1,4 +1,4 @@ -use actix_web::error::ErrorBadRequest; +use actix_web::error::{ErrorBadRequest, ErrorNotImplemented}; use actix_web::{web, HttpResponse}; use comm_lib::blob::types::http::{ AssignHoldersRequest, AssignHoldersResponse, BlobInfo, @@ -63,10 +63,13 @@ service: web::Data, payload: web::Json, ) -> actix_web::Result { - let RemoveHoldersRequest { + let RemoveHoldersRequest::Items { requests, instant_delete, - } = payload.into_inner(); + } = payload.into_inner() + else { + return Err(ErrorNotImplemented("not implemented")); + }; info!( instant_delete, "Remove request for {} holders.", diff --git a/shared/comm-lib/src/blob/client.rs b/shared/comm-lib/src/blob/client.rs --- a/shared/comm-lib/src/blob/client.rs +++ b/shared/comm-lib/src/blob/client.rs @@ -253,7 +253,7 @@ debug!(num_holders, "Revoke multiple holders request."); let url = self.get_holders_url()?; - let payload = RemoveHoldersRequest { + let payload = RemoveHoldersRequest::Items { requests: blob_infos, instant_delete: false, }; diff --git a/shared/comm-lib/src/blob/types.rs b/shared/comm-lib/src/blob/types.rs --- a/shared/comm-lib/src/blob/types.rs +++ b/shared/comm-lib/src/blob/types.rs @@ -1,5 +1,6 @@ use derive_more::Constructor; use hex::ToHex; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; pub mod http { @@ -31,12 +32,23 @@ // Remove multiple holders #[derive(Serialize, Deserialize, Debug)] - #[serde(rename_all = "camelCase")] - pub struct RemoveHoldersRequest { - pub requests: Vec, - #[serde(default)] - pub instant_delete: bool, + #[serde(untagged)] + pub enum RemoveHoldersRequest { + // remove holders with given (hash, holder) pairs + #[serde(rename_all = "camelCase")] + Items { + requests: Vec, + /// If true, the blobs will be deleted instantly + /// after their last holders are revoked. + #[serde(default)] + instant_delete: bool, + }, + // remove all holders that are indexed by any of given tags + ByIndexedTags { + tags: Vec, + }, } + #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct RemoveHoldersResponse { @@ -59,7 +71,7 @@ pub struct RemoveHolderRequest { pub blob_hash: String, pub holder: String, - /// If true, the blob will be deleted intantly + /// If true, the blob will be deleted instantly /// after the last holder is revoked. #[serde(default)] pub instant_delete: bool, @@ -67,7 +79,7 @@ } /// Blob owning information - stores both blob_hash and holder -#[derive(Clone, Debug, Constructor, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, Constructor, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BlobInfo { pub blob_hash: String, @@ -132,3 +144,67 @@ } } } + +#[cfg(test)] +mod serialization_tests { + use super::http::*; + mod remove_holders_request { + use super::*; + + #[test] + fn serialize_items() { + let req = RemoveHoldersRequest::Items { + requests: vec![BlobInfo::new("a".into(), "b".into())], + instant_delete: false, + }; + let expected = + r#"{"requests":[{"blobHash":"a","holder":"b"}],"instantDelete":false}"#; + assert_eq!(expected, serde_json::to_string(&req).unwrap()); + } + + #[test] + fn deserialize_items() { + let json = + r#"{"requests":[{"blobHash":"a","holder":"b"}],"instantDelete":false}"#; + let deserialized: RemoveHoldersRequest = + serde_json::from_str(json).expect("Request JSON payload invalid"); + + let expected_items = vec![BlobInfo::new("a".into(), "b".into())]; + + let is_matching = matches!( + deserialized, + RemoveHoldersRequest::Items { + requests: items, + instant_delete: false, + } if items == expected_items + ); + assert!(is_matching, "Deserialized request is incorrect"); + } + + #[test] + fn serialize_tags() { + let req = RemoveHoldersRequest::ByIndexedTags { + tags: vec!["foo".into(), "bar".into()], + }; + let expected = r#"{"tags":["foo","bar"]}"#; + assert_eq!(expected, serde_json::to_string(&req).unwrap()); + } + + #[test] + fn deserialize_tags() { + let json = r#"{"tags":["foo","bar"]}"#; + let deserialized: RemoveHoldersRequest = + serde_json::from_str(json).expect("Request JSON payload invalid"); + + let expected_tags: Vec = vec!["foo".into(), "bar".into()]; + + let is_matching = matches!( + deserialized, + RemoveHoldersRequest::ByIndexedTags { + tags: actual_tags + } if actual_tags == expected_tags + ); + assert!(is_matching, "Deserialized request is incorrect"); + } + } +}