1use std::time::{Duration, SystemTime};
4
5use serde::{Deserialize, Serialize};
6
7use super::{ConfigUpdate, RuntimeState};
8
9#[derive(Default)]
11pub struct UpdateBuilder {
12 updates: Vec<ConfigUpdate>,
13}
14
15impl UpdateBuilder {
16 pub fn new() -> Self {
18 Self::default()
19 }
20
21 pub fn max_connections(mut self, max: usize) -> Self {
23 self.updates.push(ConfigUpdate::MaxConnections(max));
24 self
25 }
26
27 pub fn idle_timeout(mut self, duration: Duration) -> Self {
29 self.updates.push(ConfigUpdate::IdleTimeout(duration));
30 self
31 }
32
33 pub fn request_timeout(mut self, duration: Duration) -> Self {
35 self.updates.push(ConfigUpdate::RequestTimeout(duration));
36 self
37 }
38
39 pub fn unit_enabled(mut self, unit_id: u8, enabled: bool) -> Self {
41 self.updates
42 .push(ConfigUpdate::UnitEnabled { unit_id, enabled });
43 self
44 }
45
46 pub fn tcp_nodelay(mut self, enabled: bool) -> Self {
48 self.updates.push(ConfigUpdate::TcpNoDelay(enabled));
49 self
50 }
51
52 pub fn keepalive(mut self, interval: Option<Duration>) -> Self {
54 self.updates.push(ConfigUpdate::KeepaliveInterval(interval));
55 self
56 }
57
58 pub fn metrics_enabled(mut self, enabled: bool) -> Self {
60 self.updates.push(ConfigUpdate::MetricsEnabled(enabled));
61 self
62 }
63
64 pub fn debug_logging(mut self, enabled: bool) -> Self {
66 self.updates.push(ConfigUpdate::DebugLogging(enabled));
67 self
68 }
69
70 pub fn set_register(mut self, unit_id: u8, address: u16, value: u16) -> Self {
72 self.updates.push(ConfigUpdate::SetRegister {
73 unit_id,
74 address,
75 value,
76 });
77 self
78 }
79
80 pub fn set_registers(mut self, unit_id: u8, start_address: u16, values: Vec<u16>) -> Self {
82 self.updates.push(ConfigUpdate::SetRegisters {
83 unit_id,
84 start_address,
85 values,
86 });
87 self
88 }
89
90 pub fn set_coil(mut self, unit_id: u8, address: u16, value: bool) -> Self {
92 self.updates.push(ConfigUpdate::SetCoil {
93 unit_id,
94 address,
95 value,
96 });
97 self
98 }
99
100 pub fn custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
102 self.updates.push(ConfigUpdate::Custom {
103 key: key.into(),
104 value: value.into(),
105 });
106 self
107 }
108
109 pub fn update(mut self, update: ConfigUpdate) -> Self {
111 self.updates.push(update);
112 self
113 }
114
115 pub fn build(self) -> Vec<ConfigUpdate> {
117 self.updates
118 }
119
120 pub fn len(&self) -> usize {
122 self.updates.len()
123 }
124
125 pub fn is_empty(&self) -> bool {
127 self.updates.is_empty()
128 }
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct UpdateRecord {
134 pub id: u64,
136
137 pub update: ConfigUpdate,
139
140 pub timestamp: SystemTime,
142
143 pub success: bool,
145
146 pub error: Option<String>,
148
149 pub source: Option<String>,
151}
152
153impl UpdateRecord {
154 pub fn success(id: u64, update: ConfigUpdate) -> Self {
156 Self {
157 id,
158 update,
159 timestamp: SystemTime::now(),
160 success: true,
161 error: None,
162 source: None,
163 }
164 }
165
166 pub fn failure(id: u64, update: ConfigUpdate, error: String) -> Self {
168 Self {
169 id,
170 update,
171 timestamp: SystemTime::now(),
172 success: false,
173 error: Some(error),
174 source: None,
175 }
176 }
177
178 pub fn with_source(mut self, source: impl Into<String>) -> Self {
180 self.source = Some(source.into());
181 self
182 }
183}
184
185pub struct UpdateAuditLog {
187 records: Vec<UpdateRecord>,
188 max_records: usize,
189 next_id: u64,
190}
191
192impl UpdateAuditLog {
193 pub fn new(max_records: usize) -> Self {
195 Self {
196 records: Vec::with_capacity(max_records.min(1000)),
197 max_records,
198 next_id: 1,
199 }
200 }
201
202 pub fn record_success(&mut self, update: ConfigUpdate) -> &UpdateRecord {
204 let id = self.next_id;
205 self.next_id += 1;
206
207 self.add_record(UpdateRecord::success(id, update))
208 }
209
210 pub fn record_failure(&mut self, update: ConfigUpdate, error: String) -> &UpdateRecord {
212 let id = self.next_id;
213 self.next_id += 1;
214
215 self.add_record(UpdateRecord::failure(id, update, error))
216 }
217
218 fn add_record(&mut self, record: UpdateRecord) -> &UpdateRecord {
219 while self.records.len() >= self.max_records {
221 self.records.remove(0);
222 }
223
224 self.records.push(record);
225 self.records.last().unwrap()
226 }
227
228 pub fn records(&self) -> &[UpdateRecord] {
230 &self.records
231 }
232
233 pub fn recent(&self, n: usize) -> &[UpdateRecord] {
235 let start = self.records.len().saturating_sub(n);
236 &self.records[start..]
237 }
238
239 pub fn failures(&self) -> Vec<&UpdateRecord> {
241 self.records.iter().filter(|r| !r.success).collect()
242 }
243
244 pub fn clear(&mut self) {
246 self.records.clear();
247 }
248
249 pub fn total_count(&self) -> u64 {
251 self.next_id - 1
252 }
253
254 pub fn success_rate(&self) -> f64 {
256 if self.records.is_empty() {
257 return 1.0;
258 }
259 let successes = self.records.iter().filter(|r| r.success).count();
260 successes as f64 / self.records.len() as f64
261 }
262}
263
264impl Default for UpdateAuditLog {
265 fn default() -> Self {
266 Self::new(1000)
267 }
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct StateDiff {
273 pub changes: Vec<FieldChange>,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct FieldChange {
280 pub field: String,
282 pub old: String,
284 pub new: String,
286}
287
288impl StateDiff {
289 pub fn diff(old: &RuntimeState, new: &RuntimeState) -> Self {
291 let mut changes = Vec::new();
292
293 if old.max_connections != new.max_connections {
294 changes.push(FieldChange {
295 field: "max_connections".into(),
296 old: old.max_connections.to_string(),
297 new: new.max_connections.to_string(),
298 });
299 }
300
301 if old.idle_timeout != new.idle_timeout {
302 changes.push(FieldChange {
303 field: "idle_timeout".into(),
304 old: format!("{:?}", old.idle_timeout),
305 new: format!("{:?}", new.idle_timeout),
306 });
307 }
308
309 if old.request_timeout != new.request_timeout {
310 changes.push(FieldChange {
311 field: "request_timeout".into(),
312 old: format!("{:?}", old.request_timeout),
313 new: format!("{:?}", new.request_timeout),
314 });
315 }
316
317 if old.tcp_nodelay != new.tcp_nodelay {
318 changes.push(FieldChange {
319 field: "tcp_nodelay".into(),
320 old: old.tcp_nodelay.to_string(),
321 new: new.tcp_nodelay.to_string(),
322 });
323 }
324
325 if old.metrics_enabled != new.metrics_enabled {
326 changes.push(FieldChange {
327 field: "metrics_enabled".into(),
328 old: old.metrics_enabled.to_string(),
329 new: new.metrics_enabled.to_string(),
330 });
331 }
332
333 if old.debug_logging != new.debug_logging {
334 changes.push(FieldChange {
335 field: "debug_logging".into(),
336 old: old.debug_logging.to_string(),
337 new: new.debug_logging.to_string(),
338 });
339 }
340
341 if old.enabled_units != new.enabled_units {
342 changes.push(FieldChange {
343 field: "enabled_units".into(),
344 old: format!("{:?}", old.enabled_units),
345 new: format!("{:?}", new.enabled_units),
346 });
347 }
348
349 Self { changes }
350 }
351
352 pub fn has_changes(&self) -> bool {
354 !self.changes.is_empty()
355 }
356
357 pub fn change_count(&self) -> usize {
359 self.changes.len()
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_update_builder() {
369 let updates = UpdateBuilder::new()
370 .max_connections(200)
371 .idle_timeout(Duration::from_secs(600))
372 .metrics_enabled(false)
373 .build();
374
375 assert_eq!(updates.len(), 3);
376 }
377
378 #[test]
379 fn test_update_builder_empty() {
380 let builder = UpdateBuilder::new();
381 assert!(builder.is_empty());
382 }
383
384 #[test]
385 fn test_update_builder_registers() {
386 let updates = UpdateBuilder::new()
387 .set_register(1, 100, 1234)
388 .set_registers(1, 200, vec![1, 2, 3])
389 .set_coil(1, 50, true)
390 .build();
391
392 assert_eq!(updates.len(), 3);
393 }
394
395 #[test]
396 fn test_update_record() {
397 let record = UpdateRecord::success(1, ConfigUpdate::MaxConnections(100))
398 .with_source("test_user");
399
400 assert!(record.success);
401 assert_eq!(record.source, Some("test_user".to_string()));
402 }
403
404 #[test]
405 fn test_update_record_failure() {
406 let record = UpdateRecord::failure(
407 2,
408 ConfigUpdate::MaxConnections(0),
409 "Invalid value".into(),
410 );
411
412 assert!(!record.success);
413 assert!(record.error.is_some());
414 }
415
416 #[test]
417 fn test_audit_log() {
418 let mut log = UpdateAuditLog::new(10);
419
420 log.record_success(ConfigUpdate::MaxConnections(100));
421 log.record_success(ConfigUpdate::MaxConnections(200));
422 log.record_failure(ConfigUpdate::MaxConnections(0), "Invalid".into());
423
424 assert_eq!(log.records().len(), 3);
425 assert_eq!(log.failures().len(), 1);
426 assert_eq!(log.total_count(), 3);
427 }
428
429 #[test]
430 fn test_audit_log_capacity() {
431 let mut log = UpdateAuditLog::new(5);
432
433 for i in 0..10 {
434 log.record_success(ConfigUpdate::MaxConnections(i));
435 }
436
437 assert_eq!(log.records().len(), 5);
438 assert_eq!(log.total_count(), 10);
439 }
440
441 #[test]
442 fn test_audit_log_success_rate() {
443 let mut log = UpdateAuditLog::new(10);
444
445 log.record_success(ConfigUpdate::MaxConnections(100));
446 log.record_success(ConfigUpdate::MaxConnections(100));
447 log.record_failure(ConfigUpdate::MaxConnections(0), "Error".into());
448
449 let rate = log.success_rate();
450 assert!((rate - 0.6666).abs() < 0.01);
451 }
452
453 #[test]
454 fn test_state_diff() {
455 let old = RuntimeState::default();
456 let mut new = RuntimeState::default();
457 new.max_connections = 200;
458 new.metrics_enabled = false;
459
460 let diff = StateDiff::diff(&old, &new);
461
462 assert!(diff.has_changes());
463 assert_eq!(diff.change_count(), 2);
464 }
465
466 #[test]
467 fn test_state_diff_no_changes() {
468 let state = RuntimeState::default();
469 let diff = StateDiff::diff(&state, &state);
470
471 assert!(!diff.has_changes());
472 }
473}