1use glide_core::client::AuthenticationInfo;
5use glide_core::client::ConnectionRetryStrategy;
6use glide_core::client::IamAuthenticationConfig;
7use glide_core::client::NodeAddress as CoreNodeAddress;
8use glide_core::client::NodeDiscoveryMode as CoreNodeDiscoveryMode;
9use glide_core::client::PeriodicCheck;
10use glide_core::client::ReadFrom as CoreReadFrom;
11use glide_core::client::TlsMode;
12use glide_core::iam::ServiceType as CoreServiceType;
13use std::collections::HashMap;
14use std::collections::HashSet;
15use std::time::Duration;
16
17pub(crate) const LIB_NAME: &str = "GlideRust";
19
20pub(crate) const LIB_VERSION: &str = env!("CARGO_PKG_VERSION");
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum PubSubChannelMode {
28 Exact,
30 Pattern,
32 Sharded,
34}
35
36impl PubSubChannelMode {
37 fn to_core(self) -> redis::PubSubSubscriptionKind {
38 match self {
39 PubSubChannelMode::Exact => redis::PubSubSubscriptionKind::Exact,
40 PubSubChannelMode::Pattern => redis::PubSubSubscriptionKind::Pattern,
41 PubSubChannelMode::Sharded => redis::PubSubSubscriptionKind::Sharded,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Default)]
54pub struct PubSubSubscriptions {
55 channels: HashMap<PubSubChannelMode, HashSet<Vec<u8>>>,
56}
57
58impl PubSubSubscriptions {
59 pub fn new() -> Self {
61 PubSubSubscriptions::default()
62 }
63
64 pub fn subscribe(mut self, mode: PubSubChannelMode, channel: impl Into<Vec<u8>>) -> Self {
66 self.channels
67 .entry(mode)
68 .or_default()
69 .insert(channel.into());
70 self
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.channels.values().all(|s| s.is_empty())
76 }
77
78 pub(crate) fn to_core(&self) -> redis::PubSubSubscriptionInfo {
79 let mut info: redis::PubSubSubscriptionInfo = HashMap::new();
80 for (mode, set) in &self.channels {
81 info.insert(mode.to_core(), set.clone());
82 }
83 info
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
89pub enum ProtocolVersion {
90 RESP2,
92 #[default]
94 RESP3,
95}
96
97impl ProtocolVersion {
98 pub(crate) fn to_core(self) -> redis::ProtocolVersion {
99 match self {
100 ProtocolVersion::RESP2 => redis::ProtocolVersion::RESP2,
101 ProtocolVersion::RESP3 => redis::ProtocolVersion::RESP3,
102 }
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Default)]
108#[non_exhaustive]
109pub enum ReadFrom {
110 #[default]
112 Primary,
113 PreferReplica,
115 AZAffinity(String),
117 AZAffinityReplicasAndPrimary(String),
119 AZAffinityAllNodes(String),
122 AllNodes,
124}
125
126impl ReadFrom {
127 pub(crate) fn to_core(&self) -> CoreReadFrom {
128 match self {
129 ReadFrom::Primary => CoreReadFrom::Primary,
130 ReadFrom::PreferReplica => CoreReadFrom::PreferReplica,
131 ReadFrom::AZAffinity(az) => CoreReadFrom::AZAffinity(az.clone()),
132 ReadFrom::AZAffinityReplicasAndPrimary(az) => {
133 CoreReadFrom::AZAffinityReplicasAndPrimary(az.clone())
134 }
135 ReadFrom::AZAffinityAllNodes(az) => CoreReadFrom::AZAffinityAllNodes(az.clone()),
136 ReadFrom::AllNodes => CoreReadFrom::AllNodes,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
143#[non_exhaustive]
144pub enum NodeDiscoveryMode {
145 #[default]
147 Standard,
148
149 Static,
152
153 DiscoverAll,
155}
156
157impl NodeDiscoveryMode {
158 pub(crate) fn to_core(self) -> CoreNodeDiscoveryMode {
159 match self {
160 NodeDiscoveryMode::Standard => CoreNodeDiscoveryMode::Standard,
161 NodeDiscoveryMode::Static => CoreNodeDiscoveryMode::Static,
162 NodeDiscoveryMode::DiscoverAll => CoreNodeDiscoveryMode::DiscoverAll,
163 }
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct NodeAddress {
172 pub host: String,
174 pub port: u16,
176}
177
178impl NodeAddress {
179 pub fn new(host: impl Into<String>, port: u16) -> Self {
181 NodeAddress {
182 host: host.into(),
183 port,
184 }
185 }
186}
187
188impl Default for NodeAddress {
189 fn default() -> Self {
190 NodeAddress::new("localhost", 6379)
191 }
192}
193
194impl NodeAddress {
195 pub(crate) fn to_core(&self) -> CoreNodeAddress {
196 CoreNodeAddress {
197 host: self.host.clone(),
198 port: self.port,
199 }
200 }
201}
202
203#[derive(Clone, Default, PartialEq, Eq)]
206pub struct ServerCredentials {
207 username: Option<String>,
211 pub(crate) password: Option<String>,
214 iam_config: Option<IamAuthConfig>,
217}
218
219impl ServerCredentials {
220 pub fn password(password: impl Into<String>) -> Self {
223 ServerCredentials {
224 username: None,
225 password: Some(password.into()),
226 iam_config: None,
227 }
228 }
229
230 pub fn username_password(username: impl Into<String>, password: impl Into<String>) -> Self {
233 ServerCredentials {
234 username: Some(username.into()),
235 password: Some(password.into()),
236 iam_config: None,
237 }
238 }
239
240 pub fn iam(username: impl Into<String>, iam_config: IamAuthConfig) -> Self {
242 ServerCredentials {
243 username: Some(username.into()),
244 password: None,
245 iam_config: Some(iam_config),
246 }
247 }
248
249 pub fn username(&self) -> Option<&str> {
251 self.username.as_deref()
252 }
253
254 pub fn iam_config(&self) -> Option<&IamAuthConfig> {
256 self.iam_config.as_ref()
257 }
258
259 pub fn is_iam_auth(&self) -> bool {
261 self.iam_config.is_some()
262 }
263
264 pub(crate) fn to_core(&self) -> AuthenticationInfo {
265 AuthenticationInfo {
266 username: self.username.clone(),
267 password: self.password.clone(),
268 iam_config: self.iam_config.as_ref().map(IamAuthConfig::to_core),
269 }
270 }
271}
272
273impl std::fmt::Debug for ServerCredentials {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277 f.debug_struct("ServerCredentials")
278 .field("username", &self.username)
279 .field("password", &self.password.as_ref().map(|_| "<redacted>"))
280 .field("iam_config", &self.iam_config)
281 .finish()
282 }
283}
284
285#[derive(Clone)]
291pub struct ClientIdentity {
292 cert_pem: Vec<u8>,
293 key_pem: Vec<u8>,
294}
295
296impl ClientIdentity {
297 pub fn new(cert_pem: impl Into<Vec<u8>>, key_pem: impl Into<Vec<u8>>) -> Self {
299 ClientIdentity {
300 cert_pem: cert_pem.into(),
301 key_pem: key_pem.into(),
302 }
303 }
304
305 pub fn cert_pem(&self) -> &[u8] {
307 &self.cert_pem
308 }
309
310 pub(crate) fn key_pem(&self) -> &[u8] {
311 &self.key_pem
312 }
313}
314
315impl std::fmt::Debug for ClientIdentity {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 f.debug_struct("ClientIdentity")
320 .field("cert_pem", &format_args!("[{} bytes]", self.cert_pem.len()))
321 .field("key_pem", &"<redacted>")
322 .finish()
323 }
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328#[non_exhaustive]
329pub enum ServiceType {
330 ElastiCache,
332 MemoryDB,
334}
335
336impl ServiceType {
337 pub(crate) fn to_core(self) -> CoreServiceType {
338 match self {
339 ServiceType::ElastiCache => CoreServiceType::ElastiCache,
340 ServiceType::MemoryDB => CoreServiceType::MemoryDB,
341 }
342 }
343}
344
345#[derive(Debug, Clone, PartialEq, Eq)]
347pub struct IamAuthConfig {
348 cluster_name: String,
350 region: String,
352 service_type: ServiceType,
354 refresh_interval_seconds: Option<u32>,
357}
358
359impl IamAuthConfig {
360 pub fn new(
363 cluster_name: impl Into<String>,
364 region: impl Into<String>,
365 service_type: ServiceType,
366 ) -> Self {
367 IamAuthConfig {
368 cluster_name: cluster_name.into(),
369 region: region.into(),
370 service_type,
371 refresh_interval_seconds: None,
372 }
373 }
374
375 #[must_use]
377 pub fn with_refresh_interval_seconds(mut self, seconds: u32) -> Self {
378 self.refresh_interval_seconds = Some(seconds);
379 self
380 }
381
382 pub fn cluster_name(&self) -> &str {
384 &self.cluster_name
385 }
386
387 pub fn region(&self) -> &str {
389 &self.region
390 }
391
392 pub fn service_type(&self) -> ServiceType {
394 self.service_type
395 }
396
397 pub fn refresh_interval_seconds(&self) -> Option<u32> {
399 self.refresh_interval_seconds
400 }
401
402 fn to_core(&self) -> IamAuthenticationConfig {
403 IamAuthenticationConfig {
404 cluster_name: self.cluster_name.clone(),
405 region: self.region.clone(),
406 service_type: self.service_type.to_core(),
407 refresh_interval_seconds: self.refresh_interval_seconds,
408 credentials_provider: None,
411 }
412 }
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
419pub struct BackoffStrategy {
420 pub num_of_retries: u32,
422 pub factor: u32,
424 pub exponent_base: u32,
426 pub jitter_percent: Option<u32>,
428}
429
430impl BackoffStrategy {
431 pub(crate) fn to_core(self) -> ConnectionRetryStrategy {
432 ConnectionRetryStrategy {
433 exponent_base: self.exponent_base,
434 factor: self.factor,
435 number_of_retries: self.num_of_retries,
436 jitter_percent: self.jitter_percent,
437 }
438 }
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
445pub enum PeriodicChecks {
446 #[default]
448 Enabled,
449 Disabled,
451 ManualInterval(u64),
453}
454
455impl PeriodicChecks {
456 pub(crate) fn to_core(self) -> PeriodicCheck {
457 match self {
458 PeriodicChecks::Enabled => PeriodicCheck::Enabled,
459 PeriodicChecks::Disabled => PeriodicCheck::Disabled,
460 PeriodicChecks::ManualInterval(secs) => {
461 PeriodicCheck::ManualInterval(Duration::from_secs(secs))
462 }
463 }
464 }
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
469pub enum TlsConfig {
470 #[default]
472 NoTls,
473 SecureTls,
475 InsecureTls,
477}
478
479impl TlsConfig {
480 pub(crate) fn to_core(self) -> TlsMode {
481 match self {
482 TlsConfig::NoTls => TlsMode::NoTls,
483 TlsConfig::SecureTls => TlsMode::SecureTls,
484 TlsConfig::InsecureTls => TlsMode::InsecureTls,
485 }
486 }
487}
488
489pub(crate) fn to_redis_connection_info(
493 url: impl AsRef<str>,
494) -> crate::ValkeyResult<redis::ConnectionInfo> {
495 redis::IntoConnectionInfo::into_connection_info(url.as_ref())
496 .map_err(|e| crate::error::GlideError::Configuration(e.to_string()))
497}
498
499pub(crate) fn split_connection_addr(
501 addr: redis::ConnectionAddr,
502) -> crate::ValkeyResult<(NodeAddress, TlsConfig)> {
503 match addr {
504 redis::ConnectionAddr::Tcp(host, port) => {
505 Ok((NodeAddress::new(host, port), TlsConfig::NoTls))
506 }
507 redis::ConnectionAddr::TcpTls {
508 host,
509 port,
510 insecure,
511 tls_params,
512 } => {
513 if tls_params.is_some() {
519 return Err(crate::error::GlideError::Configuration(
520 "ConnectionInfo carries TLS certificate parameters (TlsCertificates) that \
521 cannot be mapped; configure them via the config's `root_certs` field and \
522 `client_identity(cert, key)` instead"
523 .into(),
524 ));
525 }
526 let tls = if insecure {
527 TlsConfig::InsecureTls
528 } else {
529 TlsConfig::SecureTls
530 };
531 Ok((NodeAddress::new(host, port), tls))
532 }
533 redis::ConnectionAddr::Unix(_) => Err(crate::error::GlideError::Configuration(
534 "unix-socket connections are not supported by glide-core".into(),
535 )),
536 }
537}
538
539pub(crate) fn from_redis_protocol(p: redis::ProtocolVersion) -> ProtocolVersion {
541 match p {
542 redis::ProtocolVersion::RESP2 => ProtocolVersion::RESP2,
543 redis::ProtocolVersion::RESP3 => ProtocolVersion::RESP3,
544 }
545}
546
547pub(crate) fn credentials_from_info(
550 username: Option<String>,
551 password: Option<String>,
552) -> Option<ServerCredentials> {
553 match (username, password) {
554 (None, None) => None,
555 (username, password) => Some(ServerCredentials {
556 username,
557 password,
558 iam_config: None,
559 }),
560 }
561}
562
563pub(crate) fn duration_as_millis_u32(d: Duration) -> u32 {
568 u32::try_from(d.as_millis()).unwrap_or(u32::MAX)
569}
570
571macro_rules! impl_common_config_builders {
583 ($ty:ty) => {
584 impl $ty {
585 pub fn with_address(host: impl Into<String>, port: u16) -> Self {
587 Self::new(vec![NodeAddress::new(host, port)])
588 }
589
590 pub fn tls(mut self, tls: TlsConfig) -> Self {
592 self.tls = tls;
593 self
594 }
595
596 pub fn tls_ca_cert(mut self, pem: impl Into<Vec<u8>>) -> Self {
601 self.root_certs.push(pem.into());
602 self
603 }
604
605 #[must_use]
609 pub fn client_identity(
610 mut self,
611 cert_pem: impl Into<Vec<u8>>,
612 key_pem: impl Into<Vec<u8>>,
613 ) -> Self {
614 self.client_identity = Some(ClientIdentity::new(cert_pem, key_pem));
615 self
616 }
617
618 pub fn credentials(mut self, creds: ServerCredentials) -> Self {
620 self.credentials = Some(creds);
621 self
622 }
623
624 pub fn read_from(mut self, read_from: ReadFrom) -> Self {
626 self.read_from = read_from;
627 self
628 }
629
630 pub fn request_timeout(mut self, timeout: Duration) -> Self {
632 self.request_timeout = Some(timeout);
633 self
634 }
635
636 pub fn connection_timeout(mut self, timeout: Duration) -> Self {
638 self.connection_timeout = Some(timeout);
639 self
640 }
641
642 pub fn inflight_requests_limit(mut self, limit: u32) -> Self {
644 self.inflight_requests_limit = Some(limit);
645 self
646 }
647
648 pub fn client_name(mut self, name: impl Into<String>) -> Self {
650 self.client_name = Some(name.into());
651 self
652 }
653
654 pub fn protocol(mut self, protocol: ProtocolVersion) -> Self {
656 self.protocol = protocol;
657 self
658 }
659
660 pub fn lazy_connect(mut self, lazy: bool) -> Self {
662 self.lazy_connect = lazy;
663 self
664 }
665
666 pub fn reconnect_strategy(mut self, strategy: BackoffStrategy) -> Self {
668 self.reconnect_strategy = Some(strategy);
669 self
670 }
671
672 pub fn subscriptions(mut self, subscriptions: PubSubSubscriptions) -> Self {
674 self.pubsub_subscriptions = Some(subscriptions);
675 self
676 }
677
678 #[must_use]
686 pub fn enable_pubsub(mut self) -> Self {
687 self.force_pubsub_channel = true;
688 self
689 }
690
691 pub(crate) fn common_request(&self) -> glide_core::client::ConnectionRequest {
695 use glide_core::client::ConnectionRequest;
696 let mut req = ConnectionRequest {
700 addresses: self.addresses.iter().map(NodeAddress::to_core).collect(),
701 tls_mode: Some(self.tls.to_core()),
702 read_from: Some(self.read_from.to_core()),
703 protocol: Some(self.protocol.to_core()),
704 client_name: self.client_name.clone(),
705 lib_name: Some($crate::config::common::LIB_NAME.to_string()),
706 lib_ver: Some($crate::config::common::LIB_VERSION.to_string()),
707 lazy_connect: self.lazy_connect,
708 inflight_requests_limit: self.inflight_requests_limit,
709 tcp_nodelay: true,
717 ..ConnectionRequest::default()
724 };
725
726 if !self.root_certs.is_empty() {
727 req.root_certs = self.root_certs.clone();
728 }
729 if let Some(identity) = &self.client_identity {
730 req.client_cert = identity.cert_pem().to_vec();
731 req.client_key = identity.key_pem().to_vec();
732 }
733 if let Some(subs) = &self.pubsub_subscriptions {
734 req.pubsub_subscriptions = Some(subs.to_core());
735 }
736 if let Some(creds) = &self.credentials {
737 req.authentication_info = Some(creds.to_core());
738 }
739 if let Some(t) = self.request_timeout {
740 req.request_timeout = Some(duration_as_millis_u32(t));
741 }
742 if let Some(t) = self.connection_timeout {
743 req.connection_timeout = Some(duration_as_millis_u32(t));
744 }
745 if let Some(strategy) = self.reconnect_strategy {
746 req.connection_retry_strategy = Some(strategy.to_core());
747 }
748 req
749 }
750 }
751 };
752}
753
754pub(crate) use impl_common_config_builders;