use crate::{
client::FalkorClientProvider, FalkorConnectionInfo, FalkorDBError, FalkorResult,
FalkorSyncClient,
};
use std::num::NonZeroU8;
#[cfg(feature = "tokio")]
use crate::FalkorAsyncClient;
pub struct FalkorClientBuilder<const R: char> {
connection_info: Option<FalkorConnectionInfo>,
num_connections: NonZeroU8,
}
impl<const R: char> FalkorClientBuilder<R> {
pub fn with_connection_info(
self,
falkor_connection_info: FalkorConnectionInfo,
) -> Self {
Self {
connection_info: Some(falkor_connection_info),
..self
}
}
pub fn with_num_connections(
self,
num_connections: NonZeroU8,
) -> Self {
Self {
num_connections,
..self
}
}
fn get_client<E: ToString, T: TryInto<FalkorConnectionInfo, Error = E>>(
connection_info: T
) -> FalkorResult<(FalkorClientProvider, FalkorConnectionInfo)> {
let connection_info = connection_info
.try_into()
.map_err(|err| FalkorDBError::InvalidConnectionInfo(err.to_string()))?;
#[cfg(feature = "embedded")]
if let FalkorConnectionInfo::Embedded(ref config) = connection_info {
let embedded_server =
std::sync::Arc::new(crate::embedded::EmbeddedServer::start(config.clone())?);
let socket_path = embedded_server.socket_path();
let redis_connection_info = redis::ConnectionInfo {
addr: redis::ConnectionAddr::Unix(socket_path.to_path_buf()),
redis: redis::RedisConnectionInfo {
db: 0,
username: None,
password: None,
protocol: redis::ProtocolVersion::RESP2,
},
};
let client = redis::Client::open(redis_connection_info.clone())
.map_err(|err| FalkorDBError::RedisError(err.to_string()))?;
return Ok((
FalkorClientProvider::Redis {
client,
sentinel: None,
embedded_server: Some(embedded_server),
},
FalkorConnectionInfo::Redis(redis_connection_info),
));
}
Ok((
match connection_info {
FalkorConnectionInfo::Redis(ref redis_info) => FalkorClientProvider::Redis {
client: redis::Client::open(redis_info.clone())
.map_err(|err| FalkorDBError::RedisError(err.to_string()))?,
sentinel: None,
#[cfg(feature = "embedded")]
embedded_server: None,
},
#[cfg(feature = "embedded")]
FalkorConnectionInfo::Embedded(_) => unreachable!("Handled above"),
},
connection_info,
))
}
}
impl FalkorClientBuilder<'S'> {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
FalkorClientBuilder {
connection_info: None,
num_connections: NonZeroU8::new(8).expect("Error creating perfectly valid u8"),
}
}
pub fn build(self) -> FalkorResult<FalkorSyncClient> {
let connection_info = self
.connection_info
.unwrap_or("falkor://127.0.0.1:6379".try_into()?);
let (mut client, actual_connection_info) = Self::get_client(connection_info)?;
#[allow(irrefutable_let_patterns)]
if let FalkorConnectionInfo::Redis(redis_conn_info) = &actual_connection_info {
if let Some(sentinel) = client.get_sentinel_client(redis_conn_info)? {
client.set_sentinel(sentinel);
}
}
FalkorSyncClient::create(client, actual_connection_info, self.num_connections.get())
}
}
#[cfg(feature = "tokio")]
impl FalkorClientBuilder<'A'> {
pub fn new_async() -> Self {
FalkorClientBuilder {
connection_info: None,
num_connections: NonZeroU8::new(8).expect("Error creating perfectly valid u8"),
}
}
pub async fn build(self) -> FalkorResult<FalkorAsyncClient> {
let connection_info = self
.connection_info
.unwrap_or("falkor://127.0.0.1:6379".try_into()?);
let (mut client, actual_connection_info) = Self::get_client(connection_info)?;
#[allow(irrefutable_let_patterns)]
if let FalkorConnectionInfo::Redis(redis_conn_info) = &actual_connection_info {
if let Some(sentinel) = client.get_sentinel_client_async(redis_conn_info).await? {
client.set_sentinel(sentinel);
}
}
FalkorAsyncClient::create(client, actual_connection_info, self.num_connections.get()).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sync_builder() {
let connection_info = "falkor://127.0.0.1:6379".try_into();
assert!(connection_info.is_ok());
assert!(FalkorClientBuilder::new()
.with_connection_info(connection_info.unwrap())
.build()
.is_ok());
}
#[test]
fn test_connection_pool_size() {
let client = FalkorClientBuilder::new()
.with_num_connections(NonZeroU8::new(16).expect("Could not create a perfectly fine u8"))
.build();
assert!(client.is_ok());
assert_eq!(client.unwrap().connection_pool_size(), 16);
}
#[test]
#[cfg(feature = "embedded")]
fn test_embedded_config_creation() {
let config = crate::EmbeddedConfig::default();
assert_eq!(config.db_filename, "falkordb.rdb");
assert!(config.redis_server_path.is_none());
assert!(config.falkordb_module_path.is_none());
}
#[test]
#[cfg(feature = "embedded")]
fn test_embedded_builder_fails_without_binaries() {
use std::path::PathBuf;
let config = crate::EmbeddedConfig {
redis_server_path: Some(PathBuf::from("/nonexistent/redis-server")),
falkordb_module_path: Some(PathBuf::from("/nonexistent/falkordb.so")),
..Default::default()
};
let result = FalkorClientBuilder::new()
.with_connection_info(crate::FalkorConnectionInfo::Embedded(config))
.build();
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, crate::FalkorDBError::EmbeddedServerError(_)));
}
}
#[test]
#[cfg(feature = "embedded")]
fn test_embedded_builder_with_custom_config() {
use std::path::PathBuf;
let config = crate::EmbeddedConfig {
redis_server_path: Some(PathBuf::from("/custom/redis")),
falkordb_module_path: Some(PathBuf::from("/custom/falkordb.so")),
db_filename: "custom.rdb".to_string(),
..Default::default()
};
let builder = FalkorClientBuilder::new()
.with_connection_info(crate::FalkorConnectionInfo::Embedded(config));
let result = builder.build();
assert!(result.is_err());
}
#[test]
fn test_builder_with_different_pool_sizes() {
for size in [1, 4, 8, 16, 32] {
let client = FalkorClientBuilder::new()
.with_num_connections(NonZeroU8::new(size).expect("Could not create non-zero u8"))
.build();
if let Ok(client) = client {
assert_eq!(client.connection_pool_size(), size);
}
}
}
#[test]
fn test_builder_without_connection_info() {
let client = FalkorClientBuilder::new().build();
assert!(client.is_ok() || client.is_err());
}
#[test]
fn test_builder_with_invalid_connection_string() {
let result = FalkorClientBuilder::new()
.with_connection_info("invalid://bad:url".try_into().unwrap_or_else(|_| {
"falkor://127.0.0.1:6379".try_into().unwrap()
}))
.build();
assert!(result.is_ok() || result.is_err());
}
#[test]
#[cfg(feature = "tokio")]
fn test_async_builder_creation() {
let _builder = FalkorClientBuilder::new_async();
}
#[test]
#[cfg(all(feature = "embedded", feature = "tokio"))]
fn test_async_builder_with_embedded() {
use std::path::PathBuf;
let config = crate::EmbeddedConfig {
redis_server_path: Some(PathBuf::from("/nonexistent/redis")),
falkordb_module_path: Some(PathBuf::from("/nonexistent/falkordb.so")),
..Default::default()
};
let _builder = FalkorClientBuilder::new_async()
.with_connection_info(crate::FalkorConnectionInfo::Embedded(config));
}
}