use std::collections::BTreeSet;
use mig_bo4e::model::{Nachricht, Nachrichtendaten, MSG_METADATA_ENTITY};
use serde_json::{Map, Value};
use crate::{Mapper, MapperError};
#[derive(Debug, Clone, PartialEq)]
pub struct TransactionView {
pub value: Value,
pub synthetic: bool,
}
fn view_error(message: String) -> MapperError {
MapperError::TransactionView(message)
}
fn object(value: &Value) -> Map<String, Value> {
value.as_object().cloned().unwrap_or_default()
}
impl Mapper {
pub fn transaction_views(
&self,
nachricht: &Nachricht<Value, Value>,
) -> Result<Vec<TransactionView>, MapperError> {
let message = object(&nachricht.stammdaten);
let own = Value::Object(nachricht.nachrichtendaten.nachricht.clone());
let view = |transaktionsdaten: Value, stammdaten: Map<String, Value>| {
serde_json::json!({
"transaktionsdaten": transaktionsdaten,
"stammdaten": Value::Object(stammdaten),
"nachricht": own.clone(),
})
};
if nachricht.transaktionen.is_empty() {
return Ok(vec![TransactionView {
value: view(Value::Null, message),
synthetic: true,
}]);
}
nachricht
.transaktionen
.iter()
.map(|tx| {
let mut stammdaten = message.clone();
for (key, entity) in object(&tx["stammdaten"]) {
if stammdaten.contains_key(&key) {
return Err(view_error(format!(
"entity `{key}` is both message-level and in a transaction"
)));
}
stammdaten.insert(key, entity);
}
let transaktionsdaten = tx.get("transaktionsdaten").cloned().unwrap_or(Value::Null);
Ok(TransactionView {
value: view(transaktionsdaten, stammdaten),
synthetic: false,
})
})
.collect()
}
pub fn from_transaction_views(
&self,
views: &[TransactionView],
kopf: &Nachrichtendaten,
fv: &str,
variant: &str,
pid: &str,
) -> Result<Nachricht<Value, Value>, MapperError> {
let message_keys = self.message_entity_keys(fv, variant, pid)?;
let Some(first) = views.first() else {
return Err(view_error("no view".to_string()));
};
let own = object(&first.value["nachricht"]);
let mut message: Option<Map<String, Value>> = None;
let mut transaktionen = Vec::new();
for view in views {
if object(&view.value["nachricht"]) != own {
return Err(view_error("the views disagree on `nachricht`".to_string()));
}
let (here, transaction): (Map<String, Value>, Map<String, Value>) =
object(&view.value["stammdaten"])
.into_iter()
.partition(|(key, _)| message_keys.contains(key));
match &message {
None => message = Some(here),
Some(seen) => {
let keys: BTreeSet<&String> = seen.keys().chain(here.keys()).collect();
if let Some(key) = keys.into_iter().find(|k| seen.get(*k) != here.get(*k)) {
return Err(view_error(format!(
"the views disagree on message-level entity `{key}`"
)));
}
}
}
let transaktionsdaten = view.value["transaktionsdaten"].clone();
if view.synthetic {
if let Some(key) = transaction.keys().next() {
return Err(view_error(format!(
"a view standing for no transaction holds transaction-level entity `{key}`"
)));
}
if !transaktionsdaten.is_null() {
return Err(view_error(
"a view standing for no transaction holds `transaktionsdaten`".to_string(),
));
}
continue;
}
let mut tx = Map::new();
tx.insert("transaktionsdaten".to_string(), transaktionsdaten);
tx.insert("stammdaten".to_string(), Value::Object(transaction));
transaktionen.push(Value::Object(tx));
}
let mut nachrichtendaten = kopf.clone();
nachrichtendaten.nachricht = own;
Ok(Nachricht {
nachrichtendaten,
stammdaten: Value::Object(message.unwrap_or_default()),
transaktionen,
})
}
fn message_entity_keys(
&self,
fv: &str,
variant: &str,
pid: &str,
) -> Result<BTreeSet<String>, MapperError> {
let definitions = self.message_definitions(fv, variant, pid)?;
Ok(definitions
.iter()
.map(|d| {
let mut chars = d.meta.entity.chars();
match chars.next() {
Some(c) => c.to_lowercase().chain(chars).collect(),
None => String::new(),
}
})
.filter(|key| key != MSG_METADATA_ENTITY)
.collect())
}
}