1use serde::{Deserialize, Serialize};
22
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum RecoveryPolicy {
30 #[default]
34 Strict,
35 AutoReset,
38}
39
40#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47#[repr(u8)]
48pub enum ReactionRecoveryPolicy {
49 #[default]
52 Strict,
53 AutoReset,
56 AutoSkipGap,
59}
60
61#[derive(Debug, thiserror::Error)]
63pub enum RecoveryError {
64 #[error("Incompatible source '{source_id}' for persistent query '{query_id}': {reason}")]
68 IncompatibleSource {
69 query_id: String,
70 source_id: String,
71 reason: String,
72 },
73
74 #[error("Auto-reset triggered for query '{query_id}': {reason}")]
77 AutoResetTriggered { query_id: String, reason: String },
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn default_is_strict() {
86 assert_eq!(RecoveryPolicy::default(), RecoveryPolicy::Strict);
87 }
88
89 #[test]
90 fn json_round_trip_strict() {
91 let json = serde_json::to_string(&RecoveryPolicy::Strict).unwrap();
92 assert_eq!(json, "\"strict\"");
93 let back: RecoveryPolicy = serde_json::from_str(&json).unwrap();
94 assert_eq!(back, RecoveryPolicy::Strict);
95 }
96
97 #[test]
98 fn json_round_trip_auto_reset() {
99 let json = serde_json::to_string(&RecoveryPolicy::AutoReset).unwrap();
100 assert_eq!(json, "\"auto_reset\"");
101 let back: RecoveryPolicy = serde_json::from_str(&json).unwrap();
102 assert_eq!(back, RecoveryPolicy::AutoReset);
103 }
104
105 #[test]
106 fn yaml_round_trip() {
107 for policy in [RecoveryPolicy::Strict, RecoveryPolicy::AutoReset] {
108 let yaml = serde_yaml::to_string(&policy).unwrap();
109 let back: RecoveryPolicy = serde_yaml::from_str(&yaml).unwrap();
110 assert_eq!(back, policy);
111 }
112 }
113
114 #[test]
115 fn incompatible_source_message() {
116 let err = RecoveryError::IncompatibleSource {
117 query_id: "q1".into(),
118 source_id: "s1".into(),
119 reason: "no WAL".into(),
120 };
121 let msg = err.to_string();
122 assert!(msg.contains("q1"));
123 assert!(msg.contains("s1"));
124 assert!(msg.contains("no WAL"));
125 }
126
127 #[test]
128 fn auto_reset_message() {
129 let err = RecoveryError::AutoResetTriggered {
130 query_id: "q1".into(),
131 reason: "gap".into(),
132 };
133 let msg = err.to_string();
134 assert!(msg.contains("q1"));
135 assert!(msg.contains("gap"));
136 }
137
138 #[test]
139 fn reaction_default_is_strict() {
140 assert_eq!(
141 ReactionRecoveryPolicy::default(),
142 ReactionRecoveryPolicy::Strict
143 );
144 }
145
146 #[test]
147 fn reaction_json_round_trip() {
148 for (policy, expected_json) in [
149 (ReactionRecoveryPolicy::Strict, "\"strict\""),
150 (ReactionRecoveryPolicy::AutoReset, "\"auto_reset\""),
151 (ReactionRecoveryPolicy::AutoSkipGap, "\"auto_skip_gap\""),
152 ] {
153 let json = serde_json::to_string(&policy).unwrap();
154 assert_eq!(json, expected_json);
155 let back: ReactionRecoveryPolicy = serde_json::from_str(&json).unwrap();
156 assert_eq!(back, policy);
157 }
158 }
159
160 #[test]
161 fn reaction_yaml_round_trip() {
162 for policy in [
163 ReactionRecoveryPolicy::Strict,
164 ReactionRecoveryPolicy::AutoReset,
165 ReactionRecoveryPolicy::AutoSkipGap,
166 ] {
167 let yaml = serde_yaml::to_string(&policy).unwrap();
168 let back: ReactionRecoveryPolicy = serde_yaml::from_str(&yaml).unwrap();
169 assert_eq!(back, policy);
170 }
171 }
172}