From 0ae251dbbbd42c3f8c6077012028da79488c49c8 Mon Sep 17 00:00:00 2001 From: quettabit <27509167+quettabit@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:23:20 -0700 Subject: [PATCH] initial commit --- README.md | 1 + src/object_store/config.rs | 2 ++ src/object_store/downloader.rs | 3 +++ src/service/routes.rs | 46 ++++++++++++++++++++++++++-------- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7b7e270..040bd46 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Space-separated key-value pairs to override S3 request configuration per page mi - `ma=` Maximum attempts - `ib=` Initial backoff duration - `mb=` Maximum backoff duration +- `fps=` Force path-style addressing #### Example Request diff --git a/src/object_store/config.rs b/src/object_store/config.rs index 8acfdd3..ed5d9e0 100644 --- a/src/object_store/config.rs +++ b/src/object_store/config.rs @@ -11,6 +11,7 @@ pub struct RequestConfig { pub max_attempts: Option, pub initial_backoff: Option, pub max_backoff: Option, + pub force_path_style: Option, } impl RequestConfig { @@ -23,6 +24,7 @@ impl RequestConfig { && self.max_attempts.is_none() && self.initial_backoff.is_none() && self.max_backoff.is_none() + && self.force_path_style.is_none() } fn has_timeout_overrides(&self) -> bool { diff --git a/src/object_store/downloader.rs b/src/object_store/downloader.rs index d55b3e5..6f2a39c 100644 --- a/src/object_store/downloader.rs +++ b/src/object_store/downloader.rs @@ -240,6 +240,9 @@ impl Downloader { { config_override = config_override.retry_config(retry_config); } + if let Some(force_path_style) = req_config.force_path_style { + config_override = config_override.force_path_style(force_path_style); + } request .customize() diff --git a/src/service/routes.rs b/src/service/routes.rs index 85a440d..86a6336 100644 --- a/src/service/routes.rs +++ b/src/service/routes.rs @@ -157,6 +157,15 @@ where let mut config = Self::default(); + let parse_duration = |v: &str| -> Result { + v.parse::().map(Duration::from_millis).map_err(|_| { + ( + StatusCode::BAD_REQUEST, + "Invalid duration value in C0-Config header", + ) + }) + }; + for pair in header_str.split_whitespace() { let Some((key, value)) = pair.split_once('=') else { return Err(( @@ -165,15 +174,6 @@ where )); }; - let parse_duration = |v: &str| -> Result { - v.parse::().map(Duration::from_millis).map_err(|_| { - ( - StatusCode::BAD_REQUEST, - "Invalid duration value in C0-Config header", - ) - }) - }; - match key { "ct" => config.connect_timeout = Some(parse_duration(value)?), "rt" => config.read_timeout = Some(parse_duration(value)?), @@ -183,12 +183,20 @@ where config.max_attempts = Some(value.parse().map_err(|_| { ( StatusCode::BAD_REQUEST, - "Invalid max_attempts value in C0-Config hheader", + "Invalid value for ma in C0-Config header", ) })?); } "ib" => config.initial_backoff = Some(parse_duration(value)?), "mb" => config.max_backoff = Some(parse_duration(value)?), + "fps" => { + config.force_path_style = Some(value.parse().map_err(|_| { + ( + StatusCode::BAD_REQUEST, + "Invalid value for fps in C0-Config header", + ) + })?); + } _ => {} // Ignore unrecognized keys } } @@ -542,6 +550,12 @@ mod tests { assert_eq!(config.max_attempts, Some(3)); } + #[tokio::test] + async fn test_c0_config_force_path_style() { + let config = parse_c0_config("fps=true").await.unwrap(); + assert_eq!(config.force_path_style, Some(true)); + } + #[tokio::test] async fn test_c0_config_mixed_settings() { let config = parse_c0_config("ct=1000 ma=5 ib=10 oat=1500") @@ -591,7 +605,17 @@ mod tests { assert!(result.is_err()); assert_eq!( result.unwrap_err().1, - "Invalid max_attempts value in C0-Config hheader" + "Invalid value for ma in C0-Config header" + ); + } + + #[tokio::test] + async fn test_c0_config_invalid_force_path_style() { + let result = parse_c0_config("fps=1").await; + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().1, + "Invalid value for fps in C0-Config header" ); }