Skip to content

Commit fe670bf

Browse files
committed
Refactor ClusterClient into it's own file
Pulls all the logic around building/creating a ClusterClient into a separate file.
1 parent 51e88b4 commit fe670bf

File tree

3 files changed

+190
-174
lines changed

3 files changed

+190
-174
lines changed

src/cluster.rs

Lines changed: 3 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -54,122 +54,12 @@ use super::{
5454
ErrorKind, IntoConnectionInfo, RedisError, RedisResult, Value,
5555
};
5656

57+
pub use crate::cluster_client::{ClusterClient, ClusterClientBuilder};
58+
5759
const SLOT_SIZE: usize = 16384;
5860

5961
type SlotMap = BTreeMap<u16, String>;
6062

61-
/// This is a ClusterClientBuilder of Redis cluster client.
62-
pub struct ClusterClientBuilder {
63-
initial_nodes: RedisResult<Vec<ConnectionInfo>>,
64-
readonly: bool,
65-
password: Option<String>,
66-
}
67-
68-
impl ClusterClientBuilder {
69-
/// Generate the base configuration for new Client.
70-
pub fn new<T: IntoConnectionInfo>(initial_nodes: Vec<T>) -> ClusterClientBuilder {
71-
ClusterClientBuilder {
72-
initial_nodes: initial_nodes
73-
.into_iter()
74-
.map(|x| x.into_connection_info())
75-
.collect(),
76-
readonly: false,
77-
password: None,
78-
}
79-
}
80-
81-
/// Connect to a redis cluster server and return a cluster client.
82-
/// This does not actually open a connection yet but it performs some basic checks on the URL.
83-
/// The password of initial nodes must be the same all.
84-
///
85-
/// # Errors
86-
///
87-
/// If it is failed to parse initial_nodes or the initial nodes has different password, an error is returned.
88-
pub fn open(self) -> RedisResult<ClusterClient> {
89-
ClusterClient::open_internal(self)
90-
}
91-
92-
/// Set password for new ClusterClient.
93-
pub fn password(mut self, password: String) -> ClusterClientBuilder {
94-
self.password = Some(password);
95-
self
96-
}
97-
98-
/// Set read only mode for new ClusterClient.
99-
/// Default is not read only mode.
100-
/// When it is set to readonly mode, all query use replica nodes except there are no replica nodes.
101-
/// If there are no replica nodes, it use master node.
102-
pub fn readonly(mut self, readonly: bool) -> ClusterClientBuilder {
103-
self.readonly = readonly;
104-
self
105-
}
106-
}
107-
108-
/// This is a Redis cluster client.
109-
pub struct ClusterClient {
110-
initial_nodes: Vec<ConnectionInfo>,
111-
readonly: bool,
112-
password: Option<String>,
113-
}
114-
115-
impl ClusterClient {
116-
/// Connect to a redis cluster server and return a cluster client.
117-
/// This does not actually open a connection yet but it performs some basic checks on the URL.
118-
/// The password of initial nodes must be the same all.
119-
///
120-
/// # Errors
121-
///
122-
/// If it is failed to parse initial_nodes or the initial nodes has different password, an error is returned.
123-
pub fn open<T: IntoConnectionInfo>(initial_nodes: Vec<T>) -> RedisResult<ClusterClient> {
124-
ClusterClientBuilder::new(initial_nodes).open()
125-
}
126-
127-
/// Open and get a Redis cluster connection.
128-
///
129-
/// # Errors
130-
///
131-
/// If it is failed to open connections and to create slots, an error is returned.
132-
pub fn get_connection(&self) -> RedisResult<ClusterConnection> {
133-
ClusterConnection::new(
134-
self.initial_nodes.clone(),
135-
self.readonly,
136-
self.password.clone(),
137-
)
138-
}
139-
140-
fn open_internal(builder: ClusterClientBuilder) -> RedisResult<ClusterClient> {
141-
let initial_nodes = builder.initial_nodes?;
142-
let mut nodes = Vec::with_capacity(initial_nodes.len());
143-
let mut connection_info_password = None::<String>;
144-
145-
for (index, info) in initial_nodes.into_iter().enumerate() {
146-
if let ConnectionAddr::Unix(_) = *info.addr {
147-
return Err(RedisError::from((ErrorKind::InvalidClientConfig,
148-
"This library cannot use unix socket because Redis's cluster command returns only cluster's IP and port.")));
149-
}
150-
151-
if builder.password.is_none() {
152-
if index == 0 {
153-
connection_info_password = info.passwd.clone();
154-
} else if connection_info_password != info.passwd {
155-
return Err(RedisError::from((
156-
ErrorKind::InvalidClientConfig,
157-
"Cannot use different password among initial nodes.",
158-
)));
159-
}
160-
}
161-
162-
nodes.push(info);
163-
}
164-
165-
Ok(ClusterClient {
166-
initial_nodes: nodes,
167-
readonly: builder.readonly,
168-
password: builder.password.or(connection_info_password),
169-
})
170-
}
171-
}
172-
17363
/// This is a connection of Redis cluster.
17464
pub struct ClusterConnection {
17565
initial_nodes: Vec<ConnectionInfo>,
@@ -181,7 +71,7 @@ pub struct ClusterConnection {
18171
}
18272

18373
impl ClusterConnection {
184-
fn new(
74+
pub(crate) fn new(
18575
initial_nodes: Vec<ConnectionInfo>,
18676
readonly: bool,
18777
password: Option<String>,
@@ -610,12 +500,6 @@ impl ConnectionLike for ClusterConnection {
610500
}
611501
}
612502

613-
impl Clone for ClusterClient {
614-
fn clone(&self) -> ClusterClient {
615-
ClusterClient::open(self.initial_nodes.clone()).unwrap()
616-
}
617-
}
618-
619503
fn connect<T: IntoConnectionInfo>(
620504
info: T,
621505
readonly: bool,
@@ -861,61 +745,6 @@ fn get_slots(connection: &mut Connection) -> RedisResult<Vec<Slot>> {
861745
#[cfg(test)]
862746
mod tests {
863747
use super::get_hashtag;
864-
use super::{ClusterClient, ClusterClientBuilder};
865-
use super::{ConnectionInfo, IntoConnectionInfo};
866-
867-
fn get_connection_data() -> Vec<ConnectionInfo> {
868-
vec![
869-
"redis://127.0.0.1:6379".into_connection_info().unwrap(),
870-
"redis://127.0.0.1:6378".into_connection_info().unwrap(),
871-
"redis://127.0.0.1:6377".into_connection_info().unwrap(),
872-
]
873-
}
874-
875-
fn get_connection_data_with_password() -> Vec<ConnectionInfo> {
876-
vec![
877-
"redis://:password@127.0.0.1:6379"
878-
.into_connection_info()
879-
.unwrap(),
880-
"redis://:password@127.0.0.1:6378"
881-
.into_connection_info()
882-
.unwrap(),
883-
"redis://:password@127.0.0.1:6377"
884-
.into_connection_info()
885-
.unwrap(),
886-
]
887-
}
888-
889-
#[test]
890-
fn give_no_password() {
891-
let client = ClusterClient::open(get_connection_data()).unwrap();
892-
assert_eq!(client.password, None);
893-
}
894-
895-
#[test]
896-
fn give_password_by_initial_nodes() {
897-
let client = ClusterClient::open(get_connection_data_with_password()).unwrap();
898-
assert_eq!(client.password, Some("password".to_string()));
899-
}
900-
901-
#[test]
902-
fn give_different_password_by_initial_nodes() {
903-
let result = ClusterClient::open(vec![
904-
"redis://:password1@127.0.0.1:6379",
905-
"redis://:password2@127.0.0.1:6378",
906-
"redis://:password3@127.0.0.1:6377",
907-
]);
908-
assert!(result.is_err());
909-
}
910-
911-
#[test]
912-
fn give_password_by_method() {
913-
let client = ClusterClientBuilder::new(get_connection_data_with_password())
914-
.password("pass".to_string())
915-
.open()
916-
.unwrap();
917-
assert_eq!(client.password, Some("pass".to_string()));
918-
}
919748

920749
#[test]
921750
fn test_get_hashtag() {

src/cluster_client.rs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
use crate::cluster::ClusterConnection;
2+
3+
use super::{
4+
ConnectionAddr, ConnectionInfo, ErrorKind, IntoConnectionInfo, RedisError, RedisResult,
5+
};
6+
7+
/// Used to configure and build a [ClusterClient](ClusterClient).
8+
pub struct ClusterClientBuilder {
9+
initial_nodes: RedisResult<Vec<ConnectionInfo>>,
10+
readonly: bool,
11+
password: Option<String>,
12+
}
13+
14+
impl ClusterClientBuilder {
15+
/// Generate the base configuration for new Client.
16+
pub fn new<T: IntoConnectionInfo>(initial_nodes: Vec<T>) -> ClusterClientBuilder {
17+
ClusterClientBuilder {
18+
initial_nodes: initial_nodes
19+
.into_iter()
20+
.map(|x| x.into_connection_info())
21+
.collect(),
22+
readonly: false,
23+
password: None,
24+
}
25+
}
26+
27+
/// Builds a [ClusterClient](ClusterClient). Despite the name, this does not actually open
28+
/// a connection to Redis Cluster, but will perform some basic checks of the initial
29+
/// nodes' URLs and passwords.
30+
///
31+
/// # Errors
32+
///
33+
/// Upon failure to parse initial nodes or if the initial nodes have different passwords,
34+
/// an error is returned.
35+
pub fn open(self) -> RedisResult<ClusterClient> {
36+
ClusterClient::build(self)
37+
}
38+
39+
/// Set password for new ClusterClient.
40+
pub fn password(mut self, password: String) -> ClusterClientBuilder {
41+
self.password = Some(password);
42+
self
43+
}
44+
45+
/// Set read only mode for new ClusterClient (default is false).
46+
/// If readonly is true, all queries will go to replica nodes. If there are no replica nodes,
47+
/// queries will be issued to the primary nodes.
48+
pub fn readonly(mut self, readonly: bool) -> ClusterClientBuilder {
49+
self.readonly = readonly;
50+
self
51+
}
52+
}
53+
54+
/// This is a Redis cluster client.
55+
pub struct ClusterClient {
56+
initial_nodes: Vec<ConnectionInfo>,
57+
readonly: bool,
58+
password: Option<String>,
59+
}
60+
61+
impl ClusterClient {
62+
/// Create a [ClusterClient](ClusterClient) with the default configuration. Despite the name,
63+
/// this does not actually open a connection to Redis Cluster, but only performs some basic
64+
/// checks of the initial nodes' URLs and passwords.
65+
///
66+
/// # Errors
67+
///
68+
/// Upon failure to parse initial nodes or if the initial nodes have different passwords,
69+
/// an error is returned.
70+
pub fn open<T: IntoConnectionInfo>(initial_nodes: Vec<T>) -> RedisResult<ClusterClient> {
71+
ClusterClientBuilder::new(initial_nodes).open()
72+
}
73+
74+
/// Opens connections to Redis Cluster nodes and returns a
75+
/// [ClusterConnection](ClusterConnection).
76+
///
77+
/// # Errors
78+
///
79+
/// An error is returned if there is a failure to open connections or to create slots.
80+
pub fn get_connection(&self) -> RedisResult<ClusterConnection> {
81+
ClusterConnection::new(
82+
self.initial_nodes.clone(),
83+
self.readonly,
84+
self.password.clone(),
85+
)
86+
}
87+
88+
fn build(builder: ClusterClientBuilder) -> RedisResult<ClusterClient> {
89+
let initial_nodes = builder.initial_nodes?;
90+
let mut nodes = Vec::with_capacity(initial_nodes.len());
91+
let mut connection_info_password = None::<String>;
92+
93+
for (index, info) in initial_nodes.into_iter().enumerate() {
94+
if let ConnectionAddr::Unix(_) = *info.addr {
95+
return Err(RedisError::from((ErrorKind::InvalidClientConfig,
96+
"This library cannot use unix socket because Redis's cluster command returns only cluster's IP and port.")));
97+
}
98+
99+
if builder.password.is_none() {
100+
if index == 0 {
101+
connection_info_password = info.passwd.clone();
102+
} else if connection_info_password != info.passwd {
103+
return Err(RedisError::from((
104+
ErrorKind::InvalidClientConfig,
105+
"Cannot use different password among initial nodes.",
106+
)));
107+
}
108+
}
109+
110+
nodes.push(info);
111+
}
112+
113+
Ok(ClusterClient {
114+
initial_nodes: nodes,
115+
readonly: builder.readonly,
116+
password: builder.password.or(connection_info_password),
117+
})
118+
}
119+
}
120+
121+
impl Clone for ClusterClient {
122+
fn clone(&self) -> ClusterClient {
123+
ClusterClient::open(self.initial_nodes.clone()).unwrap()
124+
}
125+
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use super::{ClusterClient, ClusterClientBuilder};
130+
use super::{ConnectionInfo, IntoConnectionInfo};
131+
132+
fn get_connection_data() -> Vec<ConnectionInfo> {
133+
vec![
134+
"redis://127.0.0.1:6379".into_connection_info().unwrap(),
135+
"redis://127.0.0.1:6378".into_connection_info().unwrap(),
136+
"redis://127.0.0.1:6377".into_connection_info().unwrap(),
137+
]
138+
}
139+
140+
fn get_connection_data_with_password() -> Vec<ConnectionInfo> {
141+
vec![
142+
"redis://:password@127.0.0.1:6379"
143+
.into_connection_info()
144+
.unwrap(),
145+
"redis://:password@127.0.0.1:6378"
146+
.into_connection_info()
147+
.unwrap(),
148+
"redis://:password@127.0.0.1:6377"
149+
.into_connection_info()
150+
.unwrap(),
151+
]
152+
}
153+
154+
#[test]
155+
fn give_no_password() {
156+
let client = ClusterClient::open(get_connection_data()).unwrap();
157+
assert_eq!(client.password, None);
158+
}
159+
160+
#[test]
161+
fn give_password_by_initial_nodes() {
162+
let client = ClusterClient::open(get_connection_data_with_password()).unwrap();
163+
assert_eq!(client.password, Some("password".to_string()));
164+
}
165+
166+
#[test]
167+
fn give_different_password_by_initial_nodes() {
168+
let result = ClusterClient::open(vec![
169+
"redis://:password1@127.0.0.1:6379",
170+
"redis://:password2@127.0.0.1:6378",
171+
"redis://:password3@127.0.0.1:6377",
172+
]);
173+
assert!(result.is_err());
174+
}
175+
176+
#[test]
177+
fn give_password_by_method() {
178+
let client = ClusterClientBuilder::new(get_connection_data_with_password())
179+
.password("pass".to_string())
180+
.open()
181+
.unwrap();
182+
assert_eq!(client.password, Some("pass".to_string()));
183+
}
184+
}

0 commit comments

Comments
 (0)