1use opendal::layers::LoggingLayer;
14use opendal::services::S3;
15use opendal::Operator;
16
17use crate::errors::*;
18
19use super::http_client::set_user_agent;
20
21pub struct S3Cache;
22
23impl S3Cache {
24 pub fn build(
25 bucket: &str,
26 region: Option<&str>,
27 key_prefix: &str,
28 no_credentials: bool,
29 endpoint: Option<&str>,
30 use_ssl: Option<bool>,
31 server_side_encryption: Option<bool>,
32 ) -> Result<Operator> {
33 let mut builder = S3::default()
34 .http_client(set_user_agent())
35 .bucket(bucket)
36 .root(key_prefix);
37
38 if let Some(region) = region {
39 builder = builder.region(region);
40 }
41
42 if no_credentials {
43 builder = builder
44 .disable_config_load()
45 .disable_ec2_metadata()
50 .allow_anonymous();
53 }
54
55 if let Some(endpoint) = endpoint {
56 builder = builder.endpoint(&endpoint_resolver(endpoint, use_ssl)?);
57 }
58
59 if server_side_encryption.unwrap_or_default() {
60 builder = builder.server_side_encryption_with_s3_key();
61 }
62
63 let op = Operator::new(builder)?
64 .layer(LoggingLayer::default())
65 .finish();
66 Ok(op)
67 }
68}
69
70fn endpoint_resolver(endpoint: &str, use_ssl: Option<bool>) -> Result<String> {
72 let endpoint_uri: http::Uri = endpoint
73 .try_into()
74 .map_err(|err| anyhow!("input endpoint {endpoint} is invalid: {:?}", err))?;
75 let mut parts = endpoint_uri.into_parts();
76 match use_ssl {
77 Some(true) => {
78 parts.scheme = Some(http::uri::Scheme::HTTPS);
79 }
80 Some(false) => {
81 parts.scheme = Some(http::uri::Scheme::HTTP);
82 }
83 None => {
84 if parts.scheme.is_none() {
85 parts.scheme = Some(http::uri::Scheme::HTTP);
86 }
87 }
88 }
89 if parts.path_and_query.is_none() {
91 parts.path_and_query = Some(http::uri::PathAndQuery::from_static("/"));
92 }
93
94 Ok(http::Uri::from_parts(parts)?.to_string())
95}
96
97#[cfg(test)]
98mod test {
99 use super::*;
100
101 #[test]
102 fn test_endpoint_resolver() -> Result<()> {
103 let cases = vec![
104 (
105 "no scheme without use_ssl",
106 "s3-us-east-1.amazonaws.com",
107 None,
108 "http://s3-us-east-1.amazonaws.com/",
109 ),
110 (
111 "http without use_ssl",
112 "http://s3-us-east-1.amazonaws.com",
113 None,
114 "http://s3-us-east-1.amazonaws.com/",
115 ),
116 (
117 "https without use_ssl",
118 "https://s3-us-east-1.amazonaws.com",
119 None,
120 "https://s3-us-east-1.amazonaws.com/",
121 ),
122 (
123 "no scheme with use_ssl",
124 "s3-us-east-1.amazonaws.com",
125 Some(true),
126 "https://s3-us-east-1.amazonaws.com/",
127 ),
128 (
129 "http with use_ssl",
130 "http://s3-us-east-1.amazonaws.com",
131 Some(true),
132 "https://s3-us-east-1.amazonaws.com/",
133 ),
134 (
135 "https with use_ssl",
136 "https://s3-us-east-1.amazonaws.com",
137 Some(true),
138 "https://s3-us-east-1.amazonaws.com/",
139 ),
140 (
141 "no scheme with not use_ssl",
142 "s3-us-east-1.amazonaws.com",
143 Some(false),
144 "http://s3-us-east-1.amazonaws.com/",
145 ),
146 (
147 "http with not use_ssl",
148 "http://s3-us-east-1.amazonaws.com",
149 Some(false),
150 "http://s3-us-east-1.amazonaws.com/",
151 ),
152 (
153 "https with not use_ssl",
154 "https://s3-us-east-1.amazonaws.com",
155 Some(false),
156 "http://s3-us-east-1.amazonaws.com/",
157 ),
158 ];
159
160 for (name, endpoint, use_ssl, expected) in cases {
161 let actual = endpoint_resolver(endpoint, use_ssl)?;
162 assert_eq!(actual, expected, "{}", name);
163 }
164
165 Ok(())
166 }
167}