opendal_service_redis/
config.rs1use std::fmt::Debug;
19
20use opendal_core::raw::*;
21use opendal_core::*;
22use serde::Deserialize;
23use serde::Serialize;
24
25use super::REDIS_SCHEME;
26use super::backend::RedisBuilder;
27
28#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
30#[serde(default)]
31#[non_exhaustive]
32pub struct RedisConfig {
33 pub endpoint: Option<String>,
37 pub cluster_endpoints: Option<String>,
41 pub connection_pool_max_size: Option<usize>,
45 pub username: Option<String>,
49 pub password: Option<String>,
53 pub root: Option<String>,
57 pub db: i64,
61 pub default_ttl: Option<Duration>,
63}
64
65impl Debug for RedisConfig {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.debug_struct("RedisConfig")
68 .field("endpoint", &self.endpoint)
69 .field("cluster_endpoints", &self.cluster_endpoints)
70 .field("username", &self.username)
71 .field("root", &self.root)
72 .field("db", &self.db)
73 .field("default_ttl", &self.default_ttl)
74 .finish_non_exhaustive()
75 }
76}
77
78impl Configurator for RedisConfig {
79 type Builder = RedisBuilder;
80
81 fn from_uri(uri: &OperatorUri) -> Result<Self> {
82 let mut map = uri.options().clone();
83
84 if let Some(authority) = uri.authority() {
85 map.entry("endpoint".to_string())
86 .or_insert_with(|| format!("redis://{authority}"));
87 } else if !map.contains_key("endpoint") && !map.contains_key("cluster_endpoints") {
88 return Err(Error::new(
89 ErrorKind::ConfigInvalid,
90 "endpoint or cluster_endpoints is required",
91 )
92 .with_context("service", REDIS_SCHEME));
93 }
94
95 if let Some(path) = uri.root()
96 && !path.is_empty()
97 {
98 if let Some((first, rest)) = path.split_once('/') {
99 if let Ok(db) = first.parse::<i64>() {
100 map.insert("db".to_string(), db.to_string());
101 if !rest.is_empty() {
102 map.insert("root".to_string(), rest.to_string());
103 }
104 } else {
105 let mut root_value = first.to_string();
106 if !rest.is_empty() {
107 root_value.push('/');
108 root_value.push_str(rest);
109 }
110 map.insert("root".to_string(), root_value);
111 }
112 } else if let Ok(db) = path.parse::<i64>() {
113 map.insert("db".to_string(), db.to_string());
114 } else {
115 map.insert("root".to_string(), path.to_string());
116 }
117 }
118
119 Self::from_iter(map)
120 }
121
122 fn into_builder(self) -> Self::Builder {
123 RedisBuilder { config: self }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn from_uri_sets_endpoint_db_and_root() -> Result<()> {
133 let uri = OperatorUri::new(
134 "redis://localhost:6379/2/cache",
135 Vec::<(String, String)>::new(),
136 )?;
137
138 let cfg = RedisConfig::from_uri(&uri)?;
139 assert_eq!(cfg.endpoint.as_deref(), Some("redis://localhost:6379"));
140 assert_eq!(cfg.db, 2);
141 assert_eq!(cfg.root.as_deref(), Some("cache"));
142 Ok(())
143 }
144
145 #[test]
146 fn from_uri_treats_non_numeric_path_as_root() -> Result<()> {
147 let uri = OperatorUri::new(
148 "redis://localhost:6379/app/data",
149 Vec::<(String, String)>::new(),
150 )?;
151
152 let cfg = RedisConfig::from_uri(&uri)?;
153 assert_eq!(cfg.endpoint.as_deref(), Some("redis://localhost:6379"));
154 assert_eq!(cfg.db, 0);
155 assert_eq!(cfg.root.as_deref(), Some("app/data"));
156 Ok(())
157 }
158
159 #[test]
160 fn test_redis_builder_interface() {
161 let builder = RedisBuilder::default()
163 .endpoint("redis://localhost:6379")
164 .username("testuser")
165 .password("testpass")
166 .db(1)
167 .root("/test");
168
169 assert!(builder.config.endpoint.is_some());
171 assert_eq!(
172 builder.config.endpoint.as_ref().unwrap(),
173 "redis://localhost:6379"
174 );
175 assert_eq!(builder.config.username.as_ref().unwrap(), "testuser");
176 assert_eq!(builder.config.password.as_ref().unwrap(), "testpass");
177 assert_eq!(builder.config.db, 1);
178 assert_eq!(builder.config.root.as_ref().unwrap(), "/test");
179 }
180}