From 02d2616ca05f301a43847e3ef5f74b992d57620e Mon Sep 17 00:00:00 2001 From: Gursharan Singh <3442979+G8XSU@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:08:26 -0700 Subject: [PATCH 1/3] Derive clone trait for VssClient --- src/client.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client.rs b/src/client.rs index 3474574..f5ef392 100644 --- a/src/client.rs +++ b/src/client.rs @@ -10,6 +10,7 @@ use crate::types::{ /// Thin-client to access a hosted instance of Versioned Storage Service (VSS). /// The provided [`VssClient`] API is minimalistic and is congruent to the VSS server-side API. +#[derive(Clone)] pub struct VssClient { base_url: String, client: Client, From 58976cea1cf9e15f624dfee0a86a130f4434c2bf Mon Sep 17 00:00:00 2001 From: Gursharan Singh <3442979+G8XSU@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:11:10 -0700 Subject: [PATCH 2/3] Update generated types acc. to latest vss-server api --- build.rs | 2 +- src/types.rs | 180 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 117 insertions(+), 65 deletions(-) diff --git a/build.rs b/build.rs index dd96086..594b631 100644 --- a/build.rs +++ b/build.rs @@ -14,7 +14,7 @@ fn main() { #[cfg(feature = "genproto")] fn generate_protos() { download_file( - "https://raw.githubusercontent.com/lightningdevkit/vss-server/ff4b5fc6a079ed8719eb8be7ec35ca1d01c1cc55/app/src/main/proto/vss.proto", + "https://raw.githubusercontent.com/lightningdevkit/vss-server/ac646dd419bc70db2b79772b1bfa1b2d9a4b8b53/app/src/main/proto/vss.proto", "src/proto/vss.proto", ).unwrap(); diff --git a/src/types.rs b/src/types.rs index 142931f..f30140d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,178 +1,229 @@ +/// Request payload to be used for `GetObject` API call to server. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetObjectRequest { - /// store_id is a keyspace identifier. + /// `store_id` is a keyspace identifier. /// Ref: ) - /// All APIs operate within a single store_id. + /// All APIs operate within a single `store_id`. /// It is up to clients to use single or multiple stores for their use-case. /// This can be used for client-isolation/ rate-limiting / throttling on the server-side. - /// Authorization and billing can also be performed at the store_id level. + /// Authorization and billing can also be performed at the `store_id` level. #[prost(string, tag = "1")] pub store_id: ::prost::alloc::string::String, - /// Key for which the value is to be fetched. + /// `Key` for which the value is to be fetched. /// /// Consistency Guarantee: - /// Get(read) operations against a key are consistent reads and will reflect all previous writes, + /// Get(read) operations against a `key` are consistent reads and will reflect all previous writes, /// since Put/Write provides read-after-write and read-after-update consistency guarantees. /// /// Read Isolation: - /// Get/Read operations against a key are ensured to have read-committed isolation. + /// Get/Read operations against a `key` are ensured to have read-committed isolation. /// Ref: )#Read_committed #[prost(string, tag = "2")] pub key: ::prost::alloc::string::String, } +/// Server response for `GetObject` API. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetObjectResponse { - /// Fetched value and version along with the corresponding key in the request. + /// Fetched `value` and `version` along with the corresponding `key` in the request. #[prost(message, optional, tag = "2")] pub value: ::core::option::Option, } +/// Request payload to be used for `PutObject` API call to server. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PutObjectRequest { - /// store_id is a keyspace identifier. + /// `store_id` is a keyspace identifier. /// Ref: ) - /// All APIs operate within a single store_id. + /// All APIs operate within a single `store_id`. /// It is up to clients to use single or multiple stores for their use-case. /// This can be used for client-isolation/ rate-limiting / throttling on the server-side. - /// Authorization and billing can also be performed at the store_id level. + /// Authorization and billing can also be performed at the `store_id` level. #[prost(string, tag = "1")] pub store_id: ::prost::alloc::string::String, - /// global_version is a sequence-number/version of the whole store. This can be used for versioning + /// `global_version` is a sequence-number/version of the whole store. This can be used for versioning /// and ensures that multiple updates in case of multiple devices can only be done linearly, even - /// if those updates did not directly conflict with each other based on keys/transaction_items. + /// if those updates did not directly conflict with each other based on keys/`transaction_items`. /// - /// If present, the write will only succeed if the current server-side global_version against - /// the store_id is same as in the request. - /// Clients are expected to store (client-side) the global version against store_id. - /// The request must contain their client-side value of global_version if global versioning and + /// If present, the write will only succeed if the current server-side `global_version` against + /// the `store_id` is same as in the request. + /// Clients are expected to store (client-side) the global version against `store_id`. + /// The request must contain their client-side value of `global_version` if global versioning and /// conflict detection is desired. /// /// For the first write of the store, global version should be '0'. If the write succeeds, clients /// must increment their global version (client-side) by 1. - /// The server increments global_version (server-side) for every successful write, hence this + /// The server increments `global_version` (server-side) for every successful write, hence this /// client-side increment is required to ensure matching versions. This updated global version - /// should be used in subsequent PutObjectRequests for the store. + /// should be used in subsequent `PutObjectRequest`s for the store. /// /// Requests with a conflicting version will fail with `CONFLICT_EXCEPTION` as ErrorCode. #[prost(int64, optional, tag = "2")] pub global_version: ::core::option::Option, - /// Items to be written as a result of this PutObjectRequest. + /// Items to be written as a result of this `PutObjectRequest`. /// - /// In an item, each key is supplied with its corresponding value and version. + /// In an item, each `key` is supplied with its corresponding `value` and `version`. /// Clients can choose to encrypt the keys client-side in order to obfuscate their usage patterns. - /// If the write is successful, the previous value corresponding to the key will be overwritten. + /// If the write is successful, the previous `value` corresponding to the `key` will be overwritten. /// - /// Multiple items in transaction_items of a single PutObjectRequest are written in + /// Multiple items in `transaction_items` and `delete_items` of a single `PutObjectRequest` are written in /// a database-transaction in an all-or-nothing fashion. - /// Items in a single PutObjectRequest must have distinct keys. + /// All Items in a single `PutObjectRequest` must have distinct keys. /// - /// Clients are expected to store a version against every key. - /// The write will succeed if the current DB version against the key is the same as in the request. - /// When initiating a PutObjectRequest, the request should contain their client-side version for + /// Clients are expected to store a `version` against every `key`. + /// The write will succeed if the current DB version against the `key` is the same as in the request. + /// When initiating a `PutObjectRequest`, the request should contain their client-side version for /// that key-value. /// - /// For the first write of any key, the version should be '0'. If the write succeeds, the client + /// For the first write of any key, the `version` should be '0'. If the write succeeds, the client /// must increment their corresponding key versions (client-side) by 1. /// The server increments key versions (server-side) for every successful write, hence this /// client-side increment is required to ensure matching versions. These updated key versions should - /// be used in subsequent PutObjectRequests for the keys. + /// be used in subsequent `PutObjectRequest`s for the keys. /// /// Requests with a conflicting version will fail with `CONFLICT_EXCEPTION` as ErrorCode. /// /// Considerations for transactions: /// Transaction writes of multiple items have a performance overhead, hence it is recommended to use /// them only if required by the client application to ensure logic/code correctness. - /// That is, transaction_items are not a substitute for batch-write of multiple unrelated items. + /// That is, `transaction_items` are not a substitute for batch-write of multiple unrelated items. /// When a write of multiple unrelated items is desired, it is recommended to use separate - /// PutObjectRequests. + /// `PutObjectRequest`s. /// /// Consistency guarantee: - /// All PutObjectRequests are strongly consistent i.e. they provide read-after-write and + /// All `PutObjectRequest`s are strongly consistent i.e. they provide read-after-write and /// read-after-update consistency guarantees. #[prost(message, repeated, tag = "3")] pub transaction_items: ::prost::alloc::vec::Vec, + /// Items to be deleted as a result of this `PutObjectRequest`. + /// + /// Each item in the `delete_items` field consists of a `key` and its corresponding `version`. + /// The `version` is used to perform a version check before deleting the item. + /// The delete will only succeed if the current database version against the `key` is the same as the `version` + /// specified in the request. + /// + /// Fails with `CONFLICT_EXCEPTION` as the ErrorCode if: + /// * The requested item does not exist. + /// * The requested item does exist but there is a version-number mismatch with the one in the database. + /// + /// Multiple items in the `delete_items` field, along with the `transaction_items`, are written in a + /// database transaction in an all-or-nothing fashion. + /// + /// All items within a single `PutObjectRequest` must have distinct keys. + #[prost(message, repeated, tag = "4")] + pub delete_items: ::prost::alloc::vec::Vec, } +/// Server response for `PutObject` API. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PutObjectResponse {} +/// Request payload to be used for `DeleteObject` API call to server. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteObjectRequest { + /// `store_id` is a keyspace identifier. + /// Ref: ) + /// All APIs operate within a single `store_id`. + /// It is up to clients to use single or multiple stores for their use-case. + /// This can be used for client-isolation/ rate-limiting / throttling on the server-side. + /// Authorization and billing can also be performed at the `store_id` level. + #[prost(string, tag = "1")] + pub store_id: ::prost::alloc::string::String, + /// Item to be deleted as a result of this `DeleteObjectRequest`. + /// + /// An item consists of a `key` and its corresponding `version`. + /// The item is only deleted if the current database version against the `key` is the same as the `version` + /// specified in the request. + /// This operation is idempotent, that is, multiple delete calls for the same item will not fail. + /// + /// If the requested item does not exist, this operation will not fail. + /// If you wish to perform stricter checks while deleting an item, consider using `PutObject` API. + #[prost(message, optional, tag = "2")] + pub key_value: ::core::option::Option, +} +/// Server response for `DeleteObject` API. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteObjectResponse {} +/// Request payload to be used for `ListKeyVersions` API call to server. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListKeyVersionsRequest { - /// store_id is a keyspace identifier. + /// `store_id` is a keyspace identifier. /// Ref: ) - /// All APIs operate within a single store_id. + /// All APIs operate within a single `store_id`. /// It is up to clients to use single or multiple stores for their use-case. /// This can be used for client-isolation/ rate-limiting / throttling on the server-side. - /// Authorization and billing can also be performed at the store_id level. + /// Authorization and billing can also be performed at the `store_id` level. #[prost(string, tag = "1")] pub store_id: ::prost::alloc::string::String, - /// A key_prefix is a string of characters at the beginning of the key. Prefixes can be used as + /// A `key_prefix` is a string of characters at the beginning of the key. Prefixes can be used as /// a way to organize key-values in a similar way to directories. /// - /// If key_prefix is specified, the response results will be limited to those keys that begin with + /// If `key_prefix` is specified, the response results will be limited to those keys that begin with /// the specified prefix. /// - /// If no key_prefix is specified or it is empty (""), all the keys are eligible to be returned in + /// If no `key_prefix` is specified or it is empty (""), all the keys are eligible to be returned in /// the response. #[prost(string, optional, tag = "2")] pub key_prefix: ::core::option::Option<::prost::alloc::string::String>, - /// page_size is used by clients to specify the maximum number of results that can be returned by + /// `page_size` is used by clients to specify the maximum number of results that can be returned by /// the server. /// The server may further constrain the maximum number of results returned in a single page. - /// If the page_size is 0 or not set, the server will decide the number of results to be returned. + /// If the `page_size` is 0 or not set, the server will decide the number of results to be returned. #[prost(int32, optional, tag = "3")] pub page_size: ::core::option::Option, - /// page_token is a pagination token. + /// `page_token` is a pagination token. /// - /// To query for the first page of ListKeyVersions, page_token must not be specified. + /// To query for the first page of `ListKeyVersions`, `page_token` must not be specified. /// /// For subsequent pages, use the value that was returned as `next_page_token` in the previous - /// page's ListKeyVersionsResponse. + /// page's `ListKeyVersionsResponse`. #[prost(string, optional, tag = "4")] pub page_token: ::core::option::Option<::prost::alloc::string::String>, } +/// Server response for `ListKeyVersions` API. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListKeyVersionsResponse { /// Fetched keys and versions. - /// Even though this API reuses KeyValue struct, the value sub-field will not be set by the server. + /// Even though this API reuses the `KeyValue` struct, the `value` sub-field will not be set by the server. #[prost(message, repeated, tag = "1")] pub key_versions: ::prost::alloc::vec::Vec, - /// next_page_token is a pagination token, used to retrieve the next page of results. - /// Use this value to query for next_page of paginated ListKeyVersions operation, by specifying + /// `next_page_token` is a pagination token, used to retrieve the next page of results. + /// Use this value to query for next-page of paginated `ListKeyVersions` operation, by specifying /// this value as the `page_token` in the next request. /// - /// If next_page_token is empty (""), then the "last page" of results has been processed and + /// If `next_page_token` is empty (""), then the "last page" of results has been processed and /// there is no more data to be retrieved. /// - /// If next_page_token is not empty, it does not necessarily mean that there is more data in the + /// If `next_page_token` is not empty, it does not necessarily mean that there is more data in the /// result set. The only way to know when you have reached the end of the result set is when - /// next_page_token is empty. + /// `next_page_token` is empty. /// /// Caution: Clients must not assume a specific number of key_versions to be present in a page for /// paginated response. #[prost(string, optional, tag = "2")] pub next_page_token: ::core::option::Option<::prost::alloc::string::String>, - /// global_version is a sequence-number/version of the whole store. + /// `global_version` is a sequence-number/version of the whole store. /// - /// global_version is only returned in response for the first page of the ListKeyVersionsResponse + /// `global_version` is only returned in response for the first page of the `ListKeyVersionsResponse` /// and is guaranteed to be read before reading any key-versions. /// /// In case of refreshing the complete key-version view on the client-side, correct usage for - /// the returned global_version is as following: - /// 1. Read global_version from the first page of paginated response and save it as local variable. - /// 2. Update all the key_versions on client-side from all the pages of paginated response. - /// 3. Update global_version on client_side from the local variable saved in step-1. - /// This ensures that on client-side, all current key_versions were stored at global_version or later. - /// This guarantee is helpful for ensuring the versioning correctness if using the global_version - /// in PutObject API and can help avoid the race conditions related to it. + /// the returned `global_version` is as following: + /// 1. Read `global_version` from the first page of paginated response and save it as local variable. + /// 2. Update all the `key_versions` on client-side from all the pages of paginated response. + /// 3. Update `global_version` on client_side from the local variable saved in step-1. + /// This ensures that on client-side, all current `key_versions` were stored at `global_version` or later. + /// This guarantee is helpful for ensuring the versioning correctness if using the `global_version` + /// in `PutObject` API and can help avoid the race conditions related to it. #[prost(int64, optional, tag = "3")] pub global_version: ::core::option::Option, } -/// When HttpStatusCode is not ok (200), the response `content` contains a serialized ErrorResponse -/// with the relevant ErrorCode and message +/// When HttpStatusCode is not ok (200), the response `content` contains a serialized `ErrorResponse` +/// with the relevant `ErrorCode` and `message` #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ErrorResponse { @@ -187,6 +238,7 @@ pub struct ErrorResponse { #[prost(string, tag = "2")] pub message: ::prost::alloc::string::String, } +/// Represents a key-value pair to be stored or retrieved. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct KeyValue { @@ -194,11 +246,11 @@ pub struct KeyValue { #[prost(string, tag = "1")] pub key: ::prost::alloc::string::String, /// Version field is used for key-level versioning. - /// For first write of key, version should be '0'. If the write succeeds, clients must increment + /// For first write of key, `version` should be '0'. If the write succeeds, clients must increment /// their corresponding key version (client-side) by 1. /// The server increments key version (server-side) for every successful write, hence this /// client-side increment is required to ensure matching versions. These updated key versions should - /// be used in subsequent PutObjectRequests for the keys. + /// be used in subsequent `PutObjectRequest`s for the keys. #[prost(int64, tag = "2")] pub version: i64, /// Object value in bytes which is stored (in put) and fetched (in get). @@ -207,14 +259,14 @@ pub struct KeyValue { #[prost(bytes = "vec", tag = "3")] pub value: ::prost::alloc::vec::Vec, } -/// ErrorCodes to be used in ErrorResponse +/// ErrorCodes to be used in `ErrorResponse` #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum ErrorCode { - /// Default protobuf Enum value. Will not be used as ErrorCode by server. + /// Default protobuf Enum value. Will not be used as `ErrorCode` by server. Unknown = 0, /// CONFLICT_EXCEPTION is used when the request contains mismatched version (either key or global) - /// in PutObjectRequest. For more info refer PutObjectRequest. + /// in `PutObjectRequest`. For more info refer `PutObjectRequest`. ConflictException = 1, /// INVALID_REQUEST_EXCEPTION is used in the following cases: /// - The request was missing a required argument. From 6a42ce677e5c6bababc4dd0737bfe6b8f509be9a Mon Sep 17 00:00:00 2001 From: Gursharan Singh <3442979+G8XSU@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:34:11 -0700 Subject: [PATCH 3/3] Add delete_object fn to client --- src/client.rs | 22 +++++++++++++++-- src/lib.rs | 9 +++---- tests/tests.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/client.rs b/src/client.rs index f5ef392..b3ab04f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,8 +4,8 @@ use reqwest::Client; use crate::error::VssError; use crate::types::{ - GetObjectRequest, GetObjectResponse, ListKeyVersionsRequest, ListKeyVersionsResponse, PutObjectRequest, - PutObjectResponse, + DeleteObjectRequest, DeleteObjectResponse, GetObjectRequest, GetObjectResponse, ListKeyVersionsRequest, + ListKeyVersionsResponse, PutObjectRequest, PutObjectResponse, }; /// Thin-client to access a hosted instance of Versioned Storage Service (VSS). @@ -60,6 +60,24 @@ impl VssClient { } } + /// Deletes the given `key` and `value` in `request`. + /// Makes a service call to the `DeleteObject` endpoint of the VSS server. + /// For API contract/usage, refer to docs for [`DeleteObjectRequest`] and [`DeleteObjectResponse`]. + pub async fn delete_object(&self, request: &DeleteObjectRequest) -> Result { + let url = format!("{}/deleteObject", self.base_url); + + let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?; + let status = response_raw.status(); + let payload = response_raw.bytes().await?; + + if status.is_success() { + let response = DeleteObjectResponse::decode(&payload[..])?; + Ok(response) + } else { + Err(VssError::new(status, payload)) + } + } + /// Lists keys and their corresponding version for a given [`ListKeyVersionsRequest::store_id`]. /// Makes a service call to the `ListKeyVersions` endpoint of the VSS server. /// For API contract/usage, refer to docs for [`ListKeyVersionsRequest`] and [`ListKeyVersionsResponse`]. diff --git a/src/lib.rs b/src/lib.rs index 4569ad4..237d6fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Client-side library to interact with Versioned Storage Service (VSS). //! //! VSS is an open-source project designed to offer a server-side cloud storage solution specifically -//! tailored for non-custodial Lightning supporting mobile wallets. Its primary objective is to +//! tailored for noncustodial Lightning supporting mobile wallets. Its primary objective is to //! simplify the development process for Lightning wallets by providing a secure means to store //! and manage the essential state required for Lightning Network (LN) operations. //! @@ -10,13 +10,10 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -use crate::client::VssClient; -use crate::error::VssError; - -/// Implements a thin-client ([`VssClient`]) to access a hosted instance of Versioned Storage Service (VSS). +/// Implements a thin-client ([`client::VssClient`]) to access a hosted instance of Versioned Storage Service (VSS). pub mod client; -/// Implements the error type ([`VssError`]) returned on interacting with [`VssClient`] +/// Implements the error type ([`error::VssError`]) returned on interacting with [`client::VssClient`] pub mod error; /// Contains request/response types generated from the API definition of VSS. diff --git a/tests/tests.rs b/tests/tests.rs index af26256..610d988 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -6,12 +6,13 @@ mod tests { use vss_client::error::VssError; use vss_client::types::{ - ErrorCode, ErrorResponse, GetObjectRequest, GetObjectResponse, KeyValue, ListKeyVersionsRequest, - ListKeyVersionsResponse, PutObjectRequest, PutObjectResponse, + DeleteObjectRequest, DeleteObjectResponse, ErrorCode, ErrorResponse, GetObjectRequest, GetObjectResponse, + KeyValue, ListKeyVersionsRequest, ListKeyVersionsResponse, PutObjectRequest, PutObjectResponse, }; const GET_OBJECT_ENDPOINT: &'static str = "/getObject"; const PUT_OBJECT_ENDPOINT: &'static str = "/putObjects"; + const DELETE_OBJECT_ENDPOINT: &'static str = "/deleteObject"; const LIST_KEY_VERSIONS_ENDPOINT: &'static str = "/listKeyVersions"; #[tokio::test] @@ -55,6 +56,7 @@ mod tests { store_id: "store".to_string(), global_version: Some(4), transaction_items: vec![KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }], + delete_items: vec![], }; let mock_response = PutObjectResponse::default(); @@ -76,6 +78,36 @@ mod tests { mock_server.expect(1).assert(); } + #[tokio::test] + async fn test_delete() { + // Spin-up mock server with mock response for given request. + let base_url = mockito::server_url().to_string(); + + // Set up the mock request/response. + let request = DeleteObjectRequest { + store_id: "store".to_string(), + key_value: Some(KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }), + }; + let mock_response = DeleteObjectResponse::default(); + + // Register the mock endpoint with the mockito server. + let mock_server = mockito::mock("POST", DELETE_OBJECT_ENDPOINT) + .match_body(request.encode_to_vec()) + .with_status(200) + .with_body(mock_response.encode_to_vec()) + .create(); + + // Create a new VssClient with the mock server URL. + let vss_client = VssClient::new(&base_url); + let actual_result = vss_client.delete_object(&request).await.unwrap(); + + let expected_result = &mock_response; + assert_eq!(actual_result, *expected_result); + + // Verify server endpoint was called exactly once. + mock_server.expect(1).assert(); + } + #[tokio::test] async fn test_list_key_versions() { // Spin-up mock server with mock response for given request. @@ -142,10 +174,19 @@ mod tests { store_id: "store".to_string(), global_version: Some(4), transaction_items: vec![KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }], + delete_items: vec![], }) .await; assert!(matches!(put_result.unwrap_err(), VssError::InvalidRequestError { .. })); + let delete_result = vss_client + .delete_object(&DeleteObjectRequest { + store_id: "store".to_string(), + key_value: Some(KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }), + }) + .await; + assert!(matches!(delete_result.unwrap_err(), VssError::InvalidRequestError { .. })); + let list_result = vss_client .list_key_versions(&ListKeyVersionsRequest { store_id: "store".to_string(), @@ -156,8 +197,8 @@ mod tests { .await; assert!(matches!(list_result.unwrap_err(), VssError::InvalidRequestError { .. })); - // Verify 3 requests hit the server - mock_server.expect(3).assert(); + // Verify 4 requests hit the server + mock_server.expect(4).assert(); } #[tokio::test] @@ -178,6 +219,7 @@ mod tests { store_id: "store".to_string(), global_version: Some(4), transaction_items: vec![KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }], + delete_items: vec![], }) .await; assert!(matches!(put_result.unwrap_err(), VssError::ConflictError { .. })); @@ -211,10 +253,19 @@ mod tests { store_id: "store".to_string(), global_version: Some(4), transaction_items: vec![KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }], + delete_items: vec![], }) .await; assert!(matches!(put_result.unwrap_err(), VssError::InternalServerError { .. })); + let delete_result = vss_client + .delete_object(&DeleteObjectRequest { + store_id: "store".to_string(), + key_value: Some(KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }), + }) + .await; + assert!(matches!(delete_result.unwrap_err(), VssError::InternalServerError { .. })); + let list_result = vss_client .list_key_versions(&ListKeyVersionsRequest { store_id: "store".to_string(), @@ -225,8 +276,8 @@ mod tests { .await; assert!(matches!(list_result.unwrap_err(), VssError::InternalServerError { .. })); - // Verify 3 requests hit the server - mock_server.expect(3).assert(); + // Verify 4 requests hit the server + mock_server.expect(4).assert(); } #[tokio::test] @@ -248,6 +299,7 @@ mod tests { store_id: "store".to_string(), global_version: Some(4), transaction_items: vec![KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }], + delete_items: vec![], }; let put_result = vss_client.put_object(&put_request).await; assert!(matches!(put_result.unwrap_err(), VssError::InternalError { .. }));