1use crate::DetRng;
28
29#[derive(Debug, Clone)]
31pub enum FaultCheck {
32 Pass,
34 Inject { message: String },
36 Delay { us: u64 },
38}
39
40pub struct PluginContext<'a> {
42 pub rng: &'a mut DetRng,
44 pub seed: u64,
46 pub metadata: &'a std::collections::HashMap<String, String>,
48}
49
50pub trait VortexPlugin: Send + Sync {
55 fn name(&self) -> &str;
57
58 fn check_fault(&self, ctx: &mut PluginContext<'_>, operation: &str) -> FaultCheck;
64
65 fn on_register(&self) {}
67
68 fn on_shutdown(&self) {}
70}
71
72pub struct PluginRegistry {
74 plugins: Vec<Box<dyn VortexPlugin>>,
75}
76
77impl PluginRegistry {
78 pub fn new() -> Self {
80 Self {
81 plugins: Vec::new(),
82 }
83 }
84
85 pub fn register(&mut self, plugin: Box<dyn VortexPlugin>) {
87 plugin.on_register();
88 self.plugins.push(plugin);
89 }
90
91 pub fn check_all(
93 &self,
94 rng: &mut DetRng,
95 seed: u64,
96 operation: &str,
97 metadata: &std::collections::HashMap<String, String>,
98 ) -> FaultCheck {
99 for plugin in &self.plugins {
100 let mut ctx = PluginContext {
101 rng,
102 seed,
103 metadata,
104 };
105 match plugin.check_fault(&mut ctx, operation) {
106 FaultCheck::Pass => continue,
107 fault => return fault,
108 }
109 }
110 FaultCheck::Pass
111 }
112
113 pub fn shutdown(&self) {
115 for plugin in &self.plugins {
116 plugin.on_shutdown();
117 }
118 }
119
120 pub fn len(&self) -> usize {
122 self.plugins.len()
123 }
124
125 pub fn is_empty(&self) -> bool {
127 self.plugins.is_empty()
128 }
129}
130
131impl Default for PluginRegistry {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 struct TestPlugin {
142 probability: f64,
143 }
144
145 impl VortexPlugin for TestPlugin {
146 fn name(&self) -> &str {
147 "test"
148 }
149
150 fn check_fault(&self, ctx: &mut PluginContext<'_>, _operation: &str) -> FaultCheck {
151 if ctx.rng.chance(self.probability) {
152 FaultCheck::Inject {
153 message: "test fault".into(),
154 }
155 } else {
156 FaultCheck::Pass
157 }
158 }
159 }
160
161 #[test]
162 fn test_plugin_registry() {
163 let mut registry = PluginRegistry::new();
164 assert!(registry.is_empty());
165
166 registry.register(Box::new(TestPlugin { probability: 1.0 }));
167 assert_eq!(registry.len(), 1);
168
169 let mut rng = DetRng::new(42);
170 let metadata = std::collections::HashMap::new();
171 let result = registry.check_all(&mut rng, 42, "test:op", &metadata);
172 assert!(matches!(result, FaultCheck::Inject { .. }));
173 }
174
175 #[test]
176 fn test_plugin_pass() {
177 let mut registry = PluginRegistry::new();
178 registry.register(Box::new(TestPlugin { probability: 0.0 }));
179
180 let mut rng = DetRng::new(42);
181 let metadata = std::collections::HashMap::new();
182 let result = registry.check_all(&mut rng, 42, "test:op", &metadata);
183 assert!(matches!(result, FaultCheck::Pass));
184 }
185
186 #[test]
187 fn test_multiple_plugins() {
188 let mut registry = PluginRegistry::new();
189 registry.register(Box::new(TestPlugin { probability: 0.0 })); registry.register(Box::new(TestPlugin { probability: 1.0 })); let mut rng = DetRng::new(42);
193 let metadata = std::collections::HashMap::new();
194 let result = registry.check_all(&mut rng, 42, "test:op", &metadata);
195 assert!(matches!(result, FaultCheck::Inject { .. }));
197 }
198}