use glide_core::client::AuthenticationInfo;
use glide_core::client::ConnectionRetryStrategy;
use glide_core::client::IamAuthenticationConfig;
use glide_core::client::NodeAddress as CoreNodeAddress;
use glide_core::client::NodeDiscoveryMode as CoreNodeDiscoveryMode;
use glide_core::client::PeriodicCheck;
use glide_core::client::ReadFrom as CoreReadFrom;
use glide_core::client::TlsMode;
use glide_core::iam::ServiceType as CoreServiceType;
use std::collections::HashMap;
use std::collections::HashSet;
use std::time::Duration;
pub(crate) const LIB_NAME: &str = "GlideRust";
pub(crate) const LIB_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PubSubChannelMode {
Exact,
Pattern,
Sharded,
}
impl PubSubChannelMode {
fn to_core(self) -> redis::PubSubSubscriptionKind {
match self {
PubSubChannelMode::Exact => redis::PubSubSubscriptionKind::Exact,
PubSubChannelMode::Pattern => redis::PubSubSubscriptionKind::Pattern,
PubSubChannelMode::Sharded => redis::PubSubSubscriptionKind::Sharded,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PubSubSubscriptions {
channels: HashMap<PubSubChannelMode, HashSet<Vec<u8>>>,
}
impl PubSubSubscriptions {
pub fn new() -> Self {
PubSubSubscriptions::default()
}
pub fn subscribe(mut self, mode: PubSubChannelMode, channel: impl Into<Vec<u8>>) -> Self {
self.channels
.entry(mode)
.or_default()
.insert(channel.into());
self
}
pub fn is_empty(&self) -> bool {
self.channels.values().all(|s| s.is_empty())
}
pub(crate) fn to_core(&self) -> redis::PubSubSubscriptionInfo {
let mut info: redis::PubSubSubscriptionInfo = HashMap::new();
for (mode, set) in &self.channels {
info.insert(mode.to_core(), set.clone());
}
info
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ProtocolVersion {
RESP2,
#[default]
RESP3,
}
impl ProtocolVersion {
pub(crate) fn to_core(self) -> redis::ProtocolVersion {
match self {
ProtocolVersion::RESP2 => redis::ProtocolVersion::RESP2,
ProtocolVersion::RESP3 => redis::ProtocolVersion::RESP3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ReadFrom {
#[default]
Primary,
PreferReplica,
AZAffinity(String),
AZAffinityReplicasAndPrimary(String),
AZAffinityAllNodes(String),
AllNodes,
}
impl ReadFrom {
pub(crate) fn to_core(&self) -> CoreReadFrom {
match self {
ReadFrom::Primary => CoreReadFrom::Primary,
ReadFrom::PreferReplica => CoreReadFrom::PreferReplica,
ReadFrom::AZAffinity(az) => CoreReadFrom::AZAffinity(az.clone()),
ReadFrom::AZAffinityReplicasAndPrimary(az) => {
CoreReadFrom::AZAffinityReplicasAndPrimary(az.clone())
}
ReadFrom::AZAffinityAllNodes(az) => CoreReadFrom::AZAffinityAllNodes(az.clone()),
ReadFrom::AllNodes => CoreReadFrom::AllNodes,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum NodeDiscoveryMode {
#[default]
Standard,
Static,
DiscoverAll,
}
impl NodeDiscoveryMode {
pub(crate) fn to_core(self) -> CoreNodeDiscoveryMode {
match self {
NodeDiscoveryMode::Standard => CoreNodeDiscoveryMode::Standard,
NodeDiscoveryMode::Static => CoreNodeDiscoveryMode::Static,
NodeDiscoveryMode::DiscoverAll => CoreNodeDiscoveryMode::DiscoverAll,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeAddress {
pub host: String,
pub port: u16,
}
impl NodeAddress {
pub fn new(host: impl Into<String>, port: u16) -> Self {
NodeAddress {
host: host.into(),
port,
}
}
}
impl Default for NodeAddress {
fn default() -> Self {
NodeAddress::new("localhost", 6379)
}
}
impl NodeAddress {
pub(crate) fn to_core(&self) -> CoreNodeAddress {
CoreNodeAddress {
host: self.host.clone(),
port: self.port,
}
}
}
#[derive(Clone, Default, PartialEq, Eq)]
pub struct ServerCredentials {
username: Option<String>,
pub(crate) password: Option<String>,
iam_config: Option<IamAuthConfig>,
}
impl ServerCredentials {
pub fn password(password: impl Into<String>) -> Self {
ServerCredentials {
username: None,
password: Some(password.into()),
iam_config: None,
}
}
pub fn username_password(username: impl Into<String>, password: impl Into<String>) -> Self {
ServerCredentials {
username: Some(username.into()),
password: Some(password.into()),
iam_config: None,
}
}
pub fn iam(username: impl Into<String>, iam_config: IamAuthConfig) -> Self {
ServerCredentials {
username: Some(username.into()),
password: None,
iam_config: Some(iam_config),
}
}
pub fn username(&self) -> Option<&str> {
self.username.as_deref()
}
pub fn iam_config(&self) -> Option<&IamAuthConfig> {
self.iam_config.as_ref()
}
pub fn is_iam_auth(&self) -> bool {
self.iam_config.is_some()
}
pub(crate) fn to_core(&self) -> AuthenticationInfo {
AuthenticationInfo {
username: self.username.clone(),
password: self.password.clone(),
iam_config: self.iam_config.as_ref().map(IamAuthConfig::to_core),
}
}
}
impl std::fmt::Debug for ServerCredentials {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ServerCredentials")
.field("username", &self.username)
.field("password", &self.password.as_ref().map(|_| "<redacted>"))
.field("iam_config", &self.iam_config)
.finish()
}
}
#[derive(Clone)]
pub struct ClientIdentity {
cert_pem: Vec<u8>,
key_pem: Vec<u8>,
}
impl ClientIdentity {
pub fn new(cert_pem: impl Into<Vec<u8>>, key_pem: impl Into<Vec<u8>>) -> Self {
ClientIdentity {
cert_pem: cert_pem.into(),
key_pem: key_pem.into(),
}
}
pub fn cert_pem(&self) -> &[u8] {
&self.cert_pem
}
pub(crate) fn key_pem(&self) -> &[u8] {
&self.key_pem
}
}
impl std::fmt::Debug for ClientIdentity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClientIdentity")
.field("cert_pem", &format_args!("[{} bytes]", self.cert_pem.len()))
.field("key_pem", &"<redacted>")
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ServiceType {
ElastiCache,
MemoryDB,
}
impl ServiceType {
pub(crate) fn to_core(self) -> CoreServiceType {
match self {
ServiceType::ElastiCache => CoreServiceType::ElastiCache,
ServiceType::MemoryDB => CoreServiceType::MemoryDB,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IamAuthConfig {
cluster_name: String,
region: String,
service_type: ServiceType,
refresh_interval_seconds: Option<u32>,
}
impl IamAuthConfig {
pub fn new(
cluster_name: impl Into<String>,
region: impl Into<String>,
service_type: ServiceType,
) -> Self {
IamAuthConfig {
cluster_name: cluster_name.into(),
region: region.into(),
service_type,
refresh_interval_seconds: None,
}
}
#[must_use]
pub fn with_refresh_interval_seconds(mut self, seconds: u32) -> Self {
self.refresh_interval_seconds = Some(seconds);
self
}
pub fn cluster_name(&self) -> &str {
&self.cluster_name
}
pub fn region(&self) -> &str {
&self.region
}
pub fn service_type(&self) -> ServiceType {
self.service_type
}
pub fn refresh_interval_seconds(&self) -> Option<u32> {
self.refresh_interval_seconds
}
fn to_core(&self) -> IamAuthenticationConfig {
IamAuthenticationConfig {
cluster_name: self.cluster_name.clone(),
region: self.region.clone(),
service_type: self.service_type.to_core(),
refresh_interval_seconds: self.refresh_interval_seconds,
credentials_provider: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BackoffStrategy {
pub num_of_retries: u32,
pub factor: u32,
pub exponent_base: u32,
pub jitter_percent: Option<u32>,
}
impl BackoffStrategy {
pub(crate) fn to_core(self) -> ConnectionRetryStrategy {
ConnectionRetryStrategy {
exponent_base: self.exponent_base,
factor: self.factor,
number_of_retries: self.num_of_retries,
jitter_percent: self.jitter_percent,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PeriodicChecks {
#[default]
Enabled,
Disabled,
ManualInterval(u64),
}
impl PeriodicChecks {
pub(crate) fn to_core(self) -> PeriodicCheck {
match self {
PeriodicChecks::Enabled => PeriodicCheck::Enabled,
PeriodicChecks::Disabled => PeriodicCheck::Disabled,
PeriodicChecks::ManualInterval(secs) => {
PeriodicCheck::ManualInterval(Duration::from_secs(secs))
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TlsConfig {
#[default]
NoTls,
SecureTls,
InsecureTls,
}
impl TlsConfig {
pub(crate) fn to_core(self) -> TlsMode {
match self {
TlsConfig::NoTls => TlsMode::NoTls,
TlsConfig::SecureTls => TlsMode::SecureTls,
TlsConfig::InsecureTls => TlsMode::InsecureTls,
}
}
}
pub(crate) fn to_redis_connection_info(
url: impl AsRef<str>,
) -> crate::ValkeyResult<redis::ConnectionInfo> {
redis::IntoConnectionInfo::into_connection_info(url.as_ref())
.map_err(|e| crate::error::GlideError::Configuration(e.to_string()))
}
pub(crate) fn split_connection_addr(
addr: redis::ConnectionAddr,
) -> crate::ValkeyResult<(NodeAddress, TlsConfig)> {
match addr {
redis::ConnectionAddr::Tcp(host, port) => {
Ok((NodeAddress::new(host, port), TlsConfig::NoTls))
}
redis::ConnectionAddr::TcpTls {
host,
port,
insecure,
tls_params,
} => {
if tls_params.is_some() {
return Err(crate::error::GlideError::Configuration(
"ConnectionInfo carries TLS certificate parameters (TlsCertificates) that \
cannot be mapped; configure them via the config's `root_certs` field and \
`client_identity(cert, key)` instead"
.into(),
));
}
let tls = if insecure {
TlsConfig::InsecureTls
} else {
TlsConfig::SecureTls
};
Ok((NodeAddress::new(host, port), tls))
}
redis::ConnectionAddr::Unix(_) => Err(crate::error::GlideError::Configuration(
"unix-socket connections are not supported by glide-core".into(),
)),
}
}
pub(crate) fn from_redis_protocol(p: redis::ProtocolVersion) -> ProtocolVersion {
match p {
redis::ProtocolVersion::RESP2 => ProtocolVersion::RESP2,
redis::ProtocolVersion::RESP3 => ProtocolVersion::RESP3,
}
}
pub(crate) fn credentials_from_info(
username: Option<String>,
password: Option<String>,
) -> Option<ServerCredentials> {
match (username, password) {
(None, None) => None,
(username, password) => Some(ServerCredentials {
username,
password,
iam_config: None,
}),
}
}
pub(crate) fn duration_as_millis_u32(d: Duration) -> u32 {
u32::try_from(d.as_millis()).unwrap_or(u32::MAX)
}
macro_rules! impl_common_config_builders {
($ty:ty) => {
impl $ty {
pub fn with_address(host: impl Into<String>, port: u16) -> Self {
Self::new(vec![NodeAddress::new(host, port)])
}
pub fn tls(mut self, tls: TlsConfig) -> Self {
self.tls = tls;
self
}
pub fn tls_ca_cert(mut self, pem: impl Into<Vec<u8>>) -> Self {
self.root_certs.push(pem.into());
self
}
#[must_use]
pub fn client_identity(
mut self,
cert_pem: impl Into<Vec<u8>>,
key_pem: impl Into<Vec<u8>>,
) -> Self {
self.client_identity = Some(ClientIdentity::new(cert_pem, key_pem));
self
}
pub fn credentials(mut self, creds: ServerCredentials) -> Self {
self.credentials = Some(creds);
self
}
pub fn read_from(mut self, read_from: ReadFrom) -> Self {
self.read_from = read_from;
self
}
pub fn request_timeout(mut self, timeout: Duration) -> Self {
self.request_timeout = Some(timeout);
self
}
pub fn connection_timeout(mut self, timeout: Duration) -> Self {
self.connection_timeout = Some(timeout);
self
}
pub fn inflight_requests_limit(mut self, limit: u32) -> Self {
self.inflight_requests_limit = Some(limit);
self
}
pub fn client_name(mut self, name: impl Into<String>) -> Self {
self.client_name = Some(name.into());
self
}
pub fn protocol(mut self, protocol: ProtocolVersion) -> Self {
self.protocol = protocol;
self
}
pub fn lazy_connect(mut self, lazy: bool) -> Self {
self.lazy_connect = lazy;
self
}
pub fn reconnect_strategy(mut self, strategy: BackoffStrategy) -> Self {
self.reconnect_strategy = Some(strategy);
self
}
pub fn subscriptions(mut self, subscriptions: PubSubSubscriptions) -> Self {
self.pubsub_subscriptions = Some(subscriptions);
self
}
#[must_use]
pub fn enable_pubsub(mut self) -> Self {
self.force_pubsub_channel = true;
self
}
pub(crate) fn common_request(&self) -> glide_core::client::ConnectionRequest {
use glide_core::client::ConnectionRequest;
let mut req = ConnectionRequest {
addresses: self.addresses.iter().map(NodeAddress::to_core).collect(),
tls_mode: Some(self.tls.to_core()),
read_from: Some(self.read_from.to_core()),
protocol: Some(self.protocol.to_core()),
client_name: self.client_name.clone(),
lib_name: Some($crate::config::common::LIB_NAME.to_string()),
lib_ver: Some($crate::config::common::LIB_VERSION.to_string()),
lazy_connect: self.lazy_connect,
inflight_requests_limit: self.inflight_requests_limit,
tcp_nodelay: true,
..ConnectionRequest::default()
};
if !self.root_certs.is_empty() {
req.root_certs = self.root_certs.clone();
}
if let Some(identity) = &self.client_identity {
req.client_cert = identity.cert_pem().to_vec();
req.client_key = identity.key_pem().to_vec();
}
if let Some(subs) = &self.pubsub_subscriptions {
req.pubsub_subscriptions = Some(subs.to_core());
}
if let Some(creds) = &self.credentials {
req.authentication_info = Some(creds.to_core());
}
if let Some(t) = self.request_timeout {
req.request_timeout = Some(duration_as_millis_u32(t));
}
if let Some(t) = self.connection_timeout {
req.connection_timeout = Some(duration_as_millis_u32(t));
}
if let Some(strategy) = self.reconnect_strategy {
req.connection_retry_strategy = Some(strategy.to_core());
}
req
}
}
};
}
pub(crate) use impl_common_config_builders;