Skip to main content

dynomite/proto/memcache/
repair.rs

1//! Memcached repair surface.
2//!
3//! Memcached responses are never rewritten, so every repair function
4//! here returns [`RepairOutcome::NoOp`]. The one exception is
5//! reconciliation, which returns the first response when consistency
6//! is `DC_QUORUM` and an error response otherwise. The functions
7//! exist so the cluster layer can call into either protocol through
8//! the same trait set as the RESP surface.
9
10#[cfg(test)]
11use crate::msg::MsgType;
12use crate::msg::{ConsistencyLevel, DynErrorCode, Msg, ResponseMgr};
13
14/// Repair-surface result type. Matches the RESP repair return
15/// shape so the cluster dispatcher can use a single
16/// `Result<RepairOutcome, RepairError>` discriminant.
17#[derive(Debug)]
18pub enum RepairOutcome {
19    /// No rewrite or repair message produced.
20    NoOp,
21    /// A rewritten message (Memcached path never produces one).
22    Rewritten(Box<Msg>),
23}
24
25/// Errors the Memcached repair surface can raise. Memcached has no
26/// live failure modes; the variant exists to mirror the RESP
27/// surface.
28#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)]
29#[non_exhaustive]
30pub enum RepairError {
31    /// Reserved variant.
32    #[error("memcache repair: reserved")]
33    Reserved,
34}
35
36/// Memcached query rewrite. Memcached responses are not rewritten,
37/// so this returns [`RepairOutcome::NoOp`].
38///
39/// # Examples
40///
41/// ```
42/// use dynomite::msg::{Msg, MsgType};
43/// use dynomite::proto::memcache::memcache_rewrite_query;
44///
45/// let mut m = Msg::new(0, MsgType::ReqMcSet, true);
46/// let outcome = memcache_rewrite_query(&mut m).unwrap();
47/// matches!(outcome, dynomite::proto::memcache::repair::RepairOutcome::NoOp);
48/// ```
49pub fn memcache_rewrite_query(_orig: &mut Msg) -> Result<RepairOutcome, RepairError> {
50    Ok(RepairOutcome::NoOp)
51}
52
53/// Memcached query rewrite with timestamp metadata. Memcached
54/// responses are not rewritten, so this returns
55/// [`RepairOutcome::NoOp`].
56///
57/// # Examples
58///
59/// ```
60/// use dynomite::msg::{Msg, MsgType};
61/// use dynomite::proto::memcache::memcache_rewrite_query_with_timestamp_md;
62///
63/// let mut m = Msg::new(0, MsgType::ReqMcSet, true);
64/// assert!(memcache_rewrite_query_with_timestamp_md(&mut m).is_ok());
65/// ```
66pub fn memcache_rewrite_query_with_timestamp_md(
67    _orig: &mut Msg,
68) -> Result<RepairOutcome, RepairError> {
69    Ok(RepairOutcome::NoOp)
70}
71
72/// Build a repair query for a Memcached response set. Memcached has
73/// no repair query, so this returns [`RepairOutcome::NoOp`].
74///
75/// # Examples
76///
77/// ```
78/// use dynomite::msg::{Msg, MsgType, ResponseMgr};
79/// use dynomite::proto::memcache::memcache_make_repair_query;
80///
81/// let req = Msg::new(0, MsgType::ReqMcGet, true);
82/// let mgr = ResponseMgr::new(&req, 1, None);
83/// assert!(memcache_make_repair_query(&mgr).is_ok());
84/// ```
85pub fn memcache_make_repair_query(_rspmgr: &ResponseMgr) -> Result<RepairOutcome, RepairError> {
86    Ok(RepairOutcome::NoOp)
87}
88
89/// Clear repair metadata for a key. Memcached carries no repair
90/// metadata, so this returns [`RepairOutcome::NoOp`].
91///
92/// # Examples
93///
94/// ```
95/// use dynomite::msg::{Msg, MsgType};
96/// use dynomite::proto::memcache::memcache_clear_repair_md_for_key;
97///
98/// let mut req = Msg::new(0, MsgType::ReqMcSet, true);
99/// assert!(memcache_clear_repair_md_for_key(&mut req).is_ok());
100/// ```
101pub fn memcache_clear_repair_md_for_key(_req: &mut Msg) -> Result<RepairOutcome, RepairError> {
102    Ok(RepairOutcome::NoOp)
103}
104
105/// Reconcile responses across replicas. Picks
106/// the first response under `DC_QUORUM` and otherwise returns an
107/// error response, reporting
108/// the chosen index plus an optional fresh error response.
109///
110/// # Examples
111///
112/// ```
113/// use dynomite::msg::{ConsistencyLevel, Msg, MsgType, ResponseMgr};
114/// use dynomite::proto::memcache::memcache_reconcile_responses;
115///
116/// let mut req = Msg::new(0, MsgType::ReqMcGet, true);
117/// req.set_consistency(ConsistencyLevel::DcQuorum);
118/// let mut mgr = ResponseMgr::new(&req, 3, None);
119/// // Submit one response (the connection layer supplies the
120/// // responses in real use).
121/// let outcome = memcache_reconcile_responses(&mgr, ConsistencyLevel::DcQuorum);
122/// matches!(outcome, dynomite::proto::memcache::repair::ReconcileOutcome::PickFirst);
123/// ```
124pub fn memcache_reconcile_responses(
125    _rspmgr: &ResponseMgr,
126    consistency: ConsistencyLevel,
127) -> ReconcileOutcome {
128    match consistency {
129        ConsistencyLevel::DcQuorum => ReconcileOutcome::PickFirst,
130        _ => ReconcileOutcome::Error(DynErrorCode::DynomiteNoQuorumAchieved),
131    }
132}
133
134/// Outcome of [`memcache_reconcile_responses`].
135#[derive(Copy, Clone, Debug, Eq, PartialEq)]
136pub enum ReconcileOutcome {
137    /// Return the first response in the response manager.
138    PickFirst,
139    /// Allocate and return a fresh error response with the given
140    /// error code.
141    Error(DynErrorCode),
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn rewrite_is_noop() {
150        let mut m = Msg::new(0, MsgType::ReqMcSet, true);
151        let outcome = memcache_rewrite_query(&mut m).unwrap();
152        assert!(matches!(outcome, RepairOutcome::NoOp));
153    }
154
155    #[test]
156    fn reconcile_quorum_picks_first() {
157        let req = Msg::new(0, MsgType::ReqMcGet, true);
158        let mgr = ResponseMgr::new(&req, 3, None);
159        let outcome = memcache_reconcile_responses(&mgr, ConsistencyLevel::DcQuorum);
160        assert_eq!(outcome, ReconcileOutcome::PickFirst);
161    }
162
163    #[test]
164    fn reconcile_safe_quorum_emits_error() {
165        let req = Msg::new(0, MsgType::ReqMcGet, true);
166        let mgr = ResponseMgr::new(&req, 3, None);
167        let outcome = memcache_reconcile_responses(&mgr, ConsistencyLevel::DcSafeQuorum);
168        assert_eq!(
169            outcome,
170            ReconcileOutcome::Error(DynErrorCode::DynomiteNoQuorumAchieved),
171        );
172    }
173}