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}