1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
pub use crate::protocol::types::{ClusterRouting, SlotRange};
use crate::{
  error::{RedisError, RedisErrorKind},
  types::RedisValue,
  utils,
};
use bytes_utils::Str;

macro_rules! parse_or_zero(
  ($data:ident, $t:ty) => {
    $data.parse::<$t>().ok().unwrap_or(0)
  }
);

fn parse_cluster_info_line(info: &mut ClusterInfo, line: &str) -> Result<(), RedisError> {
  let parts: Vec<&str> = line.split(':').collect();
  if parts.len() != 2 {
    return Err(RedisError::new(RedisErrorKind::Protocol, "Expected key:value pair."));
  }
  let (field, val) = (parts[0], parts[1]);

  match field {
    "cluster_state" => match val {
      "ok" => info.cluster_state = ClusterState::Ok,
      "fail" => info.cluster_state = ClusterState::Fail,
      _ => return Err(RedisError::new(RedisErrorKind::Protocol, "Invalid cluster state.")),
    },
    "cluster_slots_assigned" => info.cluster_slots_assigned = parse_or_zero!(val, u16),
    "cluster_slots_ok" => info.cluster_slots_ok = parse_or_zero!(val, u16),
    "cluster_slots_pfail" => info.cluster_slots_pfail = parse_or_zero!(val, u16),
    "cluster_slots_fail" => info.cluster_slots_fail = parse_or_zero!(val, u16),
    "cluster_known_nodes" => info.cluster_known_nodes = parse_or_zero!(val, u16),
    "cluster_size" => info.cluster_size = parse_or_zero!(val, u32),
    "cluster_current_epoch" => info.cluster_current_epoch = parse_or_zero!(val, u64),
    "cluster_my_epoch" => info.cluster_my_epoch = parse_or_zero!(val, u64),
    "cluster_stats_messages_sent" => info.cluster_stats_messages_sent = parse_or_zero!(val, u64),
    "cluster_stats_messages_received" => info.cluster_stats_messages_received = parse_or_zero!(val, u64),
    _ => {
      warn!("Invalid cluster info field: {}", line);
    },
  };

  Ok(())
}

/// The state of the cluster from the `CLUSTER INFO` command.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClusterState {
  Ok,
  Fail,
}

impl Default for ClusterState {
  fn default() -> Self {
    ClusterState::Ok
  }
}

/// A parsed response from the `CLUSTER INFO` command.
///
/// <https://redis.io/commands/cluster-info>
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct ClusterInfo {
  pub cluster_state:                   ClusterState,
  pub cluster_slots_assigned:          u16,
  pub cluster_slots_ok:                u16,
  pub cluster_slots_pfail:             u16,
  pub cluster_slots_fail:              u16,
  pub cluster_known_nodes:             u16,
  pub cluster_size:                    u32,
  pub cluster_current_epoch:           u64,
  pub cluster_my_epoch:                u64,
  pub cluster_stats_messages_sent:     u64,
  pub cluster_stats_messages_received: u64,
}

impl TryFrom<RedisValue> for ClusterInfo {
  type Error = RedisError;

  fn try_from(value: RedisValue) -> Result<Self, Self::Error> {
    if let Some(data) = value.as_bytes_str() {
      let mut out = ClusterInfo::default();

      for line in data.lines() {
        let trimmed = line.trim();
        if !trimmed.is_empty() {
          parse_cluster_info_line(&mut out, trimmed)?;
        }
      }
      Ok(out)
    } else {
      Err(RedisError::new(RedisErrorKind::Protocol, "Expected string response."))
    }
  }
}

/// Options for the CLUSTER FAILOVER command.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClusterFailoverFlag {
  Force,
  Takeover,
}

impl ClusterFailoverFlag {
  pub(crate) fn to_str(&self) -> Str {
    utils::static_str(match *self {
      ClusterFailoverFlag::Force => "FORCE",
      ClusterFailoverFlag::Takeover => "TAKEOVER",
    })
  }
}

/// Flags for the CLUSTER RESET command.
///
/// <https://redis.io/commands/cluster-reset>
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClusterResetFlag {
  Hard,
  Soft,
}

impl ClusterResetFlag {
  pub(crate) fn to_str(&self) -> Str {
    utils::static_str(match *self {
      ClusterResetFlag::Hard => "HARD",
      ClusterResetFlag::Soft => "SOFT",
    })
  }
}

/// Flags for the CLUSTER SETSLOT command.
///
/// <https://redis.io/commands/cluster-setslot>
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClusterSetSlotState {
  Importing,
  Migrating,
  Stable,
  Node(String),
}

impl ClusterSetSlotState {
  pub(crate) fn to_str(&self) -> (Str, Option<Str>) {
    let (prefix, value) = match *self {
      ClusterSetSlotState::Importing => ("IMPORTING", None),
      ClusterSetSlotState::Migrating => ("MIGRATING", None),
      ClusterSetSlotState::Stable => ("STABLE", None),
      ClusterSetSlotState::Node(ref n) => ("NODE", Some(n.into())),
    };

    (utils::static_str(prefix), value)
  }
}