use fred::bytes::Bytes;
use fred::types::Value;
pub(super) const CAS_WRITE: &str = r#"
local current = redis.call('GET', KEYS[1])
if not current then
return 'missing'
end
if current ~= ARGV[1] then
return 'conflict'
end
local ttl = tonumber(ARGV[3])
if ttl > 0 then
redis.call('SET', KEYS[1], ARGV[2], 'EX', ttl)
else
redis.call('SET', KEYS[1], ARGV[2])
end
if redis.call('GET', KEYS[2]) == ARGV[4] then
if ttl > 0 then
redis.call('EXPIRE', KEYS[2], ttl)
else
redis.call('PERSIST', KEYS[2])
end
end
return 'ok'
"#;
pub(super) const REMOVE_DEVICE: &str = r#"
local current = redis.call('GET', KEYS[1])
local status = 'ok'
if not current then
status = 'missing'
elseif ARGV[1] ~= '' and current ~= ARGV[1] then
return 'conflict'
else
redis.call('DEL', KEYS[1])
end
if redis.call('GET', KEYS[2]) == ARGV[2] then
redis.call('DEL', KEYS[2])
end
return status
"#;
pub(super) const DELETE_IF_POINTS_AT: &str = r#"
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
end
return 0
"#;
pub(super) const PRUNE_ABSENT: &str = r#"
local removed = 0
for i = 2, #KEYS do
if redis.call('EXISTS', KEYS[i]) == 0 then
removed = removed + redis.call('SREM', KEYS[1], ARGV[i - 1])
end
end
return removed
"#;
pub(super) fn row_keys(prefix: &str, tenant: &str, members: &[String]) -> Vec<String> {
members
.iter()
.map(|id| super::device_key(prefix, tenant, id))
.collect()
}
pub(super) type MemberRow = (String, Option<Vec<u8>>);
pub(super) fn pair_replies(
members: &[String],
replies: Vec<Value>,
) -> Result<Vec<MemberRow>, String> {
if members.len() != replies.len() {
return Err(format!(
"MGET returned {} replies for {} keys",
replies.len(),
members.len()
));
}
members
.iter()
.zip(replies)
.map(|(id, reply)| match reply {
Value::Null => Ok((id.clone(), None)),
Value::Bytes(b) => Ok((id.clone(), Some(b.to_vec()))),
Value::String(s) => Ok((id.clone(), Some(s.as_bytes().to_vec()))),
other => Err(format!("MGET reply for {id} is not a string: {other:?}")),
})
.collect()
}
pub(super) fn prune_args(members: &[String]) -> Vec<Value> {
members.iter().map(|m| bytes(m.as_bytes())).collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ScriptStatus {
Ok,
Missing,
Conflict,
}
pub(super) fn parse_status(reply: &str) -> Option<ScriptStatus> {
match reply {
"ok" => Some(ScriptStatus::Ok),
"missing" => Some(ScriptStatus::Missing),
"conflict" => Some(ScriptStatus::Conflict),
_ => None,
}
}
fn bytes(b: &[u8]) -> Value {
Value::Bytes(Bytes::copy_from_slice(b))
}
pub(super) fn cas_write_args(
expected: &[u8],
new_row: &[u8],
ttl_seconds: Option<i64>,
device_id: &str,
) -> Vec<Value> {
vec![
bytes(expected),
bytes(new_row),
Value::Integer(ttl_seconds.unwrap_or(0)),
bytes(device_id.as_bytes()),
]
}
pub(super) fn remove_args(expected: Option<&[u8]>, device_id: &str) -> Vec<Value> {
vec![bytes(expected.unwrap_or(&[])), bytes(device_id.as_bytes())]
}