1use std::sync::Arc;
2
3use teaql_core::{Record, Value};
4
5use crate::{RuntimeError, UserContext};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum RawAuditEventKind {
9 Created,
10 Updated,
11 Deleted,
12 Recovered,
13 SchemaCreated,
15 SchemaVerified,
17 FieldAdded,
19 DataSeeded,
21}
22
23#[derive(Debug, Clone, PartialEq)]
24pub struct EntityPropertyChange {
25 pub field: String,
26 pub old_value: Option<Value>,
27 pub new_value: Option<Value>,
28}
29
30impl EntityPropertyChange {
31 pub fn new(
32 field: impl Into<String>,
33 old_value: Option<Value>,
34 new_value: Option<Value>,
35 ) -> Self {
36 Self {
37 field: field.into(),
38 old_value,
39 new_value,
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq)]
45pub struct RawAuditEvent {
46 pub kind: RawAuditEventKind,
47 pub entity: String,
48 pub values: Record,
49 pub updated_fields: Vec<String>,
50 pub old_values: Option<Record>,
51 pub new_values: Option<Record>,
52 pub changes: Vec<EntityPropertyChange>,
53 pub trace_chain: Vec<teaql_core::TraceNode>,
55}
56
57impl RawAuditEvent {
58 pub fn created(entity: impl Into<String>, values: Record) -> Self {
59 let changes = values
60 .iter()
61 .map(|(field, value)| {
62 EntityPropertyChange::new(field.clone(), None, Some(value.clone()))
63 })
64 .collect();
65 Self {
66 kind: RawAuditEventKind::Created,
67 entity: entity.into(),
68 values: values.clone(),
69 updated_fields: Vec::new(),
70 old_values: None,
71 new_values: Some(values),
72 changes,
73 trace_chain: Vec::new(),
74 }
75 }
76
77 pub fn updated(entity: impl Into<String>, values: Record) -> Self {
78 let updated_fields = values.keys().cloned().collect::<Vec<_>>();
79 let changes = Self::changes_for_fields(None, Some(&values), &updated_fields);
80 Self {
81 kind: RawAuditEventKind::Updated,
82 entity: entity.into(),
83 values: values.clone(),
84 updated_fields,
85 old_values: None,
86 new_values: Some(values),
87 changes,
88 trace_chain: Vec::new(),
89 }
90 }
91
92 pub fn updated_with_old_values(
93 entity: impl Into<String>,
94 values: Record,
95 old_values: Option<Record>,
96 new_values: Record,
97 updated_fields: Vec<String>,
98 ) -> Self {
99 let changes =
100 Self::changes_for_fields(old_values.as_ref(), Some(&new_values), &updated_fields);
101 Self {
102 kind: RawAuditEventKind::Updated,
103 entity: entity.into(),
104 values,
105 updated_fields,
106 old_values,
107 new_values: Some(new_values),
108 changes,
109 trace_chain: Vec::new(),
110 }
111 }
112
113 pub fn deleted(entity: impl Into<String>, id: Value, expected_version: Option<i64>) -> Self {
114 let mut values = Record::from([("id".to_owned(), id)]);
115 if let Some(version) = expected_version {
116 values.insert("version".to_owned(), Value::I64(version));
117 }
118 Self {
119 kind: RawAuditEventKind::Deleted,
120 entity: entity.into(),
121 values,
122 updated_fields: Vec::new(),
123 old_values: None,
124 new_values: None,
125 changes: Vec::new(),
126 trace_chain: Vec::new(),
127 }
128 }
129
130 pub fn deleted_with_old_values(
131 entity: impl Into<String>,
132 id: Value,
133 expected_version: Option<i64>,
134 old_values: Option<Record>,
135 ) -> Self {
136 let mut event = Self::deleted(entity, id, expected_version);
137 event.changes = old_values
138 .as_ref()
139 .map(|values| {
140 values
141 .iter()
142 .map(|(field, value)| {
143 EntityPropertyChange::new(field.clone(), Some(value.clone()), None)
144 })
145 .collect()
146 })
147 .unwrap_or_default();
148 event.old_values = old_values;
149 event
150 }
151
152 pub fn recovered(entity: impl Into<String>, id: Value, expected_version: i64) -> Self {
153 let values = Record::from([
154 ("id".to_owned(), id),
155 ("version".to_owned(), Value::I64(expected_version)),
156 ]);
157 Self {
158 kind: RawAuditEventKind::Recovered,
159 entity: entity.into(),
160 values,
161 updated_fields: Vec::new(),
162 old_values: None,
163 new_values: None,
164 changes: Vec::new(),
165 trace_chain: Vec::new(),
166 }
167 }
168
169 pub fn recovered_with_old_values(
170 entity: impl Into<String>,
171 id: Value,
172 expected_version: i64,
173 old_values: Option<Record>,
174 ) -> Self {
175 let recovered_version = -expected_version + 1;
176 let mut new_values = old_values.clone().unwrap_or_default();
177 new_values.insert("id".to_owned(), id.clone());
178 new_values.insert("version".to_owned(), Value::I64(recovered_version));
179 let mut event = Self::recovered(entity, id, expected_version);
180 event.old_values = old_values;
181 event.new_values = Some(new_values.clone());
182 event.changes = Self::changes_for_fields(
183 event.old_values.as_ref(),
184 Some(&new_values),
185 &["version".to_owned()],
186 );
187 event
188 }
189
190 pub fn schema_created(
192 entity: impl Into<String>,
193 table_name: impl Into<String>,
194 field_count: usize,
195 ) -> Self {
196 let entity = entity.into();
197 let values = Record::from([
198 ("table_name".to_owned(), Value::Text(table_name.into())),
199 ("field_count".to_owned(), Value::I64(field_count as i64)),
200 ]);
201 let changes = values
202 .iter()
203 .map(|(k, v)| EntityPropertyChange::new(k.clone(), None, Some(v.clone())))
204 .collect();
205 Self {
206 kind: RawAuditEventKind::SchemaCreated,
207 entity,
208 values,
209 updated_fields: Vec::new(),
210 old_values: None,
211 new_values: None,
212 changes,
213 trace_chain: Vec::new(),
214 }
215 }
216
217 pub fn schema_verified(
219 entity: impl Into<String>,
220 table_name: impl Into<String>,
221 field_count: usize,
222 ) -> Self {
223 let entity = entity.into();
224 let values = Record::from([
225 ("table_name".to_owned(), Value::Text(table_name.into())),
226 ("field_count".to_owned(), Value::I64(field_count as i64)),
227 ]);
228 let changes = values
229 .iter()
230 .map(|(k, v)| EntityPropertyChange::new(k.clone(), None, Some(v.clone())))
231 .collect();
232 Self {
233 kind: RawAuditEventKind::SchemaVerified,
234 entity,
235 values,
236 updated_fields: Vec::new(),
237 old_values: None,
238 new_values: None,
239 changes,
240 trace_chain: Vec::new(),
241 }
242 }
243
244 pub fn field_added(
246 entity: impl Into<String>,
247 table_name: impl Into<String>,
248 field_name: impl Into<String>,
249 ) -> Self {
250 let entity = entity.into();
251 let values = Record::from([
252 ("table_name".to_owned(), Value::Text(table_name.into())),
253 ("field_name".to_owned(), Value::Text(field_name.into())),
254 ]);
255 let changes = values
256 .iter()
257 .map(|(k, v)| EntityPropertyChange::new(k.clone(), None, Some(v.clone())))
258 .collect();
259 Self {
260 kind: RawAuditEventKind::FieldAdded,
261 entity,
262 values,
263 updated_fields: Vec::new(),
264 old_values: None,
265 new_values: None,
266 changes,
267 trace_chain: Vec::new(),
268 }
269 }
270
271 pub fn data_seeded(
273 entity: impl Into<String>,
274 table_name: impl Into<String>,
275 inserted: usize,
276 updated: usize,
277 ) -> Self {
278 let entity = entity.into();
279 let values = Record::from([
280 ("table_name".to_owned(), Value::Text(table_name.into())),
281 ("inserted".to_owned(), Value::I64(inserted as i64)),
282 ("updated".to_owned(), Value::I64(updated as i64)),
283 ]);
284 let changes = values
285 .iter()
286 .map(|(k, v)| EntityPropertyChange::new(k.clone(), None, Some(v.clone())))
287 .collect();
288 Self {
289 kind: RawAuditEventKind::DataSeeded,
290 entity,
291 values,
292 updated_fields: Vec::new(),
293 old_values: None,
294 new_values: None,
295 changes,
296 trace_chain: Vec::new(),
297 }
298 }
299
300 fn changes_for_fields(
301 old_values: Option<&Record>,
302 new_values: Option<&Record>,
303 fields: &[String],
304 ) -> Vec<EntityPropertyChange> {
305 fields
306 .iter()
307 .map(|field| {
308 EntityPropertyChange::new(
309 field.clone(),
310 old_values.and_then(|values| values.get(field).cloned()),
311 new_values.and_then(|values| values.get(field).cloned()),
312 )
313 })
314 .collect()
315 }
316
317 pub fn build_safe_event(
318 &self,
319 audit_mask_fields: &[String],
320 audit_value_max_len: Option<usize>,
321 ) -> SafeAuditEvent {
322 let mut safe_fields = Vec::new();
323 for change in &self.changes {
324 if change.field.starts_with('_') {
325 continue;
326 }
327 let raw_val_str = change.new_value.as_ref().map(|v| format!("{:?}", v));
331 let safe_field = build_safe_audit_field(
332 &change.field,
333 raw_val_str.as_deref(),
334 audit_mask_fields,
335 audit_value_max_len,
336 );
337 safe_fields.push(safe_field);
338 }
339
340 SafeAuditEvent {
341 kind: self.kind,
342 entity: self.entity.clone(),
343 fields: safe_fields,
344 trace_chain: self.trace_chain.clone(),
345 }
346 }
347}
348
349pub fn mask_audit_value(value: &str) -> String {
350 let chars: Vec<char> = value.chars().collect();
351 let len = chars.len();
352
353 if len == 0 {
354 return String::new();
355 }
356
357 if chars.iter().all(|c| c.is_ascii_digit()) {
358 return "*".repeat(len);
359 }
360
361 if len < 8 {
362 return "*".repeat(len);
363 }
364
365 let prefix: String = chars[0..2].iter().collect();
366 let suffix: String = chars[len - 2..len].iter().collect();
367 let middle = "*".repeat(len - 4);
368
369 format!("{}{}{}", prefix, middle, suffix)
370}
371
372pub fn limit_audit_value(value: &str, max_len: usize) -> (String, bool) {
373 let chars: Vec<char> = value.chars().collect();
374 let len = chars.len();
375
376 if len <= max_len {
377 return (value.to_string(), false);
378 }
379
380 if max_len <= 3 {
381 return ("*".repeat(max_len), true);
382 }
383
384 let marker = "...";
385 let keep_len = max_len - marker.len();
386 let head_len = keep_len / 2;
387 let tail_len = keep_len - head_len;
388
389 let head: String = chars[0..head_len].iter().collect();
390 let tail: String = chars[len - tail_len..len].iter().collect();
391
392 (format!("{}{}{}", head, marker, tail), true)
393}
394
395pub fn build_safe_audit_field(
396 field_name: &str,
397 raw_value: Option<&str>,
398 audit_mask_fields: &[String],
399 audit_value_max_len: Option<usize>,
400) -> SafeAuditField {
401 match raw_value {
402 None => SafeAuditField {
403 name: field_name.to_string(),
404 value: None,
405 masked: false,
406 truncated: false,
407 raw_length: None,
408 output_length: None,
409 mask_reason: None,
410 truncate_reason: None,
411 },
412 Some(raw) => {
413 let raw_length = raw.chars().count();
414 let should_mask = audit_mask_fields.iter().any(|f| f == field_name);
415
416 let mut value = if should_mask {
417 mask_audit_value(raw)
418 } else {
419 raw.to_string()
420 };
421
422 let mut truncated = false;
423 if let Some(max_len) = audit_value_max_len {
424 let result = limit_audit_value(&value, max_len);
425 value = result.0;
426 truncated = result.1;
427 }
428
429 let output_length = value.chars().count();
430
431 SafeAuditField {
432 name: field_name.to_string(),
433 value: Some(value),
434 masked: should_mask,
435 truncated,
436 raw_length: Some(raw_length),
437 output_length: Some(output_length),
438 mask_reason: if should_mask {
439 Some("_audit_mask_fields".to_string())
440 } else {
441 None
442 },
443 truncate_reason: if truncated {
444 Some("_audit_value_max_len".to_string())
445 } else {
446 None
447 },
448 }
449 }
450 }
451}
452
453pub trait RawAuditEventSink: Send + Sync {
454 fn on_event(&self, ctx: &UserContext, event: &RawAuditEvent) -> Result<(), RuntimeError>;
455}
456
457#[derive(Default, Clone)]
458pub struct InMemoryRawAuditEventSink {
459 sinks: Vec<Arc<dyn RawAuditEventSink>>,
460}
461
462impl InMemoryRawAuditEventSink {
463 pub fn new() -> Self {
464 Self::default()
465 }
466
467 pub fn register(&mut self, sink: impl RawAuditEventSink + 'static) {
468 self.sinks.push(Arc::new(sink));
469 }
470
471 pub fn with_sink(mut self, sink: impl RawAuditEventSink + 'static) -> Self {
472 self.register(sink);
473 self
474 }
475}
476
477impl RawAuditEventSink for InMemoryRawAuditEventSink {
478 fn on_event(&self, ctx: &UserContext, event: &RawAuditEvent) -> Result<(), RuntimeError> {
479 for sink in &self.sinks {
480 sink.on_event(ctx, event)?;
481 }
482 Ok(())
483 }
484}
485
486#[derive(Debug, Clone, PartialEq)]
487pub struct SafeAuditField {
488 pub name: String,
489 pub value: Option<String>,
490 pub masked: bool,
491 pub truncated: bool,
492 pub raw_length: Option<usize>,
493 pub output_length: Option<usize>,
494 pub mask_reason: Option<String>,
495 pub truncate_reason: Option<String>,
496}
497
498#[derive(Debug, Clone, PartialEq)]
499pub struct SafeAuditEvent {
500 pub kind: RawAuditEventKind,
501 pub entity: String,
502 pub fields: Vec<SafeAuditField>,
503 pub trace_chain: Vec<teaql_core::TraceNode>,
504}
505
506pub trait SafeAuditEventSink: Send + Sync {
507 fn on_safe_event(
508 &self,
509 ctx: &crate::UserContext,
510 event: &SafeAuditEvent,
511 ) -> Result<(), crate::RuntimeError>;
512}