1use std::path::PathBuf;
19use std::sync::Arc;
20
21use http::Uri;
22use opendal_core::raw::*;
23use opendal_core::*;
24use redis::Client;
25use redis::ConnectionAddr;
26use redis::ConnectionInfo;
27use redis::IntoConnectionInfo;
28use redis::ProtocolVersion;
29use redis::RedisConnectionInfo;
30use redis::cluster::ClusterClientBuilder;
31
32use super::REDIS_SCHEME;
33use super::config::RedisConfig;
34use super::core::*;
35use super::deleter::RedisDeleter;
36use super::reader::*;
37use super::writer::RedisWriter;
38
39const DEFAULT_REDIS_ENDPOINT: &str = "tcp://127.0.0.1:6379";
40const DEFAULT_REDIS_PORT: u16 = 6379;
41
42#[doc = include_str!("docs.md")]
44#[derive(Debug, Default)]
45pub struct RedisBuilder {
46 pub(super) config: RedisConfig,
47}
48
49impl RedisBuilder {
50 pub fn endpoint(mut self, endpoint: &str) -> Self {
58 if !endpoint.is_empty() {
59 self.config.endpoint = Some(endpoint.to_owned());
60 }
61 self
62 }
63
64 pub fn cluster_endpoints(mut self, cluster_endpoints: &str) -> Self {
73 if !cluster_endpoints.is_empty() {
74 self.config.cluster_endpoints = Some(cluster_endpoints.to_owned());
75 }
76 self
77 }
78
79 pub fn username(mut self, username: &str) -> Self {
83 if !username.is_empty() {
84 self.config.username = Some(username.to_owned());
85 }
86 self
87 }
88
89 pub fn password(mut self, password: &str) -> Self {
93 if !password.is_empty() {
94 self.config.password = Some(password.to_owned());
95 }
96 self
97 }
98
99 pub fn db(mut self, db: i64) -> Self {
103 self.config.db = db;
104 self
105 }
106
107 pub fn default_ttl(mut self, ttl: Duration) -> Self {
111 self.config.default_ttl = Some(ttl);
112 self
113 }
114
115 pub fn root(mut self, root: &str) -> Self {
119 self.config.root = if root.is_empty() {
120 None
121 } else {
122 Some(root.to_string())
123 };
124
125 self
126 }
127
128 #[must_use]
136 pub fn connection_pool_max_size(mut self, max_size: usize) -> Self {
137 assert!(max_size > 0, "max_size must be greater than zero!");
138 self.config.connection_pool_max_size = Some(max_size);
139 self
140 }
141}
142
143impl Builder for RedisBuilder {
144 type Config = RedisConfig;
145
146 fn build(self) -> Result<impl Service> {
147 let root = normalize_root(
148 self.config
149 .root
150 .clone()
151 .unwrap_or_else(|| "/".to_string())
152 .as_str(),
153 );
154
155 if let Some(endpoints) = self.config.cluster_endpoints.clone() {
156 let mut cluster_endpoints: Vec<ConnectionInfo> = Vec::default();
157 for endpoint in endpoints.split(',') {
158 cluster_endpoints.push(self.get_connection_info(endpoint.to_string())?);
159 }
160 let mut client_builder = ClusterClientBuilder::new(cluster_endpoints);
161 if let Some(username) = &self.config.username {
162 client_builder = client_builder.username(username.clone());
163 }
164 if let Some(password) = &self.config.password {
165 client_builder = client_builder.password(password.clone());
166 }
167 let client = client_builder.build().map_err(format_redis_error)?;
168
169 Ok(RedisBackend::new(RedisCore::new(
170 endpoints,
171 None,
172 Some(client),
173 self.config.default_ttl,
174 self.config.connection_pool_max_size,
175 ))
176 .with_normalized_root(root))
177 } else {
178 let endpoint = self
179 .config
180 .endpoint
181 .clone()
182 .unwrap_or_else(|| DEFAULT_REDIS_ENDPOINT.to_string());
183
184 let client =
185 Client::open(self.get_connection_info(endpoint.clone())?).map_err(|e| {
186 Error::new(ErrorKind::ConfigInvalid, "invalid or unsupported scheme")
187 .with_context("service", REDIS_SCHEME)
188 .with_context("endpoint", self.config.endpoint.as_ref().unwrap())
189 .with_context("db", self.config.db.to_string())
190 .set_source(e)
191 })?;
192
193 Ok(RedisBackend::new(RedisCore::new(
194 endpoint,
195 Some(client),
196 None,
197 self.config.default_ttl,
198 self.config.connection_pool_max_size,
199 ))
200 .with_normalized_root(root))
201 }
202 }
203}
204
205impl RedisBuilder {
206 fn get_connection_info(&self, endpoint: String) -> Result<ConnectionInfo> {
207 let ep_url = endpoint.parse::<Uri>().map_err(|e| {
208 Error::new(ErrorKind::ConfigInvalid, "endpoint is invalid")
209 .with_context("service", REDIS_SCHEME)
210 .with_context("endpoint", endpoint)
211 .set_source(e)
212 })?;
213
214 let con_addr = match ep_url.scheme_str() {
215 Some("tcp") | Some("redis") | None => {
216 let host = ep_url
217 .host()
218 .map(|h| h.to_string())
219 .unwrap_or_else(|| "127.0.0.1".to_string());
220 let port = ep_url.port_u16().unwrap_or(DEFAULT_REDIS_PORT);
221 ConnectionAddr::Tcp(host, port)
222 }
223 Some("rediss") => {
224 let host = ep_url
225 .host()
226 .map(|h| h.to_string())
227 .unwrap_or_else(|| "127.0.0.1".to_string());
228 let port = ep_url.port_u16().unwrap_or(DEFAULT_REDIS_PORT);
229 ConnectionAddr::TcpTls {
230 host,
231 port,
232 insecure: false,
233 tls_params: None,
234 }
235 }
236 Some("unix") | Some("redis+unix") => {
237 let path = PathBuf::from(ep_url.path());
238 ConnectionAddr::Unix(path)
239 }
240 Some(s) => {
241 return Err(
242 Error::new(ErrorKind::ConfigInvalid, "invalid or unsupported scheme")
243 .with_context("service", REDIS_SCHEME)
244 .with_context("scheme", s),
245 );
246 }
247 };
248
249 let mut redis_info = RedisConnectionInfo::default()
250 .set_db(self.config.db)
251 .set_protocol(ProtocolVersion::RESP2);
252 if let Some(username) = &self.config.username {
253 redis_info = redis_info.set_username(username);
254 }
255 if let Some(password) = &self.config.password {
256 redis_info = redis_info.set_password(password);
257 }
258 let connection_info = con_addr
259 .clone()
260 .into_connection_info()
261 .map_err(|err| {
262 Error::new(ErrorKind::ConfigInvalid, "invalid connection address")
263 .with_context("service", REDIS_SCHEME)
264 .with_context("address", con_addr)
265 .with_context("error", err)
266 })?
267 .set_redis_settings(redis_info);
268
269 Ok(connection_info)
270 }
271}
272
273#[derive(Debug, Clone)]
275pub struct RedisBackend {
276 pub(crate) core: Arc<RedisCore>,
277 pub(crate) root: String,
278 pub(crate) info: ServiceInfo,
279 pub(crate) capability: Capability,
280}
281
282impl RedisBackend {
283 fn new(core: RedisCore) -> Self {
284 let info = ServiceInfo::new(REDIS_SCHEME, "/", core.addr());
285 let capability = Capability {
286 read: true,
287 write: true,
288 delete: true,
289 stat: true,
290 write_can_empty: true,
291 shared: true,
292 ..Default::default()
293 };
294
295 Self {
296 core: Arc::new(core),
297 root: "/".to_string(),
298 info,
299 capability,
300 }
301 }
302
303 fn with_normalized_root(mut self, root: String) -> Self {
304 self.info = self.info.with_root(&root);
305 self.root = root;
306 self
307 }
308}
309
310impl Service for RedisBackend {
311 type Reader = oio::StreamReader<RedisReader>;
312 type Writer = RedisWriter;
313 type Lister = ();
314 type Deleter = oio::OneShotDeleter<RedisDeleter>;
315 type Copier = ();
316
317 fn info(&self) -> ServiceInfo {
318 self.info.clone()
319 }
320
321 fn capability(&self) -> Capability {
322 self.capability
323 }
324
325 async fn create_dir(
326 &self,
327 _ctx: &OperationContext,
328 _path: &str,
329 _args: OpCreateDir,
330 ) -> Result<RpCreateDir> {
331 Err(Error::new(
332 ErrorKind::Unsupported,
333 "operation is not supported",
334 ))
335 }
336
337 async fn stat(&self, _ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
338 let p = build_abs_path(&self.root, path);
339
340 if p == build_abs_path(&self.root, "") {
341 Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
342 } else {
343 match self.core.len(&p).await? {
344 Some(len) => Ok(RpStat::new(
345 Metadata::new(EntryMode::FILE).with_content_length(len as u64),
346 )),
347 None => Err(Error::new(ErrorKind::NotFound, "key not found in redis")),
348 }
349 }
350 }
351
352 fn read(&self, _ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
353 let output: oio::StreamReader<RedisReader> = {
354 Ok(oio::StreamReader::new(RedisReader::new(
355 self.clone(),
356 path,
357 args,
358 )))
359 }?;
360
361 Ok(output)
362 }
363
364 fn write(&self, _ctx: &OperationContext, path: &str, _: OpWrite) -> Result<Self::Writer> {
365 let output: RedisWriter = {
366 let p = build_abs_path(&self.root, path);
367 Ok(RedisWriter::new(self.core.clone(), p))
368 }?;
369
370 Ok(output)
371 }
372
373 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
374 let output: oio::OneShotDeleter<RedisDeleter> = {
375 Ok(oio::OneShotDeleter::new(RedisDeleter::new(
376 self.core.clone(),
377 self.root.clone(),
378 )))
379 }?;
380
381 Ok(output)
382 }
383
384 fn list(&self, _ctx: &OperationContext, _path: &str, _args: OpList) -> Result<Self::Lister> {
385 Err(Error::new(
386 ErrorKind::Unsupported,
387 "operation is not supported",
388 ))
389 }
390
391 fn copy(
392 &self,
393 _ctx: &OperationContext,
394 _from: &str,
395 _to: &str,
396 _args: OpCopy,
397 _opts: OpCopier,
398 ) -> Result<Self::Copier> {
399 Err(Error::new(
400 ErrorKind::Unsupported,
401 "operation is not supported",
402 ))
403 }
404
405 async fn rename(
406 &self,
407 _ctx: &OperationContext,
408 _from: &str,
409 _to: &str,
410 _args: OpRename,
411 ) -> Result<RpRename> {
412 Err(Error::new(
413 ErrorKind::Unsupported,
414 "operation is not supported",
415 ))
416 }
417
418 async fn presign(
419 &self,
420 _ctx: &OperationContext,
421 _path: &str,
422 _args: OpPresign,
423 ) -> Result<RpPresign> {
424 Err(Error::new(
425 ErrorKind::Unsupported,
426 "operation is not supported",
427 ))
428 }
429}