use crate::error::ValueError;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct NotificationFlags(u16);
const LETTERS: [(char, NotificationFlags); 12] = [
('K', NotificationFlags::KEYSPACE),
('E', NotificationFlags::KEYEVENT),
('g', NotificationFlags::GENERIC),
('$', NotificationFlags::STRING),
('l', NotificationFlags::LIST),
('s', NotificationFlags::SET),
('h', NotificationFlags::HASH),
('z', NotificationFlags::ZSET),
('t', NotificationFlags::STREAM),
('x', NotificationFlags::EXPIRED),
('e', NotificationFlags::EVICTED),
('n', NotificationFlags::NEW_KEY),
];
impl NotificationFlags {
pub const NONE: Self = Self(0);
pub const KEYSPACE: Self = Self(1 << 0);
pub const KEYEVENT: Self = Self(1 << 1);
pub const GENERIC: Self = Self(1 << 2);
pub const STRING: Self = Self(1 << 3);
pub const LIST: Self = Self(1 << 4);
pub const SET: Self = Self(1 << 5);
pub const HASH: Self = Self(1 << 6);
pub const ZSET: Self = Self(1 << 7);
pub const STREAM: Self = Self(1 << 8);
pub const EXPIRED: Self = Self(1 << 9);
pub const EVICTED: Self = Self(1 << 10);
pub const NEW_KEY: Self = Self(1 << 11);
pub const ALL_CLASSES: Self = Self(
Self::GENERIC.0
| Self::STRING.0
| Self::LIST.0
| Self::SET.0
| Self::HASH.0
| Self::ZSET.0
| Self::STREAM.0
| Self::EXPIRED.0
| Self::EVICTED.0,
);
const CHANNELS: u16 = Self::KEYSPACE.0 | Self::KEYEVENT.0;
const CLASSES: u16 = Self::ALL_CLASSES.0 | Self::NEW_KEY.0;
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
pub fn remove(&mut self, other: Self) {
self.0 &= !other.0;
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn is_active(self) -> bool {
self.0 & Self::CHANNELS != 0 && self.0 & Self::CLASSES != 0
}
}
impl core::ops::BitOr for NotificationFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl core::ops::BitOrAssign for NotificationFlags {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl core::str::FromStr for NotificationFlags {
type Err = ValueError;
fn from_str(s: &str) -> Result<Self, ValueError> {
let mut f = Self::NONE;
for c in s.chars() {
match LETTERS.iter().find(|(l, _)| *l == c) {
Some((_, bit)) => f.insert(*bit),
None if c == 'A' => f.insert(Self::ALL_CLASSES),
None => return Err(ValueError::new(format!("unknown flag char {c:?}"))),
}
}
Ok(f)
}
}
impl core::fmt::Display for NotificationFlags {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for (l, bit) in LETTERS {
if self.contains(bit) {
core::fmt::Write::write_char(f, l)?;
}
}
Ok(())
}
}
impl core::fmt::Debug for NotificationFlags {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("NotificationFlags").field(&self.to_string()).finish()
}
}
#[cfg(test)]
mod tests {
use super::NotificationFlags as F;
#[test]
fn every_letter_round_trips_and_a_is_everything_but_n() {
for c in "KEg$lshztxen".chars() {
let f: F = c.to_string().parse().expect("a known letter");
assert_eq!(f.to_string(), c.to_string());
}
let a: F = "A".parse().expect("the alias");
assert_eq!(a.to_string(), "g$lshztxe");
assert_eq!(a, F::ALL_CLASSES);
let all: F = "KEA n".replace(' ', "").parse().expect("all letters");
assert!(all.contains(F::NEW_KEY) && all.is_active());
}
#[test]
fn unknown_letters_are_refused_by_name() {
let e = "KEZ".parse::<F>().expect_err("Z is not a flag");
assert_eq!(e.to_string(), "unknown flag char 'Z'");
assert_eq!("".parse::<F>().ok(), Some(F::NONE));
}
#[test]
fn flags_merge_clear_and_empty_as_a_set() {
let mut f = F::KEYSPACE;
f |= F::HASH | F::LIST;
assert_eq!(f.to_string(), "Klh");
f.remove(F::HASH | F::SET);
assert_eq!(f, F::KEYSPACE | F::LIST);
assert!(!f.is_empty());
f.remove(f);
assert!(f.is_empty());
assert_eq!(f, F::NONE);
}
#[test]
fn debug_shows_the_letters_and_display_passes_on_a_writer_failure() {
assert_eq!(format!("{:?}", F::KEYEVENT | F::EXPIRED), "NotificationFlags(\"Ex\")");
struct Refuses;
impl core::fmt::Write for Refuses {
fn write_str(&mut self, _: &str) -> core::fmt::Result {
Err(core::fmt::Error)
}
}
assert!(core::fmt::write(&mut Refuses, format_args!("{}", F::KEYSPACE)).is_err());
}
}