Skip to content

Commit 4e83209

Browse files
NugineCopilot
andauthored
feat(s3s): add region in VirtualHost (#481)
* feat(s3s): add region in VirtualHost * docs(s3s): add documentation and tests for VirtualHost region field (#482) * Initial plan * docs(s3s): add documentation and tests for region functionality - Add doc comments for with_region() builder method with examples - Add doc comments for region() getter method - Add comprehensive test coverage in virtual_host_builder test - Test basic construction, individual builders, and method chaining Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com>
1 parent 586f185 commit 4e83209

File tree

1 file changed

+94
-9
lines changed

1 file changed

+94
-9
lines changed

crates/s3s/src/host.rs

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,58 @@ use stdx::default::default;
88
pub struct VirtualHost<'a> {
99
domain: Cow<'a, str>,
1010
bucket: Option<Cow<'a, str>>,
11-
// pub(crate) region: Option<Cow<'a, str>>,
11+
region: Option<Cow<'a, str>>,
1212
}
1313

1414
impl<'a> VirtualHost<'a> {
1515
pub fn new(domain: impl Into<Cow<'a, str>>) -> Self {
1616
Self {
1717
domain: domain.into(),
1818
bucket: None,
19+
region: None,
1920
}
2021
}
2122

22-
pub fn with_bucket(domain: impl Into<Cow<'a, str>>, bucket: impl Into<Cow<'a, str>>) -> Self {
23-
Self {
24-
domain: domain.into(),
25-
bucket: Some(bucket.into()),
26-
}
23+
/// Sets the bucket name for this virtual host.
24+
///
25+
/// This method follows the builder pattern and returns `self` for method chaining.
26+
///
27+
/// # Examples
28+
///
29+
/// ```
30+
/// use s3s::host::VirtualHost;
31+
///
32+
/// let vh = VirtualHost::new("example.com")
33+
/// .with_bucket("my-bucket");
34+
///
35+
/// assert_eq!(vh.bucket(), Some("my-bucket"));
36+
/// ```
37+
#[must_use]
38+
pub fn with_bucket(mut self, bucket: impl Into<Cow<'a, str>>) -> Self {
39+
self.bucket = Some(bucket.into());
40+
self
41+
}
42+
43+
/// Sets the AWS region for this virtual host.
44+
///
45+
/// This method follows the builder pattern and returns `self` for method chaining.
46+
/// The region represents the AWS region where the S3 bucket is located.
47+
///
48+
/// # Examples
49+
///
50+
/// ```
51+
/// use s3s::host::VirtualHost;
52+
///
53+
/// let vh = VirtualHost::new("example.com")
54+
/// .with_bucket("my-bucket")
55+
/// .with_region("us-west-2");
56+
///
57+
/// assert_eq!(vh.region(), Some("us-west-2"));
58+
/// ```
59+
#[must_use]
60+
pub fn with_region(mut self, region: impl Into<Cow<'a, str>>) -> Self {
61+
self.region = Some(region.into());
62+
self
2763
}
2864

2965
#[inline]
@@ -37,6 +73,18 @@ impl<'a> VirtualHost<'a> {
3773
pub fn bucket(&self) -> Option<&str> {
3874
self.bucket.as_deref()
3975
}
76+
77+
/// Returns the AWS region associated with this virtual host, if set.
78+
///
79+
/// # Returns
80+
///
81+
/// - `Some(&str)` - The region name if it was set using `with_region()`
82+
/// - `None` - If no region was specified
83+
#[inline]
84+
#[must_use]
85+
pub fn region(&self) -> Option<&str> {
86+
self.region.as_deref()
87+
}
4088
}
4189

4290
pub trait S3Host: Send + Sync + 'static {
@@ -96,7 +144,7 @@ fn parse_host_header<'a>(base_domain: &'a str, host: &'a str) -> Option<VirtualH
96144
}
97145

98146
if let Some(bucket) = host.strip_suffix(base_domain).and_then(|h| h.strip_suffix('.')) {
99-
return Some(VirtualHost::with_bucket(base_domain, bucket));
147+
return Some(VirtualHost::new(base_domain).with_bucket(bucket));
100148
}
101149

102150
None
@@ -133,7 +181,7 @@ impl S3Host for SingleDomain {
133181

134182
if is_valid_domain(host) {
135183
let bucket = host.to_ascii_lowercase();
136-
return Ok(VirtualHost::with_bucket(host, bucket));
184+
return Ok(VirtualHost::new(host).with_bucket(bucket));
137185
}
138186

139187
Err(s3_error!(InvalidRequest, "Invalid host header"))
@@ -194,7 +242,7 @@ impl S3Host for MultiDomain {
194242

195243
if is_valid_domain(host) {
196244
let bucket = host.to_ascii_lowercase();
197-
return Ok(VirtualHost::with_bucket(host, bucket));
245+
return Ok(VirtualHost::new(host).with_bucket(bucket));
198246
}
199247

200248
Err(s3_error!(InvalidRequest, "Invalid host header"))
@@ -291,4 +339,41 @@ mod tests {
291339
assert_eq!(vh.domain(), "example.com");
292340
assert_eq!(vh.bucket(), Some("example.com.org"));
293341
}
342+
343+
#[test]
344+
fn virtual_host_builder() {
345+
// Test basic construction
346+
let vh = VirtualHost::new("example.com");
347+
assert_eq!(vh.domain(), "example.com");
348+
assert_eq!(vh.bucket(), None);
349+
assert_eq!(vh.region(), None);
350+
351+
// Test with_bucket builder
352+
let vh = VirtualHost::new("example.com").with_bucket("my-bucket");
353+
assert_eq!(vh.domain(), "example.com");
354+
assert_eq!(vh.bucket(), Some("my-bucket"));
355+
assert_eq!(vh.region(), None);
356+
357+
// Test with_region builder
358+
let vh = VirtualHost::new("example.com").with_region("us-west-2");
359+
assert_eq!(vh.domain(), "example.com");
360+
assert_eq!(vh.bucket(), None);
361+
assert_eq!(vh.region(), Some("us-west-2"));
362+
363+
// Test chaining with_bucket and with_region
364+
let vh = VirtualHost::new("example.com")
365+
.with_bucket("my-bucket")
366+
.with_region("us-east-1");
367+
assert_eq!(vh.domain(), "example.com");
368+
assert_eq!(vh.bucket(), Some("my-bucket"));
369+
assert_eq!(vh.region(), Some("us-east-1"));
370+
371+
// Test chaining with_region and with_bucket (reversed order)
372+
let vh = VirtualHost::new("example.com")
373+
.with_region("eu-west-1")
374+
.with_bucket("another-bucket");
375+
assert_eq!(vh.domain(), "example.com");
376+
assert_eq!(vh.bucket(), Some("another-bucket"));
377+
assert_eq!(vh.region(), Some("eu-west-1"));
378+
}
294379
}

0 commit comments

Comments
 (0)