reddb_server/replication/
commit_policy.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum CommitPolicy {
25 #[default]
26 Local,
27 RemoteWal,
28 AckN(u32),
29 Quorum,
30}
31
32impl CommitPolicy {
33 pub fn from_env_strict() -> Result<Self, String> {
34 match std::env::var("RED_PRIMARY_COMMIT_POLICY") {
35 Ok(raw) => Self::parse_strict(&raw)
36 .ok_or_else(|| format!("invalid RED_PRIMARY_COMMIT_POLICY: {raw}")),
37 Err(std::env::VarError::NotPresent) => Ok(Self::Local),
38 Err(_) => Err("RED_PRIMARY_COMMIT_POLICY must be valid UTF-8".into()),
39 }
40 }
41
42 pub fn label(self) -> &'static str {
43 match self {
44 Self::Local => "local",
45 Self::RemoteWal => "remote_wal",
46 Self::AckN(_) => "ack_n",
47 Self::Quorum => "quorum",
48 }
49 }
50
51 pub fn detail_label(self) -> String {
52 match self {
53 Self::AckN(n) => format!("ack_n={n}"),
54 _ => self.label().to_string(),
55 }
56 }
57
58 pub fn parse_strict(raw: &str) -> Option<Self> {
59 let lower = raw.trim().to_ascii_lowercase();
60 if lower == "local" {
61 return Some(Self::Local);
62 }
63 if lower == "remote_wal" {
64 return Some(Self::RemoteWal);
65 }
66 if lower == "quorum" {
67 return Some(Self::Quorum);
68 }
69 if let Some(n_str) = lower.strip_prefix("ack_n=") {
70 return n_str.parse::<u32>().ok().map(Self::AckN);
71 }
72 None
73 }
74
75 pub fn from_env() -> Self {
79 match std::env::var("RED_PRIMARY_COMMIT_POLICY").ok() {
80 Some(raw) => Self::parse(raw.trim()),
81 None => Self::Local,
82 }
83 }
84
85 pub fn parse(raw: &str) -> Self {
86 let lower = raw.to_ascii_lowercase();
87 if lower == "local" || lower.is_empty() {
88 return Self::Local;
89 }
90 if lower == "remote_wal" {
91 return Self::RemoteWal;
92 }
93 if lower == "quorum" {
94 return Self::Quorum;
95 }
96 if let Some(n_str) = lower.strip_prefix("ack_n=") {
97 if let Ok(n) = n_str.parse::<u32>() {
98 return Self::AckN(n);
99 }
100 }
101 tracing::warn!(
102 target: "reddb::replication::commit_policy",
103 value = %raw,
104 "unknown RED_PRIMARY_COMMIT_POLICY; falling back to local"
105 );
106 Self::Local
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn default_is_local() {
116 assert_eq!(CommitPolicy::default(), CommitPolicy::Local);
117 }
118
119 #[test]
120 fn parse_known_values() {
121 assert_eq!(CommitPolicy::parse("local"), CommitPolicy::Local);
122 assert_eq!(CommitPolicy::parse("LOCAL"), CommitPolicy::Local);
123 assert_eq!(CommitPolicy::parse("remote_wal"), CommitPolicy::RemoteWal);
124 assert_eq!(CommitPolicy::parse("quorum"), CommitPolicy::Quorum);
125 assert_eq!(CommitPolicy::parse("ack_n=3"), CommitPolicy::AckN(3));
126 assert_eq!(CommitPolicy::parse("ack_n=0"), CommitPolicy::AckN(0));
127 }
128
129 #[test]
130 fn parse_unknown_falls_back_to_local() {
131 assert_eq!(CommitPolicy::parse("nonsense"), CommitPolicy::Local);
132 assert_eq!(CommitPolicy::parse("ack_n=abc"), CommitPolicy::Local);
133 assert_eq!(CommitPolicy::parse(""), CommitPolicy::Local);
134 }
135
136 #[test]
137 fn label_round_trips_known_values() {
138 assert_eq!(CommitPolicy::Local.label(), "local");
139 assert_eq!(CommitPolicy::RemoteWal.label(), "remote_wal");
140 assert_eq!(CommitPolicy::AckN(5).label(), "ack_n");
141 assert_eq!(CommitPolicy::Quorum.label(), "quorum");
142 }
143
144 #[test]
145 fn strict_parse_rejects_unknown_values() {
146 assert_eq!(
147 CommitPolicy::parse_strict("ack_n=2"),
148 Some(CommitPolicy::AckN(2))
149 );
150 assert_eq!(CommitPolicy::parse_strict("nonsense"), None);
151 assert_eq!(CommitPolicy::parse_strict(""), None);
152 }
153}