use crate::cluster::ClusterConnection;
use super::{
ConnectionAddr, ConnectionInfo, ErrorKind, IntoConnectionInfo, RedisError, RedisResult,
};
pub struct ClusterClientBuilder {
initial_nodes: RedisResult<Vec<ConnectionInfo>>,
read_from_replicas: bool,
username: Option<String>,
password: Option<String>,
}
impl ClusterClientBuilder {
pub fn new<T: IntoConnectionInfo>(initial_nodes: Vec<T>) -> ClusterClientBuilder {
ClusterClientBuilder {
initial_nodes: initial_nodes
.into_iter()
.map(|x| x.into_connection_info())
.collect(),
read_from_replicas: false,
username: None,
password: None,
}
}
pub fn open(self) -> RedisResult<ClusterClient> {
ClusterClient::build(self)
}
pub fn password(mut self, password: String) -> ClusterClientBuilder {
self.password = Some(password);
self
}
pub fn username(mut self, username: String) -> ClusterClientBuilder {
self.username = Some(username);
self
}
pub fn read_from_replicas(mut self) -> ClusterClientBuilder {
self.read_from_replicas = true;
self
}
#[deprecated(since = "0.22.0", note = "Use read_from_replicas()")]
pub fn readonly(mut self, read_from_replicas: bool) -> ClusterClientBuilder {
self.read_from_replicas = read_from_replicas;
self
}
}
pub struct ClusterClient {
initial_nodes: Vec<ConnectionInfo>,
read_from_replicas: bool,
username: Option<String>,
password: Option<String>,
}
impl ClusterClient {
pub fn open<T: IntoConnectionInfo>(initial_nodes: Vec<T>) -> RedisResult<ClusterClient> {
ClusterClientBuilder::new(initial_nodes).open()
}
pub fn get_connection(&self) -> RedisResult<ClusterConnection> {
ClusterConnection::new(
self.initial_nodes.clone(),
self.read_from_replicas,
self.username.clone(),
self.password.clone(),
)
}
fn build(builder: ClusterClientBuilder) -> RedisResult<ClusterClient> {
let initial_nodes = builder.initial_nodes?;
let mut nodes = Vec::with_capacity(initial_nodes.len());
let mut connection_info_password = None::<String>;
let mut connection_info_username = None::<String>;
for (index, info) in initial_nodes.into_iter().enumerate() {
if let ConnectionAddr::Unix(_) = info.addr {
return Err(RedisError::from((ErrorKind::InvalidClientConfig,
"This library cannot use unix socket because Redis's cluster command returns only cluster's IP and port.")));
}
if builder.password.is_none() {
if index == 0 {
connection_info_password = info.redis.password.clone();
} else if connection_info_password != info.redis.password {
return Err(RedisError::from((
ErrorKind::InvalidClientConfig,
"Cannot use different password among initial nodes.",
)));
}
}
if builder.username.is_none() {
if index == 0 {
connection_info_username = info.redis.username.clone();
} else if connection_info_username != info.redis.username {
return Err(RedisError::from((
ErrorKind::InvalidClientConfig,
"Cannot use different username among initial nodes.",
)));
}
}
nodes.push(info);
}
Ok(ClusterClient {
initial_nodes: nodes,
read_from_replicas: builder.read_from_replicas,
username: builder.username.or(connection_info_username),
password: builder.password.or(connection_info_password),
})
}
}
impl Clone for ClusterClient {
fn clone(&self) -> ClusterClient {
ClusterClient::open(self.initial_nodes.clone()).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::{ClusterClient, ClusterClientBuilder};
use super::{ConnectionInfo, IntoConnectionInfo};
fn get_connection_data() -> Vec<ConnectionInfo> {
vec![
"redis://127.0.0.1:6379".into_connection_info().unwrap(),
"redis://127.0.0.1:6378".into_connection_info().unwrap(),
"redis://127.0.0.1:6377".into_connection_info().unwrap(),
]
}
fn get_connection_data_with_password() -> Vec<ConnectionInfo> {
vec![
"redis://:password@127.0.0.1:6379"
.into_connection_info()
.unwrap(),
"redis://:password@127.0.0.1:6378"
.into_connection_info()
.unwrap(),
"redis://:password@127.0.0.1:6377"
.into_connection_info()
.unwrap(),
]
}
fn get_connection_data_with_username_and_password() -> Vec<ConnectionInfo> {
vec![
"redis://user1:password@127.0.0.1:6379"
.into_connection_info()
.unwrap(),
"redis://user1:password@127.0.0.1:6378"
.into_connection_info()
.unwrap(),
"redis://user1:password@127.0.0.1:6377"
.into_connection_info()
.unwrap(),
]
}
#[test]
fn give_no_password() {
let client = ClusterClient::open(get_connection_data()).unwrap();
assert_eq!(client.password, None);
}
#[test]
fn give_password_by_initial_nodes() {
let client = ClusterClient::open(get_connection_data_with_password()).unwrap();
assert_eq!(client.password, Some("password".to_string()));
}
#[test]
fn give_username_and_password_by_initial_nodes() {
let client = ClusterClient::open(get_connection_data_with_username_and_password()).unwrap();
assert_eq!(client.password, Some("password".to_string()));
assert_eq!(client.username, Some("user1".to_string()));
}
#[test]
fn give_different_password_by_initial_nodes() {
let result = ClusterClient::open(vec![
"redis://:password1@127.0.0.1:6379",
"redis://:password2@127.0.0.1:6378",
"redis://:password3@127.0.0.1:6377",
]);
assert!(result.is_err());
}
#[test]
fn give_different_username_by_initial_nodes() {
let result = ClusterClient::open(vec![
"redis://user1:password@127.0.0.1:6379",
"redis://user2:password@127.0.0.1:6378",
"redis://user1:password@127.0.0.1:6377",
]);
assert!(result.is_err());
}
#[test]
fn give_username_password_by_method() {
let client = ClusterClientBuilder::new(get_connection_data_with_password())
.password("pass".to_string())
.username("user1".to_string())
.open()
.unwrap();
assert_eq!(client.password, Some("pass".to_string()));
assert_eq!(client.username, Some("user1".to_string()));
}
}