use super::core::{BoundaryCrossing, GgenMembrane, InterchangeablePart};
use crate::utils::error::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum OcelValue {
String(String),
Number(f64),
Boolean(bool),
StringArray(Vec<String>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OcelObject {
#[serde(rename = "ocel:type")]
pub object_type: String,
#[serde(rename = "ocel:ovmap")]
pub attributes: HashMap<String, OcelValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OcelEvent {
#[serde(rename = "ocel:activity")]
pub activity: String,
#[serde(rename = "ocel:timestamp")]
pub timestamp: String,
#[serde(rename = "ocel:omap")]
pub objects: Vec<String>,
#[serde(rename = "ocel:vmap")]
pub attributes: HashMap<String, OcelValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OcelLog {
#[serde(rename = "ocel:objects")]
pub objects: HashMap<String, OcelObject>,
#[serde(rename = "ocel:events")]
pub events: HashMap<String, OcelEvent>,
}
impl OcelLog {
pub fn from_membrane(membrane: &GgenMembrane) -> Self {
let mut objects = HashMap::new();
let mut events = HashMap::new();
for (part_id, part) in &membrane.core.parts {
let mut attrs = HashMap::new();
attrs.insert(
"version".to_string(),
OcelValue::String(part.version.clone()),
);
attrs.insert(
"part_type".to_string(),
OcelValue::String(part.part_type.clone()),
);
attrs.insert(
"payload_hash".to_string(),
OcelValue::String(part.payload_hash.clone()),
);
attrs.insert(
"payload_size".to_string(),
OcelValue::Number(part.payload_size as f64),
);
attrs.insert(
"interfaces".to_string(),
OcelValue::StringArray(part.interfaces.clone()),
);
objects.insert(
part_id.clone(),
OcelObject {
object_type: "InterchangeablePart".to_string(),
attributes: attrs,
},
);
}
for (outer_port, inner_interface) in &membrane.adapters {
let mut attrs = HashMap::new();
attrs.insert(
"inner_interface".to_string(),
OcelValue::String(inner_interface.clone()),
);
objects.insert(
format!("adapter:{}", outer_port),
OcelObject {
object_type: "AdapterBinding".to_string(),
attributes: attrs,
},
);
}
for crossing in &membrane.event_log {
let mut attrs = HashMap::new();
attrs.insert(
"crossing_type".to_string(),
OcelValue::String(crossing.crossing_type.clone()),
);
attrs.insert(
"interface_fn".to_string(),
OcelValue::String(crossing.interface_fn.clone()),
);
attrs.insert(
"input_hash".to_string(),
OcelValue::String(crossing.input_hash.clone()),
);
if let Some(ref oh) = crossing.output_hash {
attrs.insert("output_hash".to_string(), OcelValue::String(oh.clone()));
}
attrs.insert(
"status_code".to_string(),
OcelValue::Number(crossing.status_code as f64),
);
attrs.insert(
"duration_us".to_string(),
OcelValue::Number(crossing.duration_us as f64),
);
let mut related_objects = vec![crossing.callee_id.clone()];
for (outer_port, inner_interface) in &membrane.adapters {
if inner_interface.contains(&crossing.interface_fn) {
related_objects.push(format!("adapter:{}", outer_port));
}
}
events.insert(
crossing.id.clone(),
OcelEvent {
activity: format!("BoundaryCrossing:{}", crossing.interface_fn),
timestamp: crossing.timestamp.to_rfc3339(),
objects: related_objects,
attributes: attrs,
},
);
}
Self { objects, events }
}
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(self)?)
}
}