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