memcached-async 0.0.2

Asynchronous memcached protocol parser
Documentation
use bytes::Bytes;

/// Canonical memcached operation.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Op {
    Get,
    Gets,
    Gat,
    Gats,
    Set,
    Add,
    Replace,
    Append,
    Prepend,
    Cas,
    Delete,
    Flush,
    Incr,
    Decr,
    Touch,
    Stats,
    Version,
    SaslListMechs,
    SaslAuth,
    SaslStep,
    Quit,
    MetaGet,
    MetaSet,
    MetaDelete,
    MetaArithmetic,
    MetaDebug,
    MetaNoop,
    Noop,
    Unknown,
}

/// Connection-level protocol.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Protocol {
    Ascii,
    Meta,
    Binary,
}

/// Reply suppression policy.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ReplyMode {
    /// Always return a response.
    Always,
    /// Suppress success responses; errors still returned.
    SuppressSuccess,
    /// Suppress miss responses for meta-get.
    SuppressMiss,
    /// Binary quiet mode: buffer/suppress based on opcode.
    QuietBuffered,
}

/// Parsed request from any protocol.
#[derive(Debug, Clone)]
pub struct Request {
    pub op: Op,
    pub key: Option<Bytes>,
    pub keys: Vec<Bytes>,
    pub value: Option<Bytes>,
    pub flags: Option<u32>,
    pub exptime: Option<i64>,
    pub cas: Option<u64>,
    pub delta: Option<u64>,
    pub initial: Option<u64>,
    pub meta: Option<MetaFlags>,
}

impl Request {
    pub fn new(op: Op) -> Self {
        Self {
            op,
            key: None,
            keys: Vec::new(),
            value: None,
            flags: None,
            exptime: None,
            cas: None,
            delta: None,
            initial: None,
            meta: None,
        }
    }
}

/// Protocol-specific metadata carried with a request.
#[derive(Debug, Clone, Copy)]
pub struct RequestMeta {
    pub protocol: Protocol,
    pub reply: ReplyMode,
    pub opaque: Option<u32>,
    pub return_key: bool,
    pub opcode: u8,
}

impl RequestMeta {
    pub fn ascii() -> Self {
        Self {
            protocol: Protocol::Ascii,
            reply: ReplyMode::Always,
            opaque: None,
            return_key: false,
            opcode: 0,
        }
    }
}

/// Ordered meta flags with a compact mask for fast lookup.
#[derive(Debug, Clone)]
pub struct MetaFlags {
    pub ordered: Vec<MetaFlag>,
    pub mask: u64,
}

impl MetaFlags {
    pub fn new(ordered: Vec<MetaFlag>) -> Self {
        let mut mask = 0u64;
        for flag in &ordered {
            if let Some(bit) = flag_bit(flag.code) {
                mask |= 1u64 << bit;
            }
        }
        Self { ordered, mask }
    }

    pub fn has(&self, code: u8) -> bool {
        if let Some(bit) = flag_bit(code)
            && (self.mask & (1u64 << bit)) != 0
        {
            return true;
        }
        self.ordered.iter().any(|flag| flag.code == code)
    }

    pub fn token(&self, code: u8) -> Option<&Bytes> {
        self.ordered
            .iter()
            .find(|flag| flag.code == code)
            .and_then(|flag| flag.token.as_ref())
    }
}

fn flag_bit(code: u8) -> Option<u64> {
    match code {
        b'a'..=b'z' => Some((code - b'a') as u64),
        b'A'..=b'Z' => Some((26 + (code - b'A')) as u64),
        b'0'..=b'9' => Some((52 + (code - b'0')) as u64),
        _ => None,
    }
}

/// A single meta flag token.
#[derive(Debug, Clone)]
pub struct MetaFlag {
    pub code: u8,
    pub token: Option<Bytes>,
}

/// Meta response status code.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum MetaCode {
    Hd,
    Va,
    En,
    Ns,
    Ex,
    Nf,
}

impl MetaCode {
    pub fn as_bytes(self) -> &'static [u8] {
        match self {
            MetaCode::Hd => b"HD",
            MetaCode::Va => b"VA",
            MetaCode::En => b"EN",
            MetaCode::Ns => b"NS",
            MetaCode::Ex => b"EX",
            MetaCode::Nf => b"NF",
        }
    }

    pub fn is_success(self) -> bool {
        matches!(self, MetaCode::Hd | MetaCode::Va)
    }

    pub fn is_miss(self) -> bool {
        matches!(self, MetaCode::En)
    }
}

/// Meta response payload.
#[derive(Debug, Clone)]
pub struct MetaResponse {
    pub code: MetaCode,
    pub value: Option<Bytes>,
    pub cas: Option<u64>,
    pub flags: Option<u32>,
    pub ttl: Option<i64>,
    pub size: Option<usize>,
    pub hit: Option<bool>,
    pub last_access: Option<u64>,
    pub extra: MetaExtra,
}

impl MetaResponse {
    pub fn new(code: MetaCode) -> Self {
        Self {
            code,
            value: None,
            cas: None,
            flags: None,
            ttl: None,
            size: None,
            hit: None,
            last_access: None,
            extra: MetaExtra::default(),
        }
    }
}

/// Extra meta flags (W/X/Z).
#[derive(Debug, Clone, Default)]
pub struct MetaExtra {
    pub won: Option<WinState>,
    pub stale: bool,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum WinState {
    Won,
    AlreadyWon,
}

/// A single value entry for get/gets responses.
#[derive(Debug, Clone)]
pub struct ValueEntry {
    pub key: Bytes,
    pub value: Bytes,
    pub flags: u32,
    pub cas: Option<u64>,
}

/// A stat line (key/value pair).
#[derive(Debug, Clone)]
pub struct StatLine {
    pub key: Bytes,
    pub value: Bytes,
}

/// Streaming values iterator.
pub trait ValuesStream {
    fn next(&mut self) -> Option<ValueEntry>;
}

/// Streaming stats iterator.
pub trait StatsStream {
    fn next(&mut self) -> Option<StatLine>;
}