edifact_mapper/
transaction_view.rs1use std::collections::BTreeSet;
30
31use mig_bo4e::model::{Nachricht, Nachrichtendaten, MSG_METADATA_ENTITY};
32use serde_json::{Map, Value};
33
34use crate::{Mapper, MapperError};
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct TransactionView {
39 pub value: Value,
41 pub synthetic: bool,
44}
45
46fn view_error(message: String) -> MapperError {
47 MapperError::TransactionView(message)
48}
49
50fn object(value: &Value) -> Map<String, Value> {
51 value.as_object().cloned().unwrap_or_default()
52}
53
54impl Mapper {
55 pub fn transaction_views(
62 &self,
63 nachricht: &Nachricht<Value, Value>,
64 ) -> Result<Vec<TransactionView>, MapperError> {
65 let message = object(&nachricht.stammdaten);
66 let own = Value::Object(nachricht.nachrichtendaten.nachricht.clone());
67 let view = |transaktionsdaten: Value, stammdaten: Map<String, Value>| {
68 serde_json::json!({
69 "transaktionsdaten": transaktionsdaten,
70 "stammdaten": Value::Object(stammdaten),
71 "nachricht": own.clone(),
72 })
73 };
74 if nachricht.transaktionen.is_empty() {
75 return Ok(vec![TransactionView {
76 value: view(Value::Null, message),
77 synthetic: true,
78 }]);
79 }
80 nachricht
81 .transaktionen
82 .iter()
83 .map(|tx| {
84 let mut stammdaten = message.clone();
85 for (key, entity) in object(&tx["stammdaten"]) {
86 if stammdaten.contains_key(&key) {
87 return Err(view_error(format!(
88 "entity `{key}` is both message-level and in a transaction"
89 )));
90 }
91 stammdaten.insert(key, entity);
92 }
93 let transaktionsdaten = tx.get("transaktionsdaten").cloned().unwrap_or(Value::Null);
94 Ok(TransactionView {
95 value: view(transaktionsdaten, stammdaten),
96 synthetic: false,
97 })
98 })
99 .collect()
100 }
101
102 pub fn from_transaction_views(
111 &self,
112 views: &[TransactionView],
113 kopf: &Nachrichtendaten,
114 fv: &str,
115 variant: &str,
116 pid: &str,
117 ) -> Result<Nachricht<Value, Value>, MapperError> {
118 let message_keys = self.message_entity_keys(fv, variant, pid)?;
119 let Some(first) = views.first() else {
120 return Err(view_error("no view".to_string()));
121 };
122
123 let own = object(&first.value["nachricht"]);
124 let mut message: Option<Map<String, Value>> = None;
125 let mut transaktionen = Vec::new();
126 for view in views {
127 if object(&view.value["nachricht"]) != own {
128 return Err(view_error("the views disagree on `nachricht`".to_string()));
129 }
130 let (here, transaction): (Map<String, Value>, Map<String, Value>) =
131 object(&view.value["stammdaten"])
132 .into_iter()
133 .partition(|(key, _)| message_keys.contains(key));
134 match &message {
135 None => message = Some(here),
136 Some(seen) => {
137 let keys: BTreeSet<&String> = seen.keys().chain(here.keys()).collect();
138 if let Some(key) = keys.into_iter().find(|k| seen.get(*k) != here.get(*k)) {
139 return Err(view_error(format!(
140 "the views disagree on message-level entity `{key}`"
141 )));
142 }
143 }
144 }
145 let transaktionsdaten = view.value["transaktionsdaten"].clone();
146 if view.synthetic {
147 if let Some(key) = transaction.keys().next() {
148 return Err(view_error(format!(
149 "a view standing for no transaction holds transaction-level entity `{key}`"
150 )));
151 }
152 if !transaktionsdaten.is_null() {
153 return Err(view_error(
154 "a view standing for no transaction holds `transaktionsdaten`".to_string(),
155 ));
156 }
157 continue;
158 }
159 let mut tx = Map::new();
160 tx.insert("transaktionsdaten".to_string(), transaktionsdaten);
161 tx.insert("stammdaten".to_string(), Value::Object(transaction));
162 transaktionen.push(Value::Object(tx));
163 }
164
165 let mut nachrichtendaten = kopf.clone();
166 nachrichtendaten.nachricht = own;
167 Ok(Nachricht {
168 nachrichtendaten,
169 stammdaten: Value::Object(message.unwrap_or_default()),
170 transaktionen,
171 })
172 }
173
174 fn message_entity_keys(
176 &self,
177 fv: &str,
178 variant: &str,
179 pid: &str,
180 ) -> Result<BTreeSet<String>, MapperError> {
181 let definitions = self.message_definitions(fv, variant, pid)?;
182 Ok(definitions
183 .iter()
184 .map(|d| {
185 let mut chars = d.meta.entity.chars();
186 match chars.next() {
187 Some(c) => c.to_lowercase().chain(chars).collect(),
188 None => String::new(),
189 }
190 })
191 .filter(|key| key != MSG_METADATA_ENTITY)
192 .collect())
193 }
194}