use serde_json::{Map, Value};
use crate::catalog::{TriggerEffect, TriggerEntry, TriggerEvent, TriggerValueSource};
use crate::schema::HaysonRecord;
pub struct TriggerFiring<'a> {
#[allow(dead_code)]
pub event: TriggerEvent,
pub old: Option<&'a HaysonRecord>,
pub new: Option<&'a HaysonRecord>,
}
pub fn matching<'a>(triggers: &'a [TriggerEntry], table: &str, event: TriggerEvent) -> Vec<&'a TriggerEntry> {
triggers
.iter()
.filter(|t| t.table == table && t.event == event)
.collect()
}
pub fn effect_document(effect: &TriggerEffect, firing: &TriggerFiring) -> Value {
let mut document = Map::new();
for mapping in &effect.fields {
let value = match &mapping.source {
TriggerValueSource::NewField(field) => firing.new.and_then(|record| record.get(field).cloned()),
TriggerValueSource::OldField(field) => firing.old.and_then(|record| record.get(field).cloned()),
TriggerValueSource::Constant(value) => Some(value.clone()),
TriggerValueSource::Now => Some(Value::from(chrono::Utc::now().timestamp_millis())),
};
if let Some(value) = value {
document.insert(mapping.target.clone(), value);
}
}
Value::Object(document)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::catalog::TriggerFieldMapping;
fn record(pairs: &[(&str, Value)]) -> HaysonRecord {
pairs.iter().map(|(k, v)| (k.to_string(), v.clone())).collect()
}
#[test]
fn copies_new_and_old_fields_and_fills_constants() {
let effect = TriggerEffect {
table: "audit".into(),
fields: vec![
TriggerFieldMapping {
target: "afterName".into(),
source: TriggerValueSource::NewField("name".into()),
},
TriggerFieldMapping {
target: "beforeName".into(),
source: TriggerValueSource::OldField("name".into()),
},
TriggerFieldMapping {
target: "kind".into(),
source: TriggerValueSource::Constant(Value::from("rename")),
},
],
};
let old = record(&[("name", Value::from("Ania"))]);
let new = record(&[("name", Value::from("Anna"))]);
let firing = TriggerFiring {
event: TriggerEvent::Update,
old: Some(&old),
new: Some(&new),
};
let document = effect_document(&effect, &firing);
assert_eq!(document.get("afterName"), Some(&Value::from("Anna")));
assert_eq!(document.get("beforeName"), Some(&Value::from("Ania")));
assert_eq!(document.get("kind"), Some(&Value::from("rename")));
}
#[test]
fn a_field_missing_on_the_named_side_is_skipped() {
let effect = TriggerEffect {
table: "audit".into(),
fields: vec![TriggerFieldMapping {
target: "afterName".into(),
source: TriggerValueSource::NewField("name".into()),
}],
};
let firing = TriggerFiring {
event: TriggerEvent::Delete,
old: Some(&record(&[("name", Value::from("Ania"))])),
new: None,
};
let document = effect_document(&effect, &firing);
assert!(document.get("afterName").is_none());
}
}