From 09044c2cb0997c5d18d55aca5386e237e746efb2 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 19 Aug 2025 11:14:09 +0200 Subject: [PATCH 1/5] Enable `reqwest` client-level timeouts While the `RetryPolicy` has a `MaxTotalDelayRetryPolicy`, the retry `loop` would only check this configured delay once the operation future actually returns a value. However, without client-side timeouts, we're not super sure the operation is actually guaranteed to return anything (even an error, IIUC). So here, we enable some coarse client-side default timeouts to ensure the polled futures eventualy return either the response *or* an error we can handle via our retry logic. --- src/client.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index c6bf32e..5cf5388 100644 --- a/src/client.rs +++ b/src/client.rs @@ -14,6 +14,7 @@ use crate::types::{ use crate::util::retry::{retry, RetryPolicy}; const APPLICATION_OCTET_STREAM: &str = "application/octet-stream"; +const DEFAULT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); /// 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. @@ -31,7 +32,12 @@ where impl> VssClient { /// Constructs a [`VssClient`] using `base_url` as the VSS server endpoint. pub fn new(base_url: String, retry_policy: R) -> Self { - let client = Client::new(); + let client = Client::builder() + .timeout(DEFAULT_TIMEOUT) + .connect_timeout(DEFAULT_TIMEOUT) + .read_timeout(DEFAULT_TIMEOUT) + .build() + .unwrap(); Self::from_client(base_url, client, retry_policy) } @@ -51,7 +57,12 @@ impl> VssClient { pub fn new_with_headers( base_url: String, retry_policy: R, header_provider: Arc, ) -> Self { - let client = Client::new(); + let client = Client::builder() + .timeout(DEFAULT_TIMEOUT) + .connect_timeout(DEFAULT_TIMEOUT) + .read_timeout(DEFAULT_TIMEOUT) + .build() + .unwrap(); Self { base_url, client, retry_policy, header_provider } } From c2c70595210cc5486be3f601afea8e472118c75b Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 4 Nov 2025 13:10:14 +0100 Subject: [PATCH 2/5] DRY up `Client` building --- src/client.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/client.rs b/src/client.rs index 5cf5388..3af0330 100644 --- a/src/client.rs +++ b/src/client.rs @@ -32,12 +32,7 @@ where impl> VssClient { /// Constructs a [`VssClient`] using `base_url` as the VSS server endpoint. pub fn new(base_url: String, retry_policy: R) -> Self { - let client = Client::builder() - .timeout(DEFAULT_TIMEOUT) - .connect_timeout(DEFAULT_TIMEOUT) - .read_timeout(DEFAULT_TIMEOUT) - .build() - .unwrap(); + let client = build_client(); Self::from_client(base_url, client, retry_policy) } @@ -57,12 +52,7 @@ impl> VssClient { pub fn new_with_headers( base_url: String, retry_policy: R, header_provider: Arc, ) -> Self { - let client = Client::builder() - .timeout(DEFAULT_TIMEOUT) - .connect_timeout(DEFAULT_TIMEOUT) - .read_timeout(DEFAULT_TIMEOUT) - .build() - .unwrap(); + let client = build_client(); Self { base_url, client, retry_policy, header_provider } } @@ -173,3 +163,12 @@ impl> VssClient { } } } + +fn build_client() -> Client { + Client::builder() + .timeout(DEFAULT_TIMEOUT) + .connect_timeout(DEFAULT_TIMEOUT) + .read_timeout(DEFAULT_TIMEOUT) + .build() + .unwrap() +} From a25ed48ad904431e4d9f3021a9763d6bea6c424f Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 4 Nov 2025 14:21:45 +0100 Subject: [PATCH 3/5] Re-export the `reqwest` crate .. as some types are part of our API. --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2f808e6..f89bb79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,9 @@ #![deny(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] +// Crate re-exports +pub use reqwest; + /// Implements a thin-client ([`client::VssClient`]) to access a hosted instance of Versioned Storage Service (VSS). pub mod client; From 6bdf1045d4cd86e37e293fdc77cc532a180ab0da Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 5 Nov 2025 14:15:33 +0100 Subject: [PATCH 4/5] Expect `NOT_FOUND` / 404 status code in `NoSuchKey` error response --- tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.rs b/tests/tests.rs index 8fd6d04..68e22c8 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -221,7 +221,7 @@ mod tests { message: "NoSuchKeyException".to_string(), }; let mock_server = mockito::mock("POST", GET_OBJECT_ENDPOINT) - .with_status(409) + .with_status(404) .with_body(&error_response.encode_to_vec()) .create(); From 23097bfb3f53b6c79176a711d3c465ca3fe67913 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 6 Nov 2025 14:06:13 +0100 Subject: [PATCH 5/5] Add `VssClient::from_client_and_headers` constructor Previously, we'd allow to either re-use a `reqwest::Client` or supply a header provider. Here we add a new constructor that allows us to do both at the same time. --- src/client.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/client.rs b/src/client.rs index 3af0330..cf80972 100644 --- a/src/client.rs +++ b/src/client.rs @@ -46,6 +46,16 @@ impl> VssClient { } } + /// Constructs a [`VssClient`] from a given [`reqwest::Client`], using `base_url` as the VSS server endpoint. + /// + /// HTTP headers will be provided by the given `header_provider`. + pub fn from_client_and_headers( + base_url: String, client: Client, retry_policy: R, + header_provider: Arc, + ) -> Self { + Self { base_url, client, retry_policy, header_provider } + } + /// Constructs a [`VssClient`] using `base_url` as the VSS server endpoint. /// /// HTTP headers will be provided by the given `header_provider`.