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