use std::collections::HashMap;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
use crate::client::Client;
use crate::error::{Error, Result};
use crate::response::decode_variant;
use crate::wire::{byte_list, byte_lists, decode_byte_list, decode_byte_lists, decode_pair};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct CacheKeyInfo {
pub key: String,
#[serde(default)]
pub ttl_ms: Option<i64>,
#[serde(default)]
pub bytes: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CachePair {
pub field: Vec<u8>,
pub value: Vec<u8>,
}
impl CachePair {
pub fn new(field: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) -> Self {
Self {
field: field.into(),
value: value.into(),
}
}
pub fn from_text<'a>(entries: impl IntoIterator<Item = (&'a str, &'a str)>) -> Vec<CachePair> {
entries
.into_iter()
.map(|(f, v)| CachePair::new(f.as_bytes(), v.as_bytes()))
.collect()
}
pub fn field_text(&self) -> Option<&str> {
std::str::from_utf8(&self.field).ok()
}
pub fn value_text(&self) -> Option<&str> {
std::str::from_utf8(&self.value).ok()
}
fn to_wire(&self) -> Value {
Value::Array(vec![byte_list(&self.field), byte_list(&self.value)])
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamEntry {
pub id: String,
pub fields: Vec<CachePair>,
}
impl StreamEntry {
pub fn text(&self) -> HashMap<String, String> {
self.fields
.iter()
.map(|p| {
(
String::from_utf8_lossy(&p.field).into_owned(),
String::from_utf8_lossy(&p.value).into_owned(),
)
})
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CacheEntry {
pub key: String,
pub value: Vec<u8>,
}
impl CacheEntry {
pub fn new(key: impl Into<String>, value: impl Into<Vec<u8>>) -> Self {
Self {
key: key.into(),
value: value.into(),
}
}
fn to_wire(&self) -> Value {
json!({"key": self.key, "value": byte_list(&self.value)})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
pub enum SetCondition {
#[default]
Always,
IfAbsent,
IfPresent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheExpiry {
Keep,
Set(Duration),
Clear,
}
impl CacheExpiry {
fn to_wire(self) -> Value {
match self {
CacheExpiry::Keep => json!("Keep"),
CacheExpiry::Set(ttl) => json!({"Set": {"ttl_ms": millis(ttl)}}),
CacheExpiry::Clear => json!("Clear"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum ListEnd {
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum BitOpKind {
And,
Or,
Xor,
Not,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ScoredMember {
pub score: f64,
pub member: Vec<u8>,
}
impl ScoredMember {
pub fn new(score: f64, member: impl Into<Vec<u8>>) -> Self {
Self {
score,
member: member.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScoreBound {
pub value: f64,
pub exclusive: bool,
}
impl ScoreBound {
pub fn inclusive(value: f64) -> Self {
Self {
value,
exclusive: false,
}
}
pub fn exclusive(value: f64) -> Self {
Self {
value,
exclusive: true,
}
}
pub fn lowest() -> Self {
Self::inclusive(-f64::MAX)
}
pub fn highest() -> Self {
Self::inclusive(f64::MAX)
}
fn to_wire(self, name: &str) -> Result<Value> {
Ok(json!({"value": score_value(self.value, name)?, "exclusive": self.exclusive}))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct ZAddCounts {
#[serde(default)]
pub added: i64,
#[serde(default)]
pub updated: i64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct WarmCounts {
#[serde(default)]
pub written: i64,
#[serde(default, rename = "alreadyCached")]
pub already_cached: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FreshState {
Fresh,
Stale,
Missing,
Miss,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FreshRead {
pub state: FreshState,
pub value: Option<Vec<u8>>,
pub refresh: bool,
pub lease: bool,
pub retry_after_ms: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct CacheLock {
#[serde(default)]
pub acquired: bool,
#[serde(default)]
pub token: Option<u64>,
#[serde(default)]
pub holder: Option<String>,
#[serde(default, rename = "expiresAtMs")]
pub expires_at_ms: Option<i64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
pub struct CacheLockRenewal {
#[serde(default)]
pub renewed: bool,
#[serde(default)]
pub token: Option<u64>,
#[serde(default, rename = "expiresAtMs")]
pub expires_at_ms: Option<i64>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct TrimCounts {
#[serde(default)]
pub length: i64,
#[serde(default)]
pub removed: i64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct ExpireAtOutcome {
#[serde(default)]
pub updated: bool,
#[serde(default)]
pub deleted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompareAndSwap {
pub swapped: bool,
pub current: Option<Vec<u8>>,
pub current_is_binary: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
pub struct CacheMemoryStats {
#[serde(default)]
pub enabled: bool,
#[serde(default, rename = "maxBytes")]
pub max_bytes: Option<u64>,
#[serde(default, rename = "lowWaterBytes")]
pub low_water_bytes: Option<u64>,
#[serde(default, rename = "residentBytes")]
pub resident_bytes: Option<u64>,
#[serde(default)]
pub measurements: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
pub struct CacheEvictionStats {
#[serde(default)]
pub policy: String,
#[serde(default)]
pub evicted: u64,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
pub struct CacheStats {
#[serde(default)]
pub hits: u64,
#[serde(default)]
pub misses: u64,
#[serde(default, rename = "hitRatio")]
pub hit_ratio: Option<f64>,
#[serde(default)]
pub memory: CacheMemoryStats,
#[serde(default)]
pub eviction: CacheEvictionStats,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct NamespaceLimit {
#[serde(default)]
pub namespace: String,
#[serde(default)]
pub keys: u64,
#[serde(default)]
pub bytes: u64,
#[serde(default)]
pub max_keys: Option<u64>,
#[serde(default)]
pub max_bytes: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct ChannelMessage {
#[serde(default)]
pub id: u64,
#[serde(default)]
pub channel: String,
#[serde(default)]
pub message: String,
#[serde(default)]
pub at_ms: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
pub struct PolledMessages {
#[serde(default)]
pub messages: Vec<ChannelMessage>,
#[serde(default)]
pub next: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub struct CacheCommand(Value);
impl CacheCommand {
pub fn new(variant: &str, body: Value) -> Self {
CacheCommand(json!({variant: body}))
}
pub fn unit(variant: &str) -> Self {
CacheCommand(Value::String(variant.to_string()))
}
pub fn get(namespace: &str, key: &str) -> Self {
Self::new("Get", Value::Object(ns_key(namespace, key)))
}
pub fn set(namespace: &str, key: &str, value: &[u8], ttl: Option<Duration>) -> Self {
let mut body = ns_key(namespace, key);
body.insert("value".into(), byte_list(value));
body.insert("ttl_ms".into(), ttl_value(ttl));
Self::new("Set", Value::Object(body))
}
pub fn delete(namespace: &str, key: &str) -> Self {
Self::new("Delete", Value::Object(ns_key(namespace, key)))
}
pub fn exists(namespace: &str, key: &str) -> Self {
Self::new("Exists", Value::Object(ns_key(namespace, key)))
}
pub fn incr(namespace: &str, key: &str, by: i64) -> Self {
let mut body = ns_key(namespace, key);
body.insert("by".into(), json!(by));
Self::new("Incr", Value::Object(body))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CacheReply {
Ok {
kind: String,
data: Value,
},
Error(String),
}
impl CacheReply {
pub fn is_ok(&self) -> bool {
matches!(self, CacheReply::Ok { .. })
}
pub fn value(&self) -> Option<Vec<u8>> {
match self {
CacheReply::Ok { kind, data } if kind == "CacheValue" => decode_byte_list(data).ok(),
_ => None,
}
}
fn from_wire(value: &Value) -> Result<CacheReply> {
if let Some(error) = value.get("error") {
return Ok(CacheReply::Error(match error.as_str() {
Some(text) => text.to_string(),
None => error.to_string(),
}));
}
match value.get("ok") {
Some(data) => {
let (kind, data) = decode_variant(data.clone());
Ok(CacheReply::Ok { kind, data })
}
None => Err(Error::protocol(
"a pipeline result is neither `ok` nor `error`".to_string(),
)),
}
}
}
fn millis(duration: Duration) -> i64 {
duration.as_millis().min(i64::MAX as u128) as i64
}
fn score_value(score: f64, name: &str) -> Result<Value> {
serde_json::Number::from_f64(score)
.map(Value::Number)
.ok_or_else(|| Error::invalid(format!("`{name}` must be a finite number, got {score}")))
}
fn keys_body(namespace: &str, keys: &[&str]) -> Map<String, Value> {
let mut body = Map::new();
body.insert("namespace".into(), Value::String(namespace.to_string()));
body.insert("keys".into(), json!(keys));
body
}
fn range_value(range: Option<(i64, i64)>) -> Value {
range.map_or(Value::Null, |(start, end)| json!([start, end]))
}
fn decode_hex(text: &str) -> Result<Vec<u8>> {
let malformed = || Error::protocol("malformed hex value in a cache reply".to_string());
if text.len() % 2 != 0 || !text.is_ascii() {
return Err(malformed());
}
(0..text.len())
.step_by(2)
.map(|i| u8::from_str_radix(&text[i..i + 2], 16).map_err(|_| malformed()))
.collect()
}
fn scored_members(value: &Value, what: &str) -> Result<Vec<ScoredMember>> {
let malformed = || Error::protocol(format!("malformed {what} payload: no scored members"));
value
.get("members")
.and_then(Value::as_array)
.ok_or_else(malformed)?
.iter()
.map(|entry| {
let score = entry
.get("score")
.and_then(Value::as_f64)
.ok_or_else(malformed)?;
let member = decode_byte_list(entry.get("member").unwrap_or(&Value::Null))?;
Ok(ScoredMember { score, member })
})
.collect()
}
fn ns_key(namespace: &str, key: &str) -> Map<String, Value> {
let mut body = Map::new();
body.insert("namespace".into(), Value::String(namespace.to_string()));
body.insert("key".into(), Value::String(key.to_string()));
body
}
fn ttl_value(ttl: Option<Duration>) -> Value {
match ttl.filter(|t| !t.is_zero()) {
Some(t) => json!(t.as_millis().min(i64::MAX as u128) as i64),
None => Value::Null,
}
}
fn op(variant: &str, body: Map<String, Value>) -> Value {
json!({"Cache": {variant: Value::Object(body)}})
}
fn entries_op(
variant: &str,
namespace: &str,
entries: &[CacheEntry],
ttl: Option<Duration>,
) -> Value {
let mut body = Map::new();
body.insert("namespace".into(), Value::String(namespace.to_string()));
body.insert(
"entries".into(),
Value::Array(entries.iter().map(CacheEntry::to_wire).collect()),
);
body.insert("ttl_ms".into(), ttl_value(ttl));
op(variant, body)
}
fn zrange_op(
namespace: &str,
key: &str,
start: i64,
stop: i64,
rev: bool,
with_scores: bool,
) -> Value {
let mut body = ns_key(namespace, key);
body.insert("start".into(), json!(start));
body.insert("stop".into(), json!(stop));
body.insert("rev".into(), json!(rev));
body.insert("with_scores".into(), json!(with_scores));
op("ZRange", body)
}
fn zrange_by_score_op(
namespace: &str,
key: &str,
min: ScoreBound,
max: ScoreBound,
rev: bool,
with_scores: bool,
limit: Option<(u64, u64)>,
) -> Result<Value> {
let mut body = ns_key(namespace, key);
body.insert("min".into(), min.to_wire("min")?);
body.insert("max".into(), max.to_wire("max")?);
body.insert("rev".into(), json!(rev));
body.insert("with_scores".into(), json!(with_scores));
body.insert(
"limit".into(),
limit.map_or(Value::Null, |(offset, count)| json!([offset, count])),
);
Ok(op("ZRangeByScore", body))
}
fn lock_op(variant: &str, namespace: &str, key: &str, owner: &str, ttl: Option<Duration>) -> Value {
let mut body = ns_key(namespace, key);
body.insert("owner".into(), json!(owner));
if let Some(ttl) = ttl {
body.insert("ttl_ms".into(), json!(millis(ttl)));
}
op(variant, body)
}
#[derive(Deserialize)]
struct Deleted {
#[serde(default)]
deleted: i64,
}
#[derive(Deserialize)]
struct Length {
#[serde(default)]
length: i64,
}
impl Client {
pub fn cache_ping(&mut self) -> Result<()> {
self.send(json!({"Cache": "Ping"})).map(|_| ())
}
pub fn cache_get(&mut self, namespace: &str, key: &str) -> Result<Option<Vec<u8>>> {
self.cache_value(op("Get", ns_key(namespace, key)), "Get")
}
pub fn cache_set(&mut self, namespace: &str, key: &str, value: &[u8]) -> Result<()> {
self.cache_set_ttl(namespace, key, value, None)
}
pub fn cache_set_ttl(
&mut self,
namespace: &str,
key: &str,
value: &[u8],
ttl: Option<Duration>,
) -> Result<()> {
let mut body = ns_key(namespace, key);
body.insert("value".into(), byte_list(value));
body.insert("ttl_ms".into(), ttl_value(ttl));
self.send(op("Set", body)).map(|_| ())
}
pub fn cache_set_nx(
&mut self,
namespace: &str,
key: &str,
value: &[u8],
ttl: Option<Duration>,
) -> Result<bool> {
#[derive(Deserialize)]
struct Set {
#[serde(default)]
set: bool,
}
let mut body = ns_key(namespace, key);
body.insert("value".into(), byte_list(value));
body.insert("ttl_ms".into(), ttl_value(ttl));
let out: Set = self.cache_json(op("SetNx", body), "SetNx")?;
Ok(out.set)
}
pub fn cache_delete(&mut self, namespace: &str, key: &str) -> Result<bool> {
#[derive(Deserialize)]
struct DeletedFlag {
#[serde(default)]
deleted: bool,
}
let out: DeletedFlag = self.cache_json(op("Delete", ns_key(namespace, key)), "Delete")?;
Ok(out.deleted)
}
pub fn cache_exists(&mut self, namespace: &str, key: &str) -> Result<bool> {
#[derive(Deserialize)]
struct Exists {
#[serde(default)]
exists: bool,
}
let out: Exists = self.cache_json(op("Exists", ns_key(namespace, key)), "Exists")?;
Ok(out.exists)
}
pub fn cache_ttl(&mut self, namespace: &str, key: &str) -> Result<Option<Duration>> {
#[derive(Deserialize)]
struct Ttl {
#[serde(default)]
ttl_ms: Option<i64>,
}
let out: Ttl = self.cache_json(op("Ttl", ns_key(namespace, key)), "Ttl")?;
Ok(out
.ttl_ms
.filter(|ms| *ms >= 0)
.map(|ms| Duration::from_millis(ms as u64)))
}
pub fn cache_expire(&mut self, namespace: &str, key: &str, ttl: Duration) -> Result<bool> {
#[derive(Deserialize)]
struct Updated {
#[serde(default)]
updated: bool,
}
let mut body = ns_key(namespace, key);
body.insert(
"ttl_ms".into(),
json!(ttl.as_millis().min(i64::MAX as u128) as i64),
);
let out: Updated = self.cache_json(op("Expire", body), "Expire")?;
Ok(out.updated)
}
pub fn cache_persist(&mut self, namespace: &str, key: &str) -> Result<bool> {
#[derive(Deserialize)]
struct Persisted {
#[serde(default)]
persisted: bool,
}
let out: Persisted = self.cache_json(op("Persist", ns_key(namespace, key)), "Persist")?;
Ok(out.persisted)
}
pub fn cache_incr(&mut self, namespace: &str, key: &str, by: i64) -> Result<i64> {
#[derive(Deserialize)]
struct CounterValue {
#[serde(default)]
value: i64,
}
let mut body = ns_key(namespace, key);
body.insert("by".into(), json!(by));
let out: CounterValue = self.cache_json(op("Incr", body), "Incr")?;
Ok(out.value)
}
pub fn cache_clear_namespace(&mut self, namespace: &str) -> Result<i64> {
#[derive(Deserialize)]
struct Cleared {
#[serde(default)]
cleared: i64,
}
let mut body = Map::new();
body.insert("namespace".into(), Value::String(namespace.to_string()));
let out: Cleared = self.cache_json(op("ClearNamespace", body), "ClearNamespace")?;
Ok(out.cleared)
}
pub fn cache_keys(
&mut self,
namespace: &str,
pattern: Option<&str>,
limit: Option<usize>,
) -> Result<Vec<CacheKeyInfo>> {
#[derive(Deserialize)]
struct Keys {
#[serde(default)]
keys: Vec<CacheKeyInfo>,
}
let mut body = Map::new();
body.insert("namespace".into(), Value::String(namespace.to_string()));
body.insert("pattern".into(), pattern.map_or(Value::Null, |p| json!(p)));
body.insert("limit".into(), limit.map_or(Value::Null, |l| json!(l)));
let out: Keys = self.cache_json(op("Keys", body), "Keys")?;
Ok(out.keys)
}
pub fn cache_lpush(
&mut self,
namespace: &str,
key: &str,
values: &[impl AsRef<[u8]>],
) -> Result<i64> {
self.cache_push("LPush", namespace, key, values)
}
pub fn cache_rpush(
&mut self,
namespace: &str,
key: &str,
values: &[impl AsRef<[u8]>],
) -> Result<i64> {
self.cache_push("RPush", namespace, key, values)
}
fn cache_push(
&mut self,
variant: &str,
namespace: &str,
key: &str,
values: &[impl AsRef<[u8]>],
) -> Result<i64> {
let mut body = ns_key(namespace, key);
body.insert("values".into(), byte_lists(values, "values")?);
let out: Length = self.cache_json(op(variant, body), variant)?;
Ok(out.length)
}
pub fn cache_lpop(&mut self, namespace: &str, key: &str) -> Result<Option<Vec<u8>>> {
self.cache_value(op("LPop", ns_key(namespace, key)), "LPop")
}
pub fn cache_rpop(&mut self, namespace: &str, key: &str) -> Result<Option<Vec<u8>>> {
self.cache_value(op("RPop", ns_key(namespace, key)), "RPop")
}
pub fn cache_lrange(
&mut self,
namespace: &str,
key: &str,
start: i64,
stop: i64,
) -> Result<Vec<Vec<u8>>> {
let mut body = ns_key(namespace, key);
body.insert("start".into(), json!(start));
body.insert("stop".into(), json!(stop));
let value: Value = self.cache_json(op("LRange", body), "LRange")?;
decode_byte_lists(value.get("values").unwrap_or(&Value::Null))
}
pub fn cache_llen(&mut self, namespace: &str, key: &str) -> Result<i64> {
let out: Length = self.cache_json(op("LLen", ns_key(namespace, key)), "LLen")?;
Ok(out.length)
}
pub fn cache_lindex(
&mut self,
namespace: &str,
key: &str,
index: i64,
) -> Result<Option<Vec<u8>>> {
let mut body = ns_key(namespace, key);
body.insert("index".into(), json!(index));
self.cache_value(op("LIndex", body), "LIndex")
}
pub fn cache_sadd(
&mut self,
namespace: &str,
key: &str,
members: &[impl AsRef<[u8]>],
) -> Result<i64> {
#[derive(Deserialize)]
struct Added {
#[serde(default)]
added: i64,
}
let mut body = ns_key(namespace, key);
body.insert("members".into(), byte_lists(members, "members")?);
let out: Added = self.cache_json(op("SAdd", body), "SAdd")?;
Ok(out.added)
}
pub fn cache_srem(
&mut self,
namespace: &str,
key: &str,
members: &[impl AsRef<[u8]>],
) -> Result<i64> {
#[derive(Deserialize)]
struct Removed {
#[serde(default)]
removed: i64,
}
let mut body = ns_key(namespace, key);
body.insert("members".into(), byte_lists(members, "members")?);
let out: Removed = self.cache_json(op("SRem", body), "SRem")?;
Ok(out.removed)
}
pub fn cache_sismember(&mut self, namespace: &str, key: &str, member: &[u8]) -> Result<bool> {
#[derive(Deserialize)]
struct IsMember {
#[serde(default)]
is_member: bool,
}
let mut body = ns_key(namespace, key);
body.insert("member".into(), byte_list(member));
let out: IsMember = self.cache_json(op("SIsMember", body), "SIsMember")?;
Ok(out.is_member)
}
pub fn cache_scard(&mut self, namespace: &str, key: &str) -> Result<i64> {
#[derive(Deserialize)]
struct Cardinality {
#[serde(default)]
cardinality: i64,
}
let out: Cardinality = self.cache_json(op("SCard", ns_key(namespace, key)), "SCard")?;
Ok(out.cardinality)
}
pub fn cache_smembers(&mut self, namespace: &str, key: &str) -> Result<Vec<Vec<u8>>> {
let value: Value = self.cache_json(op("SMembers", ns_key(namespace, key)), "SMembers")?;
decode_byte_lists(value.get("members").unwrap_or(&Value::Null))
}
pub fn cache_hset(&mut self, namespace: &str, key: &str, entries: &[CachePair]) -> Result<i64> {
#[derive(Deserialize)]
struct Created {
#[serde(default)]
created: i64,
}
if entries.is_empty() {
return Err(Error::invalid("`entries` must not be empty"));
}
let mut body = ns_key(namespace, key);
body.insert(
"entries".into(),
Value::Array(entries.iter().map(CachePair::to_wire).collect()),
);
let out: Created = self.cache_json(op("HSet", body), "HSet")?;
Ok(out.created)
}
pub fn cache_hset_text<'a>(
&mut self,
namespace: &str,
key: &str,
entries: impl IntoIterator<Item = (&'a str, &'a str)>,
) -> Result<i64> {
self.cache_hset(namespace, key, &CachePair::from_text(entries))
}
pub fn cache_hget(
&mut self,
namespace: &str,
key: &str,
field: &[u8],
) -> Result<Option<Vec<u8>>> {
let mut body = ns_key(namespace, key);
body.insert("field".into(), byte_list(field));
self.cache_value(op("HGet", body), "HGet")
}
pub fn cache_hdel(
&mut self,
namespace: &str,
key: &str,
fields: &[impl AsRef<[u8]>],
) -> Result<i64> {
let mut body = ns_key(namespace, key);
body.insert("fields".into(), byte_lists(fields, "fields")?);
let out: Deleted = self.cache_json(op("HDel", body), "HDel")?;
Ok(out.deleted)
}
pub fn cache_hgetall(&mut self, namespace: &str, key: &str) -> Result<Vec<CachePair>> {
let value: Value = self.cache_json(op("HGetAll", ns_key(namespace, key)), "HGetAll")?;
let entries = value
.get("entries")
.and_then(Value::as_array)
.ok_or_else(|| Error::protocol("malformed HGetAll payload: no entries".to_string()))?;
entries
.iter()
.map(|entry| {
let (field, value) = decode_pair(entry)?;
Ok(CachePair { field, value })
})
.collect()
}
pub fn cache_hexists(&mut self, namespace: &str, key: &str, field: &[u8]) -> Result<bool> {
#[derive(Deserialize)]
struct Exists {
#[serde(default)]
exists: bool,
}
let mut body = ns_key(namespace, key);
body.insert("field".into(), byte_list(field));
let out: Exists = self.cache_json(op("HExists", body), "HExists")?;
Ok(out.exists)
}
pub fn cache_hlen(&mut self, namespace: &str, key: &str) -> Result<i64> {
let out: Length = self.cache_json(op("HLen", ns_key(namespace, key)), "HLen")?;
Ok(out.length)
}
pub fn cache_xadd(
&mut self,
namespace: &str,
key: &str,
fields: &[CachePair],
id: Option<&str>,
) -> Result<String> {
#[derive(Deserialize)]
struct EntryId {
#[serde(default)]
id: String,
}
if fields.is_empty() {
return Err(Error::invalid("`fields` must not be empty"));
}
let mut body = ns_key(namespace, key);
body.insert(
"fields".into(),
Value::Array(fields.iter().map(CachePair::to_wire).collect()),
);
body.insert(
"id".into(),
id.filter(|i| !i.is_empty())
.map_or(Value::Null, |i| json!(i)),
);
let out: EntryId = self.cache_json(op("XAdd", body), "XAdd")?;
Ok(out.id)
}
pub fn cache_xadd_text<'a>(
&mut self,
namespace: &str,
key: &str,
fields: impl IntoIterator<Item = (&'a str, &'a str)>,
id: Option<&str>,
) -> Result<String> {
self.cache_xadd(namespace, key, &CachePair::from_text(fields), id)
}
pub fn cache_xlen(&mut self, namespace: &str, key: &str) -> Result<i64> {
let out: Length = self.cache_json(op("XLen", ns_key(namespace, key)), "XLen")?;
Ok(out.length)
}
pub fn cache_xrange(
&mut self,
namespace: &str,
key: &str,
start: &str,
end: &str,
count: Option<usize>,
) -> Result<Vec<StreamEntry>> {
let mut body = ns_key(namespace, key);
body.insert("start".into(), json!(start));
body.insert("end".into(), json!(end));
body.insert("count".into(), count.map_or(Value::Null, |c| json!(c)));
self.stream_entries(op("XRange", body), "XRange")
}
pub fn cache_xread(
&mut self,
namespace: &str,
key: &str,
after: &str,
count: Option<usize>,
) -> Result<Vec<StreamEntry>> {
let mut body = ns_key(namespace, key);
body.insert("after".into(), json!(after));
body.insert("count".into(), count.map_or(Value::Null, |c| json!(c)));
self.stream_entries(op("XRead", body), "XRead")
}
pub fn cache_xdel(&mut self, namespace: &str, key: &str, ids: &[&str]) -> Result<i64> {
if ids.is_empty() {
return Err(Error::invalid("`ids` must not be empty"));
}
let mut body = ns_key(namespace, key);
body.insert("ids".into(), json!(ids));
let out: Deleted = self.cache_json(op("XDel", body), "XDel")?;
Ok(out.deleted)
}
pub fn cache_xtrim(&mut self, namespace: &str, key: &str, max_len: usize) -> Result<i64> {
#[derive(Deserialize)]
struct Trimmed {
#[serde(default)]
trimmed: i64,
}
let mut body = ns_key(namespace, key);
body.insert("max_len".into(), json!(max_len));
let out: Trimmed = self.cache_json(op("XTrim", body), "XTrim")?;
Ok(out.trimmed)
}
pub fn cache_mget(&mut self, namespace: &str, keys: &[&str]) -> Result<Vec<Option<Vec<u8>>>> {
self.cache_values(op("MGet", keys_body(namespace, keys)), "MGet")
}
pub fn cache_mset(
&mut self,
namespace: &str,
entries: &[CacheEntry],
ttl: Option<Duration>,
) -> Result<i64> {
#[derive(Deserialize)]
struct Set {
#[serde(default)]
set: i64,
}
let out: Set = self.cache_json(entries_op("MSet", namespace, entries, ttl), "MSet")?;
Ok(out.set)
}
pub fn cache_warm(
&mut self,
namespace: &str,
entries: &[CacheEntry],
ttl: Option<Duration>,
) -> Result<WarmCounts> {
self.cache_json(entries_op("Warm", namespace, entries, ttl), "Warm")
}
pub fn cache_set_if(
&mut self,
namespace: &str,
key: &str,
value: &[u8],
condition: SetCondition,
expiry: CacheExpiry,
) -> Result<bool> {
#[derive(Deserialize)]
struct Set {
#[serde(default)]
set: bool,
}
let mut body = ns_key(namespace, key);
body.insert("value".into(), byte_list(value));
body.insert("condition".into(), json!(condition));
body.insert("expiry".into(), expiry.to_wire());
let out: Set = self.cache_json(op("SetIf", body), "SetIf")?;
Ok(out.set)
}
pub fn cache_compare_and_swap(
&mut self,
namespace: &str,
key: &str,
expected: Option<&[u8]>,
value: Option<&[u8]>,
ttl: Option<Duration>,
) -> Result<CompareAndSwap> {
#[derive(Deserialize)]
struct Wire {
#[serde(default)]
swapped: bool,
#[serde(default)]
current: Option<String>,
#[serde(default, rename = "currentIsBinary")]
current_is_binary: bool,
}
let mut body = ns_key(namespace, key);
body.insert("expected".into(), expected.map_or(Value::Null, byte_list));
body.insert("value".into(), value.map_or(Value::Null, byte_list));
body.insert("ttl_ms".into(), ttl_value(ttl));
let out: Wire = self.cache_json(op("CompareAndSwap", body), "CompareAndSwap")?;
Ok(CompareAndSwap {
swapped: out.swapped,
current: out.current.map(String::into_bytes),
current_is_binary: out.current_is_binary,
})
}
pub fn cache_get_del(&mut self, namespace: &str, key: &str) -> Result<Option<Vec<u8>>> {
self.cache_value(op("GetDel", ns_key(namespace, key)), "GetDel")
}
pub fn cache_get_set(
&mut self,
namespace: &str,
key: &str,
value: &[u8],
ttl: Option<Duration>,
) -> Result<Option<Vec<u8>>> {
let mut body = ns_key(namespace, key);
body.insert("value".into(), byte_list(value));
body.insert("ttl_ms".into(), ttl_value(ttl));
self.cache_value(op("GetSet", body), "GetSet")
}
pub fn cache_get_ex(
&mut self,
namespace: &str,
key: &str,
expiry: CacheExpiry,
) -> Result<Option<Vec<u8>>> {
let mut body = ns_key(namespace, key);
body.insert("expiry".into(), expiry.to_wire());
self.cache_value(op("GetEx", body), "GetEx")
}
pub fn cache_append(&mut self, namespace: &str, key: &str, value: &[u8]) -> Result<i64> {
let mut body = ns_key(namespace, key);
body.insert("value".into(), byte_list(value));
let out: Length = self.cache_json(op("Append", body), "Append")?;
Ok(out.length)
}
pub fn cache_expire_at(
&mut self,
namespace: &str,
key: &str,
at_ms: i64,
) -> Result<ExpireAtOutcome> {
let mut body = ns_key(namespace, key);
body.insert("at_ms".into(), json!(at_ms));
self.cache_json(op("ExpireAt", body), "ExpireAt")
}
pub fn cache_set_fresh(
&mut self,
namespace: &str,
key: &str,
value: &[u8],
fresh: Duration,
ttl: Option<Duration>,
) -> Result<i64> {
#[derive(Deserialize)]
struct Fresh {
#[serde(default, rename = "freshUntilMs")]
fresh_until_ms: i64,
}
let mut body = ns_key(namespace, key);
body.insert("value".into(), byte_list(value));
body.insert("fresh_ms".into(), json!(millis(fresh)));
body.insert("ttl_ms".into(), ttl_value(ttl));
let out: Fresh = self.cache_json(op("SetFresh", body), "SetFresh")?;
Ok(out.fresh_until_ms)
}
pub fn cache_get_fresh(
&mut self,
namespace: &str,
key: &str,
owner: &str,
lease: Duration,
) -> Result<FreshRead> {
#[derive(Deserialize)]
struct Wire {
#[serde(default)]
state: String,
#[serde(default)]
value: Option<String>,
#[serde(default, rename = "valueHex")]
value_hex: Option<String>,
#[serde(default)]
refresh: bool,
#[serde(default)]
lease: bool,
#[serde(default, rename = "retryAfterMs")]
retry_after_ms: Option<u64>,
}
let mut body = ns_key(namespace, key);
body.insert("owner".into(), json!(owner));
body.insert("lease_ms".into(), json!(millis(lease)));
let out: Wire = self.cache_json(op("GetFresh", body), "GetFresh")?;
let state = match out.state.as_str() {
"fresh" => FreshState::Fresh,
"stale" => FreshState::Stale,
"missing" => FreshState::Missing,
"miss" => FreshState::Miss,
other => {
return Err(Error::protocol(format!(
"GetFresh answered with the unknown state `{other}`"
)))
}
};
let value = match (out.value, out.value_hex) {
(Some(text), _) => Some(text.into_bytes()),
(None, Some(hex)) => Some(decode_hex(&hex)?),
(None, None) => None,
};
Ok(FreshRead {
state,
value,
refresh: out.refresh,
lease: out.lease,
retry_after_ms: out.retry_after_ms,
})
}
pub fn cache_set_missing(&mut self, namespace: &str, key: &str, ttl: Duration) -> Result<()> {
let mut body = ns_key(namespace, key);
body.insert("ttl_ms".into(), json!(millis(ttl)));
self.send(op("SetMissing", body)).map(|_| ())
}
pub fn cache_zadd(
&mut self,
namespace: &str,
key: &str,
members: &[ScoredMember],
) -> Result<ZAddCounts> {
if members.is_empty() {
return Err(Error::invalid("`members` must not be empty"));
}
let members = members
.iter()
.map(|m| {
Ok(json!({
"score": score_value(m.score, "score")?,
"member": byte_list(&m.member),
}))
})
.collect::<Result<Vec<Value>>>()?;
let mut body = ns_key(namespace, key);
body.insert("members".into(), Value::Array(members));
self.cache_json(op("ZAdd", body), "ZAdd")
}
pub fn cache_zrem(
&mut self,
namespace: &str,
key: &str,
members: &[impl AsRef<[u8]>],
) -> Result<i64> {
#[derive(Deserialize)]
struct Removed {
#[serde(default)]
removed: i64,
}
let mut body = ns_key(namespace, key);
body.insert("members".into(), byte_lists(members, "members")?);
let out: Removed = self.cache_json(op("ZRem", body), "ZRem")?;
Ok(out.removed)
}
pub fn cache_zscore(
&mut self,
namespace: &str,
key: &str,
member: &[u8],
) -> Result<Option<f64>> {
#[derive(Deserialize)]
struct Scored {
#[serde(default)]
score: Option<f64>,
}
let mut body = ns_key(namespace, key);
body.insert("member".into(), byte_list(member));
let out: Scored = self.cache_json(op("ZScore", body), "ZScore")?;
Ok(out.score)
}
pub fn cache_zcard(&mut self, namespace: &str, key: &str) -> Result<i64> {
#[derive(Deserialize)]
struct Cardinality {
#[serde(default)]
cardinality: i64,
}
let out: Cardinality = self.cache_json(op("ZCard", ns_key(namespace, key)), "ZCard")?;
Ok(out.cardinality)
}
pub fn cache_zrank(
&mut self,
namespace: &str,
key: &str,
member: &[u8],
rev: bool,
) -> Result<Option<i64>> {
#[derive(Deserialize)]
struct Ranked {
#[serde(default)]
rank: Option<i64>,
}
let mut body = ns_key(namespace, key);
body.insert("member".into(), byte_list(member));
body.insert("rev".into(), json!(rev));
let out: Ranked = self.cache_json(op("ZRank", body), "ZRank")?;
Ok(out.rank)
}
pub fn cache_zrange(
&mut self,
namespace: &str,
key: &str,
start: i64,
stop: i64,
rev: bool,
) -> Result<Vec<Vec<u8>>> {
let value: Value =
self.cache_json(zrange_op(namespace, key, start, stop, rev, false), "ZRange")?;
decode_byte_lists(value.get("members").unwrap_or(&Value::Null))
}
pub fn cache_zrange_with_scores(
&mut self,
namespace: &str,
key: &str,
start: i64,
stop: i64,
rev: bool,
) -> Result<Vec<ScoredMember>> {
let value: Value =
self.cache_json(zrange_op(namespace, key, start, stop, rev, true), "ZRange")?;
scored_members(&value, "ZRange")
}
pub fn cache_zrange_by_score(
&mut self,
namespace: &str,
key: &str,
min: ScoreBound,
max: ScoreBound,
rev: bool,
limit: Option<(u64, u64)>,
) -> Result<Vec<Vec<u8>>> {
let request = zrange_by_score_op(namespace, key, min, max, rev, false, limit)?;
let value: Value = self.cache_json(request, "ZRangeByScore")?;
decode_byte_lists(value.get("members").unwrap_or(&Value::Null))
}
pub fn cache_zrange_by_score_with_scores(
&mut self,
namespace: &str,
key: &str,
min: ScoreBound,
max: ScoreBound,
rev: bool,
limit: Option<(u64, u64)>,
) -> Result<Vec<ScoredMember>> {
let request = zrange_by_score_op(namespace, key, min, max, rev, true, limit)?;
let value: Value = self.cache_json(request, "ZRangeByScore")?;
scored_members(&value, "ZRangeByScore")
}
pub fn cache_zincr_by(
&mut self,
namespace: &str,
key: &str,
member: &[u8],
by: f64,
) -> Result<f64> {
#[derive(Deserialize)]
struct Scored {
score: f64,
}
let mut body = ns_key(namespace, key);
body.insert("member".into(), byte_list(member));
body.insert("by".into(), score_value(by, "by")?);
let out: Scored = self.cache_json(op("ZIncrBy", body), "ZIncrBy")?;
Ok(out.score)
}
pub fn cache_lock_acquire(
&mut self,
namespace: &str,
key: &str,
owner: &str,
ttl: Duration,
) -> Result<CacheLock> {
self.cache_json(
lock_op("LockAcquire", namespace, key, owner, Some(ttl)),
"LockAcquire",
)
}
pub fn cache_lock_renew(
&mut self,
namespace: &str,
key: &str,
owner: &str,
ttl: Duration,
) -> Result<CacheLockRenewal> {
self.cache_json(
lock_op("LockRenew", namespace, key, owner, Some(ttl)),
"LockRenew",
)
}
pub fn cache_lock_release(&mut self, namespace: &str, key: &str, owner: &str) -> Result<bool> {
#[derive(Deserialize)]
struct Released {
#[serde(default)]
released: bool,
}
let out: Released = self.cache_json(
lock_op("LockRelease", namespace, key, owner, None),
"LockRelease",
)?;
Ok(out.released)
}
pub fn cache_set_bit(
&mut self,
namespace: &str,
key: &str,
offset: u64,
value: bool,
) -> Result<bool> {
#[derive(Deserialize)]
struct Previous {
#[serde(default)]
previous: u8,
}
let mut body = ns_key(namespace, key);
body.insert("offset".into(), json!(offset));
body.insert("value".into(), json!(value));
let out: Previous = self.cache_json(op("SetBit", body), "SetBit")?;
Ok(out.previous != 0)
}
pub fn cache_get_bit(&mut self, namespace: &str, key: &str, offset: u64) -> Result<bool> {
#[derive(Deserialize)]
struct Bit {
#[serde(default)]
bit: u8,
}
let mut body = ns_key(namespace, key);
body.insert("offset".into(), json!(offset));
let out: Bit = self.cache_json(op("GetBit", body), "GetBit")?;
Ok(out.bit != 0)
}
pub fn cache_bit_count(
&mut self,
namespace: &str,
key: &str,
range: Option<(i64, i64)>,
) -> Result<i64> {
#[derive(Deserialize)]
struct Count {
#[serde(default)]
count: i64,
}
let mut body = ns_key(namespace, key);
body.insert("range".into(), range_value(range));
let out: Count = self.cache_json(op("BitCount", body), "BitCount")?;
Ok(out.count)
}
pub fn cache_bit_pos(
&mut self,
namespace: &str,
key: &str,
bit: bool,
range: Option<(i64, i64)>,
) -> Result<i64> {
#[derive(Deserialize)]
struct Position {
position: i64,
}
let mut body = ns_key(namespace, key);
body.insert("bit".into(), json!(bit));
body.insert("range".into(), range_value(range));
let out: Position = self.cache_json(op("BitPos", body), "BitPos")?;
Ok(out.position)
}
pub fn cache_bit_op(
&mut self,
namespace: &str,
kind: BitOpKind,
dest: &str,
keys: &[&str],
) -> Result<i64> {
let mut body = Map::new();
body.insert("namespace".into(), json!(namespace));
body.insert("op".into(), json!(kind));
body.insert("dest".into(), json!(dest));
body.insert("keys".into(), json!(keys));
let out: Length = self.cache_json(op("BitOp", body), "BitOp")?;
Ok(out.length)
}
pub fn cache_pf_add(
&mut self,
namespace: &str,
key: &str,
elements: &[impl AsRef<[u8]>],
) -> Result<bool> {
#[derive(Deserialize)]
struct Changed {
#[serde(default)]
changed: bool,
}
let mut body = ns_key(namespace, key);
body.insert("elements".into(), byte_lists(elements, "elements")?);
let out: Changed = self.cache_json(op("PfAdd", body), "PfAdd")?;
Ok(out.changed)
}
pub fn cache_pf_count(&mut self, namespace: &str, keys: &[&str]) -> Result<i64> {
#[derive(Deserialize)]
struct Count {
#[serde(default)]
count: i64,
}
let out: Count = self.cache_json(op("PfCount", keys_body(namespace, keys)), "PfCount")?;
Ok(out.count)
}
pub fn cache_pf_merge(&mut self, namespace: &str, dest: &str, sources: &[&str]) -> Result<()> {
let mut body = Map::new();
body.insert("namespace".into(), json!(namespace));
body.insert("dest".into(), json!(dest));
body.insert("sources".into(), json!(sources));
self.send(op("PfMerge", body)).map(|_| ())
}
pub fn cache_lmove(
&mut self,
namespace: &str,
source: &str,
destination: &str,
from: ListEnd,
to: ListEnd,
) -> Result<Option<Vec<u8>>> {
let mut body = Map::new();
body.insert("namespace".into(), json!(namespace));
body.insert("source".into(), json!(source));
body.insert("destination".into(), json!(destination));
body.insert("from".into(), json!(from));
body.insert("to".into(), json!(to));
self.cache_value(op("LMove", body), "LMove")
}
pub fn cache_ltrim(
&mut self,
namespace: &str,
key: &str,
start: i64,
stop: i64,
) -> Result<TrimCounts> {
let mut body = ns_key(namespace, key);
body.insert("start".into(), json!(start));
body.insert("stop".into(), json!(stop));
self.cache_json(op("LTrim", body), "LTrim")
}
pub fn cache_blpop(
&mut self,
namespace: &str,
keys: &[&str],
timeout: Duration,
) -> Result<Option<(String, Vec<u8>)>> {
self.cache_blocking_pop("BLPop", namespace, keys, timeout)
}
pub fn cache_brpop(
&mut self,
namespace: &str,
keys: &[&str],
timeout: Duration,
) -> Result<Option<(String, Vec<u8>)>> {
self.cache_blocking_pop("BRPop", namespace, keys, timeout)
}
fn cache_blocking_pop(
&mut self,
variant: &str,
namespace: &str,
keys: &[&str],
timeout: Duration,
) -> Result<Option<(String, Vec<u8>)>> {
let mut body = keys_body(namespace, keys);
body.insert("timeout_ms".into(), json!(millis(timeout)));
let mut values = self.cache_values(op(variant, body), variant)?.into_iter();
match (values.next().flatten(), values.next().flatten()) {
(Some(key), Some(value)) => {
Ok(Some((String::from_utf8_lossy(&key).into_owned(), value)))
}
_ => Ok(None),
}
}
pub fn cache_sunion(&mut self, namespace: &str, keys: &[&str]) -> Result<Vec<Vec<u8>>> {
self.cache_members("SUnion", keys_body(namespace, keys))
}
pub fn cache_sinter(&mut self, namespace: &str, keys: &[&str]) -> Result<Vec<Vec<u8>>> {
self.cache_members("SInter", keys_body(namespace, keys))
}
pub fn cache_sdiff(&mut self, namespace: &str, keys: &[&str]) -> Result<Vec<Vec<u8>>> {
self.cache_members("SDiff", keys_body(namespace, keys))
}
pub fn cache_srand_member(
&mut self,
namespace: &str,
key: &str,
count: Option<i64>,
) -> Result<Vec<Vec<u8>>> {
let mut body = ns_key(namespace, key);
body.insert("count".into(), count.map_or(Value::Null, |c| json!(c)));
self.cache_members("SRandMember", body)
}
fn cache_members(&mut self, variant: &str, body: Map<String, Value>) -> Result<Vec<Vec<u8>>> {
let value: Value = self.cache_json(op(variant, body), variant)?;
decode_byte_lists(value.get("members").unwrap_or(&Value::Null))
}
pub fn cache_hincr_by(
&mut self,
namespace: &str,
key: &str,
field: &[u8],
by: i64,
) -> Result<i64> {
#[derive(Deserialize)]
struct CounterValue {
#[serde(default)]
value: i64,
}
let mut body = ns_key(namespace, key);
body.insert("field".into(), byte_list(field));
body.insert("by".into(), json!(by));
let out: CounterValue = self.cache_json(op("HIncrBy", body), "HIncrBy")?;
Ok(out.value)
}
pub fn cache_pipeline(&mut self, ops: &[CacheCommand]) -> Result<Vec<CacheReply>> {
self.cache_replies(json!({"Cache": {"Pipeline": {"ops": ops}}}), "Pipeline")
}
pub fn cache_transaction(&mut self, ops: &[CacheCommand]) -> Result<Vec<CacheReply>> {
self.cache_replies(
json!({"Cache": {"Transaction": {"ops": ops}}}),
"Transaction",
)
}
pub fn cache_script_load(&mut self, name: &str, ops: &[CacheCommand]) -> Result<()> {
self.send(json!({"Cache": {"ScriptLoad": {"name": name, "ops": ops}}}))
.map(|_| ())
}
pub fn cache_script_run(&mut self, name: &str, args: &[&str]) -> Result<Vec<CacheReply>> {
self.cache_replies(
json!({"Cache": {"ScriptRun": {"name": name, "args": args}}}),
"ScriptRun",
)
}
fn cache_replies(&mut self, op: Value, what: &str) -> Result<Vec<CacheReply>> {
let value: Value = self.cache_json(op, what)?;
value
.get("results")
.and_then(Value::as_array)
.ok_or_else(|| Error::protocol(format!("malformed {what} payload: no results")))?
.iter()
.map(CacheReply::from_wire)
.collect()
}
pub fn cache_publish(&mut self, channel: &str, message: &str) -> Result<u64> {
#[derive(Deserialize)]
struct Published {
#[serde(default)]
id: u64,
}
let out: Published = self.cache_json(
json!({"Cache": {"Publish": {"channel": channel, "message": message}}}),
"Publish",
)?;
Ok(out.id)
}
pub fn cache_poll(
&mut self,
channels: &[&str],
after: u64,
limit: Option<u32>,
) -> Result<PolledMessages> {
let mut body = Map::new();
body.insert("channels".into(), json!(channels));
body.insert("after".into(), json!(after));
if let Some(limit) = limit {
body.insert("limit".into(), json!(limit));
}
self.cache_json(op("Poll", body), "Poll")
}
pub fn cache_stats(&mut self) -> Result<CacheStats> {
self.cache_json(json!({"Cache": "Stats"}), "Stats")
}
pub fn cache_flush_database(&mut self) -> Result<i64> {
#[derive(Deserialize)]
struct Cleared {
#[serde(default)]
cleared: i64,
}
let out: Cleared = self.cache_json(json!({"Cache": "FlushDatabase"}), "FlushDatabase")?;
Ok(out.cleared)
}
pub fn cache_purge_expired(&mut self) -> Result<i64> {
#[derive(Deserialize)]
struct Purged {
#[serde(default)]
purged: i64,
}
let out: Purged = self.cache_json(json!({"Cache": "PurgeExpired"}), "PurgeExpired")?;
Ok(out.purged)
}
pub fn cache_hot_keys(&mut self, limit: Option<usize>) -> Result<Value> {
let mut body = Map::new();
if let Some(limit) = limit {
body.insert("limit".into(), json!(limit));
}
self.cache_json(op("HotKeys", body), "HotKeys")
}
pub fn cache_set_namespace_limit(
&mut self,
namespace: &str,
max_keys: Option<u64>,
max_bytes: Option<u64>,
) -> Result<NamespaceLimit> {
let mut body = Map::new();
body.insert("namespace".into(), json!(namespace));
body.insert("max_keys".into(), json!(max_keys));
body.insert("max_bytes".into(), json!(max_bytes));
self.cache_json(op("SetNamespaceLimit", body), "SetNamespaceLimit")
}
pub fn cache_get_namespace_limit(&mut self, namespace: &str) -> Result<NamespaceLimit> {
let mut body = Map::new();
body.insert("namespace".into(), json!(namespace));
self.cache_json(op("GetNamespaceLimit", body), "GetNamespaceLimit")
}
fn cache_values(&mut self, op: Value, what: &str) -> Result<Vec<Option<Vec<u8>>>> {
let response = self.send(op)?;
let data = response.expect("CacheValues", what)?;
data.as_array()
.ok_or_else(|| Error::protocol(format!("malformed {what} payload: not a list")))?
.iter()
.map(|item| {
if item.is_null() {
Ok(None)
} else {
decode_byte_list(item).map(Some)
}
})
.collect()
}
fn cache_json<T: serde::de::DeserializeOwned>(&mut self, op: Value, what: &str) -> Result<T> {
let response = self.send(op)?;
response.decode(what)
}
fn cache_value(&mut self, op: Value, what: &str) -> Result<Option<Vec<u8>>> {
let response = self.send(op)?;
let data = response.expect("CacheValue", what)?;
if data.is_null() {
return Ok(None);
}
decode_byte_list(data).map(Some)
}
fn stream_entries(&mut self, op: Value, what: &str) -> Result<Vec<StreamEntry>> {
#[derive(Deserialize)]
struct Entries {
#[serde(default)]
entries: Vec<WireEntry>,
}
#[derive(Deserialize)]
struct WireEntry {
#[serde(default)]
id: String,
#[serde(default)]
fields: Vec<Value>,
}
let out: Entries = self.cache_json(op, what)?;
out.entries
.into_iter()
.map(|entry| {
let fields = entry
.fields
.iter()
.map(|pair| {
let (field, value) = decode_pair(pair)?;
Ok(CachePair { field, value })
})
.collect::<Result<Vec<_>>>()?;
Ok(StreamEntry {
id: entry.id,
fields,
})
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_absent_ttl_is_null_rather_than_zero() {
assert_eq!(ttl_value(None), Value::Null);
assert_eq!(ttl_value(Some(Duration::ZERO)), Value::Null);
assert_eq!(ttl_value(Some(Duration::from_millis(1500))), json!(1500));
}
#[test]
fn a_keyed_operation_carries_its_namespace_and_key() {
let body = op("Get", ns_key("sessions", "u1"));
assert_eq!(body["Cache"]["Get"]["namespace"], "sessions");
assert_eq!(body["Cache"]["Get"]["key"], "u1");
}
#[test]
fn pairs_encode_both_halves_as_byte_arrays() {
let pair = CachePair::new("name", "ada");
assert_eq!(pair.to_wire(), json!([[110, 97, 109, 101], [97, 100, 97]]));
assert_eq!(pair.field_text(), Some("name"));
assert_eq!(pair.value_text(), Some("ada"));
}
#[test]
fn text_pairs_keep_their_order() {
let pairs = CachePair::from_text([("a", "1"), ("b", "2")]);
assert_eq!(pairs.len(), 2);
assert_eq!(pairs[0].field_text(), Some("a"));
assert_eq!(pairs[1].value_text(), Some("2"));
}
#[test]
fn a_stream_entry_reads_back_as_text() {
let entry = StreamEntry {
id: "1-0".into(),
fields: CachePair::from_text([("msg", "hi")]),
};
assert_eq!(entry.text()["msg"], "hi");
}
}