use std::fmt;
use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize};
use super::error::ConfError;
macro_rules! string_enum_serde {
($t:ty) => {
impl Serialize for $t {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for $t {
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
struct V;
impl Visitor<'_> for V {
type Value = $t;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(concat!("a string naming a ", stringify!($t)))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
<$t>::parse(v).map_err(|e| E::custom(e.to_string()))
}
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
self.visit_str(&v)
}
}
de.deserialize_str(V)
}
}
};
}
string_enum_serde!(SecureServerOption);
string_enum_serde!(HashType);
string_enum_serde!(Distribution);
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Distribution {
Vnode,
Ketama,
Modula,
Random,
RandomSlicing,
}
impl Default for Distribution {
fn default() -> Self {
Self::Vnode
}
}
impl Distribution {
pub fn parse(s: &str) -> Result<Self, ConfError> {
Ok(match s.to_ascii_lowercase().as_str() {
"vnode" => Distribution::Vnode,
"ketama" => Distribution::Ketama,
"modula" => Distribution::Modula,
"random" => Distribution::Random,
"random_slicing" | "random-slicing" => Distribution::RandomSlicing,
_ => return Err(ConfError::BadDistribution(s.to_string())),
})
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Distribution::Vnode => "vnode",
Distribution::Ketama => "ketama",
Distribution::Modula => "modula",
Distribution::Random => "random",
Distribution::RandomSlicing => "random_slicing",
}
}
#[must_use]
pub const fn is_supported(self) -> bool {
matches!(self, Distribution::Vnode | Distribution::RandomSlicing)
}
}
impl fmt::Display for Distribution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum DataStore {
Redis,
Memcache,
Noxu,
}
impl DataStore {
pub fn from_int(v: i64) -> Result<Self, ConfError> {
match v {
0 => Ok(DataStore::Redis),
1 => Ok(DataStore::Memcache),
2 => Ok(DataStore::Noxu),
n => Err(ConfError::BadDataStore(n)),
}
}
pub fn from_name(s: &str) -> Result<Self, ConfError> {
if s.eq_ignore_ascii_case("redis") {
Ok(DataStore::Redis)
} else if s.eq_ignore_ascii_case("memcache") || s.eq_ignore_ascii_case("memcached") {
Ok(DataStore::Memcache)
} else if s.eq_ignore_ascii_case("noxu") {
Ok(DataStore::Noxu)
} else {
Err(ConfError::BadDataStore(-1))
}
}
pub fn as_int(self) -> i64 {
match self {
DataStore::Redis => 0,
DataStore::Memcache => 1,
DataStore::Noxu => 2,
}
}
pub fn as_name(self) -> &'static str {
match self {
DataStore::Redis => "redis",
DataStore::Memcache => "memcache",
DataStore::Noxu => "noxu",
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum SecureServerOption {
None,
Rack,
Datacenter,
All,
}
impl SecureServerOption {
pub fn parse(s: &str) -> Result<Self, ConfError> {
match s {
"none" => Ok(SecureServerOption::None),
"rack" => Ok(SecureServerOption::Rack),
"datacenter" => Ok(SecureServerOption::Datacenter),
"all" => Ok(SecureServerOption::All),
other => Err(ConfError::BadSecure(other.to_string())),
}
}
pub fn as_str(self) -> &'static str {
match self {
SecureServerOption::None => "none",
SecureServerOption::Rack => "rack",
SecureServerOption::Datacenter => "datacenter",
SecureServerOption::All => "all",
}
}
}
impl fmt::Display for SecureServerOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum ConsistencyLevel {
DcOne,
DcQuorum,
DcSafeQuorum,
DcEachSafeQuorum,
}
impl ConsistencyLevel {
pub fn parse(field: &'static str, s: &str) -> Result<Self, ConfError> {
if s.eq_ignore_ascii_case("dc_one") {
Ok(ConsistencyLevel::DcOne)
} else if s.eq_ignore_ascii_case("dc_quorum") {
Ok(ConsistencyLevel::DcQuorum)
} else if s.eq_ignore_ascii_case("dc_safe_quorum") {
Ok(ConsistencyLevel::DcSafeQuorum)
} else if s.eq_ignore_ascii_case("dc_each_safe_quorum") {
Ok(ConsistencyLevel::DcEachSafeQuorum)
} else {
Err(ConfError::BadConsistency {
field,
value: s.to_string(),
})
}
}
pub fn as_str(self) -> &'static str {
match self {
ConsistencyLevel::DcOne => "DC_ONE",
ConsistencyLevel::DcQuorum => "DC_QUORUM",
ConsistencyLevel::DcSafeQuorum => "DC_SAFE_QUORUM",
ConsistencyLevel::DcEachSafeQuorum => "DC_EACH_SAFE_QUORUM",
}
}
}
impl fmt::Display for ConsistencyLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum HashType {
OneAtATime,
Md5,
Crc16,
Crc32,
Crc32a,
Fnv1_64,
Fnv1a64,
Fnv1_32,
Fnv1a32,
Hsieh,
Murmur,
Jenkins,
Murmur3,
#[allow(non_camel_case_types)]
Murmur3X64_64,
}
impl HashType {
pub fn parse(s: &str) -> Result<Self, ConfError> {
Ok(match s {
"one_at_a_time" => HashType::OneAtATime,
"md5" => HashType::Md5,
"crc16" => HashType::Crc16,
"crc32" => HashType::Crc32,
"crc32a" => HashType::Crc32a,
"fnv1_64" => HashType::Fnv1_64,
"fnv1a_64" => HashType::Fnv1a64,
"fnv1_32" => HashType::Fnv1_32,
"fnv1a_32" => HashType::Fnv1a32,
"hsieh" => HashType::Hsieh,
"murmur" => HashType::Murmur,
"jenkins" => HashType::Jenkins,
"murmur3" => HashType::Murmur3,
"murmur3_x64_64" => HashType::Murmur3X64_64,
other => return Err(ConfError::BadHash(other.to_string())),
})
}
pub fn as_str(self) -> &'static str {
match self {
HashType::OneAtATime => "one_at_a_time",
HashType::Md5 => "md5",
HashType::Crc16 => "crc16",
HashType::Crc32 => "crc32",
HashType::Crc32a => "crc32a",
HashType::Fnv1_64 => "fnv1_64",
HashType::Fnv1a64 => "fnv1a_64",
HashType::Fnv1_32 => "fnv1_32",
HashType::Fnv1a32 => "fnv1a_32",
HashType::Hsieh => "hsieh",
HashType::Murmur => "murmur",
HashType::Jenkins => "jenkins",
HashType::Murmur3 => "murmur3",
HashType::Murmur3X64_64 => "murmur3_x64_64",
}
}
}
impl fmt::Display for HashType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn data_store_round_trip() {
assert_eq!(DataStore::from_int(0).unwrap(), DataStore::Redis);
assert_eq!(DataStore::from_int(1).unwrap(), DataStore::Memcache);
assert_eq!(DataStore::from_int(2).unwrap(), DataStore::Noxu);
assert!(matches!(
DataStore::from_int(7),
Err(ConfError::BadDataStore(7))
));
assert_eq!(DataStore::from_name("noxu").unwrap(), DataStore::Noxu);
assert_eq!(DataStore::from_name("REDIS").unwrap(), DataStore::Redis);
assert!(DataStore::from_name("sql").is_err());
assert_eq!(DataStore::Noxu.as_name(), "noxu");
}
#[test]
fn secure_round_trip() {
for s in ["none", "rack", "datacenter", "all"] {
assert_eq!(SecureServerOption::parse(s).unwrap().as_str(), s);
}
assert!(SecureServerOption::parse("nope").is_err());
}
#[test]
fn consistency_case_insensitive() {
assert_eq!(
ConsistencyLevel::parse("read_consistency", "dc_one").unwrap(),
ConsistencyLevel::DcOne
);
assert_eq!(
ConsistencyLevel::parse("read_consistency", "DC_SAFE_QUORUM").unwrap(),
ConsistencyLevel::DcSafeQuorum
);
assert!(ConsistencyLevel::parse("read_consistency", "garbage").is_err());
}
#[test]
fn hash_round_trip() {
for &name in &[
"one_at_a_time",
"md5",
"crc16",
"crc32",
"crc32a",
"fnv1_64",
"fnv1a_64",
"fnv1_32",
"fnv1a_32",
"hsieh",
"murmur",
"jenkins",
"murmur3",
"murmur3_x64_64",
] {
assert_eq!(HashType::parse(name).unwrap().as_str(), name);
}
}
#[test]
fn distribution_round_trip() {
for &name in &["vnode", "ketama", "modula", "random", "random_slicing"] {
assert_eq!(Distribution::parse(name).unwrap().as_str(), name);
}
assert_eq!(Distribution::parse("VNODE").unwrap(), Distribution::Vnode);
assert_eq!(
Distribution::parse("random-slicing").unwrap(),
Distribution::RandomSlicing
);
assert!(matches!(
Distribution::parse("sphere"),
Err(ConfError::BadDistribution(_))
));
assert!(Distribution::Vnode.is_supported());
assert!(Distribution::RandomSlicing.is_supported());
assert!(!Distribution::Ketama.is_supported());
}
#[test]
fn distribution_default_is_vnode() {
assert_eq!(Distribution::default(), Distribution::Vnode);
}
#[test]
fn distribution_yaml_round_trip() {
let raw = serde_yaml::to_string(&Distribution::RandomSlicing).unwrap();
let parsed: Distribution = serde_yaml::from_str(&raw).unwrap();
assert_eq!(parsed, Distribution::RandomSlicing);
}
}