use crate::ValkeyResult;
use crate::value::FromValkeyValue;
use crate::value::ValkeyValue;
use crate::value::to_glide_error;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ValueType {
None,
String,
List,
Set,
ZSet,
Hash,
Stream,
Unknown(String),
}
impl<T: AsRef<str>> From<T> for ValueType {
fn from(s: T) -> Self {
match s.as_ref() {
"none" => Self::None,
"string" => Self::String,
"list" => Self::List,
"set" => Self::Set,
"zset" => Self::ZSet,
"hash" => Self::Hash,
"stream" => Self::Stream,
s => Self::Unknown(s.to_string()),
}
}
}
impl From<ValueType> for String {
fn from(v: ValueType) -> Self {
match v {
ValueType::None => "none".to_string(),
ValueType::String => "string".to_string(),
ValueType::List => "list".to_string(),
ValueType::Set => "set".to_string(),
ValueType::ZSet => "zset".to_string(),
ValueType::Hash => "hash".to_string(),
ValueType::Stream => "stream".to_string(),
ValueType::Unknown(s) => s,
}
}
}
impl FromValkeyValue for ValueType {
fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
match value {
ValkeyValue::SimpleString(s) => Ok(s.into()),
other => Err(to_glide_error(
other,
"Value type should be a simple string.",
)),
}
}
}
#[cfg(test)]
mod value_type_tests {
use super::*;
#[test]
fn value_type_round_trips_known_names() {
for name in ["none", "string", "list", "set", "zset", "hash", "stream"] {
let value_type = ValueType::from(name);
assert!(!matches!(value_type, ValueType::Unknown(_)), "{name}");
assert_eq!(String::from(value_type), name);
}
}
#[test]
fn value_type_keeps_unknown_names() {
assert_eq!(
ValueType::from("vectorset"),
ValueType::Unknown("vectorset".to_string())
);
assert_eq!(String::from(ValueType::from("vectorset")), "vectorset");
}
#[test]
fn value_type_decodes_simple_string_only() {
let decoded =
ValueType::from_owned_valkey_value(ValkeyValue::SimpleString("zset".into())).unwrap();
assert_eq!(decoded, ValueType::ZSet);
assert!(
ValueType::from_owned_valkey_value(ValkeyValue::BulkString("zset".into())).is_err()
);
}
}
#[derive(Debug, PartialEq, Clone)]
#[non_exhaustive]
pub enum IntegerReplyOrNoOp {
IntegerReply(usize),
ExistsButNotRelevant,
NotExists,
}
impl IntegerReplyOrNoOp {
pub fn raw(&self) -> isize {
match self {
Self::IntegerReply(s) => *s as isize,
Self::ExistsButNotRelevant => -1,
Self::NotExists => -2,
}
}
fn eq_signed(&self, other: i64) -> bool {
match self {
Self::IntegerReply(s) => usize::try_from(other).is_ok_and(|o| o == *s),
Self::ExistsButNotRelevant => other == -1,
Self::NotExists => other == -2,
}
}
}
impl FromValkeyValue for IntegerReplyOrNoOp {
fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
match value {
ValkeyValue::Int(-2) => Ok(Self::NotExists),
ValkeyValue::Int(-1) => Ok(Self::ExistsButNotRelevant),
ValkeyValue::Int(s) => usize::try_from(s).map(Self::IntegerReply).map_err(|_| {
to_glide_error(
ValkeyValue::Int(s),
"Value should be a non-negative integer, -1, or -2.",
)
}),
other => Err(to_glide_error(other, "Value should be an integer.")),
}
}
}
impl PartialEq<isize> for IntegerReplyOrNoOp {
fn eq(&self, other: &isize) -> bool {
i64::try_from(*other).is_ok_and(|o| self.eq_signed(o))
}
}
impl PartialEq<usize> for IntegerReplyOrNoOp {
fn eq(&self, other: &usize) -> bool {
matches!(self, Self::IntegerReply(s) if s == other)
}
}
impl PartialEq<i32> for IntegerReplyOrNoOp {
fn eq(&self, other: &i32) -> bool {
self.eq_signed(i64::from(*other))
}
}
impl PartialEq<u32> for IntegerReplyOrNoOp {
fn eq(&self, other: &u32) -> bool {
matches!(self, Self::IntegerReply(s) if usize::try_from(*other).is_ok_and(|o| o == *s))
}
}
#[cfg(test)]
mod integer_reply_or_noop_tests {
use super::*;
#[test]
fn integer_reply_or_no_op_decodes_sentinels() {
assert_eq!(decode(-2), IntegerReplyOrNoOp::NotExists);
assert_eq!(decode(-1), IntegerReplyOrNoOp::ExistsButNotRelevant);
assert_eq!(decode(0), IntegerReplyOrNoOp::IntegerReply(0));
assert_eq!(decode(42), IntegerReplyOrNoOp::IntegerReply(42));
assert!(IntegerReplyOrNoOp::from_owned_valkey_value(ValkeyValue::Nil).is_err());
}
#[test]
fn integer_reply_or_no_op_rejects_other_negatives() {
for i in [-3, i64::MIN] {
assert!(IntegerReplyOrNoOp::from_owned_valkey_value(ValkeyValue::Int(i)).is_err());
}
}
#[test]
fn integer_reply_or_no_op_raw() {
assert_eq!(IntegerReplyOrNoOp::NotExists.raw(), -2);
assert_eq!(IntegerReplyOrNoOp::ExistsButNotRelevant.raw(), -1);
assert_eq!(IntegerReplyOrNoOp::IntegerReply(7).raw(), 7);
}
#[test]
fn integer_reply_or_no_op_comparisons() {
assert_eq!(IntegerReplyOrNoOp::NotExists, -2isize);
assert_eq!(IntegerReplyOrNoOp::ExistsButNotRelevant, -1i32);
assert_eq!(IntegerReplyOrNoOp::IntegerReply(7), 7usize);
assert_eq!(IntegerReplyOrNoOp::IntegerReply(7), 7u32);
assert_ne!(IntegerReplyOrNoOp::NotExists, 2usize);
let large = IntegerReplyOrNoOp::IntegerReply((1usize << 32) + 7);
assert_ne!(large, 7u32);
assert_ne!(large, 7i32);
assert_eq!(large, (1usize << 32) + 7);
assert_eq!(large, (1isize << 32) + 7);
let max = IntegerReplyOrNoOp::IntegerReply(usize::MAX);
assert_ne!(max, -1isize);
assert_ne!(max, -1i32);
}
fn decode(i: i64) -> IntegerReplyOrNoOp {
IntegerReplyOrNoOp::from_owned_valkey_value(ValkeyValue::Int(i)).unwrap()
}
}