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<SignedDuration>,
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 {
124 config: self,
125 default_ttl: None,
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn from_uri_sets_endpoint_db_and_root() -> Result<()> {
136 let uri = OperatorUri::new(
137 "redis://localhost:6379/2/cache",
138 Vec::<(String, String)>::new(),
139 )?;
140
141 let cfg = RedisConfig::from_uri(&uri)?;
142 assert_eq!(cfg.endpoint.as_deref(), Some("redis://localhost:6379"));
143 assert_eq!(cfg.db, 2);
144 assert_eq!(cfg.root.as_deref(), Some("cache"));
145 Ok(())
146 }
147
148 #[test]
149 fn from_uri_treats_non_numeric_path_as_root() -> Result<()> {
150 let uri = OperatorUri::new(
151 "redis://localhost:6379/app/data",
152 Vec::<(String, String)>::new(),
153 )?;
154
155 let cfg = RedisConfig::from_uri(&uri)?;
156 assert_eq!(cfg.endpoint.as_deref(), Some("redis://localhost:6379"));
157 assert_eq!(cfg.db, 0);
158 assert_eq!(cfg.root.as_deref(), Some("app/data"));
159 Ok(())
160 }
161
162 #[test]
163 fn from_iter_parses_default_ttl() -> Result<()> {
164 let cfg = RedisConfig::from_iter([("default_ttl".to_string(), "5s".to_string())])?;
165
166 assert_eq!(cfg.default_ttl, Some(SignedDuration::from_secs(5)));
167 Ok(())
168 }
169
170 #[test]
171 fn test_redis_builder_interface() {
172 let builder = RedisBuilder::default()
174 .endpoint("redis://localhost:6379")
175 .username("testuser")
176 .password("testpass")
177 .db(1)
178 .root("/test");
179
180 assert!(builder.config.endpoint.is_some());
182 assert_eq!(
183 builder.config.endpoint.as_ref().unwrap(),
184 "redis://localhost:6379"
185 );
186 assert_eq!(builder.config.username.as_ref().unwrap(), "testuser");
187 assert_eq!(builder.config.password.as_ref().unwrap(), "testpass");
188 assert_eq!(builder.config.db, 1);
189 assert_eq!(builder.config.root.as_ref().unwrap(), "/test");
190 }
191}