object_store/aws/
resolve.rs1use crate::aws::STORE;
19use crate::{ClientOptions, Result};
20use snafu::{ensure, OptionExt, ResultExt, Snafu};
21
22#[derive(Debug, Snafu)]
24#[allow(missing_docs)]
25enum Error {
26 #[snafu(display("Bucket '{}' not found", bucket))]
27 BucketNotFound { bucket: String },
28
29 #[snafu(display("Failed to resolve region for bucket '{}'", bucket))]
30 ResolveRegion {
31 bucket: String,
32 source: reqwest::Error,
33 },
34
35 #[snafu(display("Failed to parse the region for bucket '{}'", bucket))]
36 RegionParse { bucket: String },
37}
38
39impl From<Error> for crate::Error {
40 fn from(source: Error) -> Self {
41 Self::Generic {
42 store: STORE,
43 source: Box::new(source),
44 }
45 }
46}
47
48pub async fn resolve_bucket_region(bucket: &str, client_options: &ClientOptions) -> Result<String> {
52 use reqwest::StatusCode;
53
54 let endpoint = format!("https://{}.s3.amazonaws.com", bucket);
55
56 let client = client_options.client()?;
57
58 let response = client
59 .head(&endpoint)
60 .send()
61 .await
62 .context(ResolveRegionSnafu { bucket })?;
63
64 ensure!(
65 response.status() != StatusCode::NOT_FOUND,
66 BucketNotFoundSnafu { bucket }
67 );
68
69 let region = response
70 .headers()
71 .get("x-amz-bucket-region")
72 .and_then(|x| x.to_str().ok())
73 .context(RegionParseSnafu { bucket })?;
74
75 Ok(region.to_string())
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[tokio::test]
83 async fn test_private_bucket() {
84 let bucket = "bloxbender";
85
86 let region = resolve_bucket_region(bucket, &ClientOptions::new())
87 .await
88 .unwrap();
89
90 let expected = "us-west-2".to_string();
91
92 assert_eq!(region, expected);
93 }
94
95 #[tokio::test]
96 async fn test_bucket_does_not_exist() {
97 let bucket = "please-dont-exist";
98
99 let result = resolve_bucket_region(bucket, &ClientOptions::new()).await;
100
101 assert!(result.is_err());
102 }
103}