use glide_core::client::{
AuthenticationInfo, ConnectionRetryStrategy, IamAuthenticationConfig,
NodeAddress as CoreNodeAddress, PeriodicCheck, ReadFrom as CoreReadFrom, TlsMode,
};
use glide_core::iam::ServiceType as CoreServiceType;
use std::collections::{HashMap, HashSet};
use std::time::Duration;
#[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 From<ProtocolVersion> for redis::ProtocolVersion {
fn from(v: ProtocolVersion) -> Self {
match v {
ProtocolVersion::RESP2 => redis::ProtocolVersion::RESP2,
ProtocolVersion::RESP3 => redis::ProtocolVersion::RESP3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum ReadFrom {
#[default]
Primary,
PreferReplica,
AZAffinity(String),
AZAffinityReplicasAndPrimary(String),
AllNodes,
}
impl From<ReadFrom> for CoreReadFrom {
fn from(v: ReadFrom) -> Self {
match v {
ReadFrom::Primary => CoreReadFrom::Primary,
ReadFrom::PreferReplica => CoreReadFrom::PreferReplica,
ReadFrom::AZAffinity(az) => CoreReadFrom::AZAffinity(az),
ReadFrom::AZAffinityReplicasAndPrimary(az) => {
CoreReadFrom::AZAffinityReplicasAndPrimary(az)
}
ReadFrom::AllNodes => CoreReadFrom::AllNodes,
}
}
}
#[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 From<NodeAddress> for CoreNodeAddress {
fn from(a: NodeAddress) -> Self {
CoreNodeAddress {
host: a.host,
port: a.port,
}
}
}
#[derive(Clone, Default, PartialEq, Eq)]
pub struct ServerCredentials {
pub username: Option<String>,
pub password: Option<String>,
pub 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 new(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),
}
}
#[must_use]
pub fn with_password(mut self, password: impl Into<String>) -> Self {
self.password = Some(password.into());
self
}
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)]
pub enum ServiceType {
ElastiCache,
MemoryDB,
}
impl From<ServiceType> for CoreServiceType {
fn from(s: ServiceType) -> Self {
match s {
ServiceType::ElastiCache => CoreServiceType::ElastiCache,
ServiceType::MemoryDB => CoreServiceType::MemoryDB,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IamAuthConfig {
pub cluster_name: String,
pub region: String,
pub service_type: ServiceType,
pub 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
}
fn to_core(&self) -> IamAuthenticationConfig {
IamAuthenticationConfig {
cluster_name: self.cluster_name.clone(),
region: self.region.clone(),
service_type: self.service_type.into(),
refresh_interval_seconds: self.refresh_interval_seconds,
}
}
}
#[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 From<BackoffStrategy> for ConnectionRetryStrategy {
fn from(b: BackoffStrategy) -> Self {
ConnectionRetryStrategy {
exponent_base: b.exponent_base,
factor: b.factor,
number_of_retries: b.num_of_retries,
jitter_percent: b.jitter_percent,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PeriodicChecks {
#[default]
Enabled,
Disabled,
ManualInterval(u64),
}
impl From<PeriodicChecks> for PeriodicCheck {
fn from(p: PeriodicChecks) -> Self {
match p {
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 From<TlsConfig> for TlsMode {
fn from(t: TlsConfig) -> Self {
match t {
TlsConfig::NoTls => TlsMode::NoTls,
TlsConfig::SecureTls => TlsMode::SecureTls,
TlsConfig::InsecureTls => TlsMode::InsecureTls,
}
}
}
pub(crate) fn split_connection_addr(
addr: redis::ConnectionAddr,
) -> crate::error::Result<(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().cloned().map(Into::into).collect(),
tls_mode: Some(self.tls.into()),
read_from: Some(self.read_from.clone().into()),
protocol: Some(self.protocol.into()),
client_name: self.client_name.clone(),
lib_name: Some("GlideRust".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.into());
}
req
}
}
};
}
pub(crate) use impl_common_config_builders;