#[derive(Debug, Clone, Copy)]
pub struct VerbMeta {
pub name: &'static str,
pub group: &'static str,
pub arity: i8,
pub flags: &'static [&'static str],
pub summary: &'static str,
pub since: &'static str,
pub syntax: &'static str,
pub complexity: &'static str,
pub compat: &'static str,
}
#[allow(clippy::too_many_arguments)]
const fn v(
name: &'static str,
group: &'static str,
arity: i8,
flags: &'static [&'static str],
summary: &'static str,
since: &'static str,
syntax: &'static str,
complexity: &'static str,
compat: &'static str,
) -> VerbMeta {
VerbMeta { name, group, arity, flags, summary, since, syntax, complexity, compat }
}
mod admin;
mod collections;
mod extensions;
mod keyspace;
mod streams_geo;
pub(crate) mod flags {
pub(crate) const W: &[&str] = &["write"];
pub(crate) const R: &[&str] = &["readonly"];
pub(crate) const WB: &[&str] = &["write", "blocking"];
pub(crate) const RB: &[&str] = &["readonly", "blocking"];
pub(crate) const AD: &[&str] = &["readonly", "admin"];
pub(crate) const WAD: &[&str] = &["write", "admin"];
pub(crate) const TX: &[&str] = &["readonly", "transaction"];
pub(crate) const PS: &[&str] = &["readonly", "pubsub"];
pub(crate) const RX: &[&str] = &["readonly", "extension"];
pub(crate) const RBX: &[&str] = &["readonly", "blocking", "extension"];
pub(crate) const WX: &[&str] = &["write", "extension"];
}
const FAMILIES: [&[VerbMeta]; 5] =
[keyspace::ROWS, collections::ROWS, streams_geo::ROWS, admin::ROWS, extensions::ROWS];
const TOTAL: usize = keyspace::ROWS.len()
+ collections::ROWS.len()
+ streams_geo::ROWS.len()
+ admin::ROWS.len()
+ extensions::ROWS.len();
const BLANK: VerbMeta = v("", "", 0, flags::R, "", "", "", "", "");
const fn concat() -> [VerbMeta; TOTAL] {
let mut out = [BLANK; TOTAL];
let mut at = 0;
let mut f = 0;
while f < FAMILIES.len() {
let fam = FAMILIES[f];
let mut i = 0;
while i < fam.len() {
out[at] = fam[i];
at += 1;
i += 1;
}
f += 1;
}
out
}
static TABLE: [VerbMeta; TOTAL] = concat();
pub const VERB_META: &[VerbMeta] = &TABLE;
pub fn verb_meta(name: &str) -> Option<&'static VerbMeta> {
VERB_META.iter().find(|m| m.name == name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_family_row_reaches_the_table() {
assert_eq!(VERB_META.len(), TOTAL);
assert_eq!(
VERB_META.len(),
keyspace::ROWS.len()
+ collections::ROWS.len()
+ streams_geo::ROWS.len()
+ admin::ROWS.len()
+ extensions::ROWS.len()
);
assert!(VERB_META.iter().all(|m| !m.name.is_empty()), "a BLANK seed row survived");
}
#[test]
fn verb_names_are_unique() {
let mut seen: Vec<&str> = VERB_META.iter().map(|m| m.name).collect();
let before = seen.len();
seen.sort_unstable();
seen.dedup();
assert_eq!(seen.len(), before, "a verb is registered twice");
}
#[test]
fn every_verb_states_its_complexity_and_its_redis_compatibility() {
for m in VERB_META {
assert!(!m.complexity.is_empty(), "{}: no complexity", m.name);
assert!(!m.compat.is_empty(), "{}: no compat", m.name);
assert!(
m.compat == "full"
|| m.compat.starts_with("differs:")
|| m.compat.starts_with("kevy-only"),
"{}: compat must be `full`, `differs: …` or `kevy-only…`, got {:?}",
m.name,
m.compat
);
}
}
#[test]
fn the_costs_that_differ_from_redis_stay_differing() {
let hscan = verb_meta("HSCAN").expect("HSCAN");
assert!(hscan.compat.contains("not a cursor iterator"), "HSCAN is still single-batch");
}
#[test]
fn the_fixed_deviations_stay_fixed() {
for name in ["SPOP", "SRANDMEMBER", "RANDOMKEY"] {
let m = verb_meta(name).expect(name);
assert_eq!(
m.compat, "full",
"{name} is genuinely random now; the old NOT-random note must not come back"
);
}
let zrank = verb_meta("ZRANK").expect("ZRANK");
assert!(
zrank.complexity.contains("O(log N)"),
"ZRANK is O(log N) now: the (score, member) tree is rank-augmented"
);
assert!(zrank.compat.contains("WITHSCORE"), "the WITHSCORE gap is still open");
let zcount = verb_meta("ZCOUNT").expect("ZCOUNT");
assert!(zcount.complexity.contains("O(log N)"), "ZCOUNT is two rank descents now");
let scan = verb_meta("SCAN").expect("SCAN");
assert!(
scan.complexity.contains("O(COUNT) buckets per call"),
"SCAN is a real cursor iterator now; the full-sweep note must not return"
);
assert!(scan.compat.contains("(shard, position)"), "the cursor-portability caveat stays");
}
}