#![cfg(feature = "bindings")]
use crate::core::{
event_data::{
case_centric::utils::activity_projection::EventLogActivityProjection,
object_centric::{
linked_ocel::{IndexLinkedOCEL, LinkedOCELAccess, SlimLinkedOCEL},
ocel_struct::OCEL,
},
},
io::ExtensionWithMime,
EventLog,
};
use macros_process_mining::register_binding;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{collections::HashMap, fmt::Display};
use std::{str::FromStr, sync::RwLock};
#[derive(Debug)]
#[allow(clippy::large_enum_variant, missing_docs)]
pub enum RegistryItem {
EventLogActivityProjection(EventLogActivityProjection),
IndexLinkedOCEL(IndexLinkedOCEL),
SlimLinkedOCEL(SlimLinkedOCEL),
EventLog(EventLog),
OCEL(OCEL),
}
impl From<EventLog> for RegistryItem {
fn from(value: EventLog) -> Self {
Self::EventLog(value)
}
}
impl From<EventLogActivityProjection> for RegistryItem {
fn from(value: EventLogActivityProjection) -> Self {
Self::EventLogActivityProjection(value)
}
}
impl From<IndexLinkedOCEL> for RegistryItem {
fn from(value: IndexLinkedOCEL) -> Self {
Self::IndexLinkedOCEL(value)
}
}
impl From<OCEL> for RegistryItem {
fn from(value: OCEL) -> Self {
Self::OCEL(value)
}
}
impl From<SlimLinkedOCEL> for RegistryItem {
fn from(value: SlimLinkedOCEL) -> Self {
Self::SlimLinkedOCEL(value)
}
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum RegistryItemKind {
EventLogActivityProjection,
IndexLinkedOCEL,
SlimLinkedOCEL,
EventLog,
OCEL,
}
impl Display for RegistryItemKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
RegistryItemKind::EventLogActivityProjection => "EventLogActivityProjection",
RegistryItemKind::IndexLinkedOCEL => "IndexLinkedOCEL",
RegistryItemKind::SlimLinkedOCEL => "SlimLinkedOCEL",
RegistryItemKind::EventLog => "EventLog",
RegistryItemKind::OCEL => "OCEL",
};
write!(f, "{}", s)
}
}
impl RegistryItemKind {
pub fn all_kinds() -> &'static [Self] {
&[
RegistryItemKind::OCEL,
RegistryItemKind::EventLog,
RegistryItemKind::EventLogActivityProjection,
RegistryItemKind::SlimLinkedOCEL,
RegistryItemKind::IndexLinkedOCEL,
]
}
pub fn known_import_formats(&self) -> Vec<ExtensionWithMime> {
match self {
RegistryItemKind::EventLogActivityProjection => {
EventLogActivityProjection::known_import_formats()
}
RegistryItemKind::IndexLinkedOCEL => IndexLinkedOCEL::known_import_formats(),
RegistryItemKind::EventLog => EventLog::known_import_formats(),
RegistryItemKind::OCEL => OCEL::known_import_formats(),
RegistryItemKind::SlimLinkedOCEL => OCEL::known_import_formats(),
}
}
pub fn known_export_formats(&self) -> Vec<ExtensionWithMime> {
match self {
RegistryItemKind::EventLogActivityProjection => {
EventLogActivityProjection::known_export_formats()
}
RegistryItemKind::IndexLinkedOCEL => IndexLinkedOCEL::known_export_formats(),
RegistryItemKind::EventLog => EventLog::known_export_formats(),
RegistryItemKind::OCEL => OCEL::known_export_formats(),
RegistryItemKind::SlimLinkedOCEL => OCEL::known_export_formats(),
}
}
}
impl std::str::FromStr for RegistryItemKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"EventLogActivityProjection" => Ok(RegistryItemKind::EventLogActivityProjection),
"IndexLinkedOCEL" => Ok(RegistryItemKind::IndexLinkedOCEL),
"EventLog" => Ok(RegistryItemKind::EventLog),
"OCEL" => Ok(RegistryItemKind::OCEL),
"SlimLinkedOCEL" => Ok(RegistryItemKind::SlimLinkedOCEL),
_ => Err(format!("Unknown RegistryItemKind: {}", s)),
}
}
}
use crate::core::io::{Exportable, Importable};
impl RegistryItem {
pub fn to_value(&self) -> Result<Value, String> {
match self {
RegistryItem::EventLog(log) => serde_json::to_value(log).map_err(|e| e.to_string()),
RegistryItem::OCEL(ocel) => serde_json::to_value(ocel).map_err(|e| e.to_string()),
RegistryItem::IndexLinkedOCEL(locel) => {
serde_json::to_value(locel).map_err(|e| e.to_string())
}
RegistryItem::SlimLinkedOCEL(locel) => {
serde_json::to_value(locel).map_err(|e| e.to_string())
}
RegistryItem::EventLogActivityProjection(proj) => {
serde_json::to_value(proj).map_err(|e| e.to_string())
}
}
}
pub fn load_from_path(item_kind: &RegistryItemKind, path: &str) -> Result<Self, String> {
let path = std::path::Path::new(path);
match item_kind {
RegistryItemKind::EventLog => Ok(RegistryItem::EventLog(
EventLog::import_from_path(path).map_err(|e| e.to_string())?,
)),
RegistryItemKind::OCEL => Ok(RegistryItem::OCEL(
OCEL::import_from_path(path).map_err(|e| e.to_string())?,
)),
RegistryItemKind::SlimLinkedOCEL => Ok(RegistryItem::SlimLinkedOCEL({
OCEL::import_from_path(path)
.map(SlimLinkedOCEL::from_ocel)
.map_err(|e| e.to_string())?
})),
RegistryItemKind::IndexLinkedOCEL => Ok(RegistryItem::IndexLinkedOCEL(
IndexLinkedOCEL::import_from_path(path).map_err(|e| e.to_string())?,
)),
RegistryItemKind::EventLogActivityProjection => {
Ok(RegistryItem::EventLogActivityProjection(
EventLogActivityProjection::import_from_path(path)
.map_err(|e| e.to_string())?,
))
}
}
}
pub fn load_from_bytes(
item_kind: &RegistryItemKind,
data: &[u8],
format: &str,
) -> Result<Self, String> {
match item_kind {
RegistryItemKind::EventLog => Ok(RegistryItem::EventLog(
EventLog::import_from_bytes(data, format).map_err(|e| e.to_string())?,
)),
RegistryItemKind::OCEL => Ok(RegistryItem::OCEL(
OCEL::import_from_bytes(data, format).map_err(|e| e.to_string())?,
)),
RegistryItemKind::IndexLinkedOCEL => Ok(RegistryItem::IndexLinkedOCEL(
IndexLinkedOCEL::import_from_bytes(data, format).map_err(|e| e.to_string())?,
)),
RegistryItemKind::SlimLinkedOCEL => Ok(RegistryItem::SlimLinkedOCEL({
OCEL::import_from_bytes(data, format)
.map(SlimLinkedOCEL::from_ocel)
.map_err(|e| e.to_string())?
})),
RegistryItemKind::EventLogActivityProjection => {
Ok(RegistryItem::EventLogActivityProjection(
EventLogActivityProjection::import_from_bytes(data, format)
.map_err(|e| e.to_string())?,
))
}
}
}
pub fn kind(&self) -> RegistryItemKind {
match self {
RegistryItem::EventLogActivityProjection(_) => {
RegistryItemKind::EventLogActivityProjection
}
RegistryItem::IndexLinkedOCEL(_) => RegistryItemKind::IndexLinkedOCEL,
RegistryItem::EventLog(_) => RegistryItemKind::EventLog,
RegistryItem::OCEL(_) => RegistryItemKind::OCEL,
RegistryItem::SlimLinkedOCEL(_) => RegistryItemKind::SlimLinkedOCEL,
}
}
pub fn export_to_path(&self, path: impl AsRef<std::path::Path>) -> Result<(), String> {
let path = path.as_ref();
match self {
RegistryItem::EventLog(x) => x.export_to_path(path).map_err(|e| e.to_string()),
RegistryItem::OCEL(x) => x.export_to_path(path).map_err(|e| e.to_string()),
RegistryItem::IndexLinkedOCEL(x) => x.export_to_path(path).map_err(|e| e.to_string()),
RegistryItem::SlimLinkedOCEL(x) => x
.construct_ocel()
.export_to_path(path)
.map_err(|e| e.to_string()),
RegistryItem::EventLogActivityProjection(x) => {
x.export_to_path(path).map_err(|e| e.to_string())
}
}
}
pub fn export_to_bytes(&self, format: &str) -> Result<Vec<u8>, String> {
let mut bytes = Vec::new();
match self {
RegistryItem::EventLog(x) => x
.export_to_writer(&mut bytes, format)
.map_err(|e| e.to_string())?,
RegistryItem::OCEL(x) => x
.export_to_writer(&mut bytes, format)
.map_err(|e| e.to_string())?,
RegistryItem::SlimLinkedOCEL(x) => x
.construct_ocel()
.export_to_writer(&mut bytes, format)
.map_err(|e| e.to_string())?,
RegistryItem::IndexLinkedOCEL(x) => x
.export_to_writer(&mut bytes, format)
.map_err(|e| e.to_string())?,
RegistryItem::EventLogActivityProjection(x) => x
.export_to_writer(&mut bytes, format)
.map_err(|e| e.to_string())?,
};
Ok(bytes)
}
pub fn convert(&self, target_kind: RegistryItemKind) -> Result<Self, String> {
match (self, target_kind) {
(RegistryItem::EventLog(log), RegistryItemKind::EventLogActivityProjection) => {
Ok(RegistryItem::EventLogActivityProjection(log.into()))
}
(RegistryItem::OCEL(ocel), RegistryItemKind::IndexLinkedOCEL) => Ok(
RegistryItem::IndexLinkedOCEL(IndexLinkedOCEL::from_ocel(ocel.clone())),
),
(RegistryItem::IndexLinkedOCEL(locel), RegistryItemKind::OCEL) => {
Ok(RegistryItem::OCEL(locel.get_ocel_ref().clone()))
}
(RegistryItem::SlimLinkedOCEL(locel), RegistryItemKind::OCEL) => {
Ok(RegistryItem::OCEL(locel.construct_ocel()))
}
(RegistryItem::OCEL(ocel), RegistryItemKind::SlimLinkedOCEL) => Ok(
RegistryItem::SlimLinkedOCEL(SlimLinkedOCEL::from_ocel(ocel.clone())),
),
_ => Err(format!("Cannot convert {} to {}", self.kind(), target_kind)),
}
}
}
pub type InnerAppState = HashMap<String, RegistryItem>;
#[derive(Debug, Default)]
pub struct AppState {
pub items: RwLock<InnerAppState>,
}
impl AppState {
pub fn add(&self, id: impl Into<String>, item: impl Into<RegistryItem>) {
self.items.write().unwrap().insert(id.into(), item.into());
}
pub fn contains_key(&self, id: &str) -> bool {
self.items.read().unwrap().contains_key(id)
}
}
#[derive(Debug)]
pub struct Binding {
pub id: &'static str,
pub name: &'static str,
pub handler: fn(&Value, &AppState) -> Result<Value, String>,
pub docs: fn() -> Vec<String>,
pub module: &'static str,
pub source_path: &'static str,
pub source_line: u32,
pub args: fn() -> Vec<(String, Value)>,
pub required_args: fn() -> Vec<String>,
pub return_type: fn() -> Value,
}
inventory::collect!(Binding);
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct BindingMeta {
pub id: String,
pub name: String,
pub docs: Vec<String>,
pub module: String,
pub source_path: String,
pub source_line: u32,
pub args: Vec<(String, Value)>,
pub required_args: Vec<String>,
pub return_type: Value,
}
impl From<&Binding> for BindingMeta {
fn from(value: &Binding) -> Self {
Self {
id: value.id.to_string(),
name: value.name.to_string(),
docs: (value.docs)(),
module: value.module.to_string(),
source_path: value.source_path.to_string(),
source_line: value.source_line,
args: (value.args)(),
required_args: (value.required_args)(),
return_type: (value.return_type)(),
}
}
}
pub trait FromContext<'a>: Sized {
fn from_context(v: &Value, s: &'a InnerAppState) -> Result<Self, String>;
}
pub fn extract_param<'a, T: FromContext<'a>>(
m: &serde_json::Map<String, Value>,
k: &str,
s: &'a InnerAppState,
default: impl FnOnce() -> Option<T>,
) -> Result<T, String> {
if let Some(x) = m.get(k) {
if x.is_null() {
let d = default();
if let Some(d) = d {
return Ok(d);
}
}
T::from_context(x, s).map_err(|e| format!("Invalid Argument: {k}\n{e}"))
} else {
let r = default();
r.ok_or_else(|| format!("Missing required argument {k}"))
}
}
pub fn extract_param_json<T: serde::de::DeserializeOwned>(
m: &serde_json::Map<String, Value>,
k: &str,
default: impl FnOnce() -> Option<T>,
) -> Result<T, String> {
if let Some(x) = m.get(k) {
if x.is_null() {
if let Some(d) = default() {
return Ok(d);
}
}
serde_json::from_value(x.clone()).map_err(|e| format!("Invalid Argument: {k}\n{e}"))
} else {
default().ok_or_else(|| format!("Missing required argument {k}"))
}
}
impl<'a, T> FromContext<'a> for T
where
T: serde::de::DeserializeOwned,
{
fn from_context(v: &Value, _: &'a InnerAppState) -> Result<Self, String> {
serde_json::from_value(v.clone()).map_err(|e| e.to_string())
}
}
pub fn resolve_argument(
arg_name: &str,
value: Value,
schema: &Value,
state: &AppState,
) -> Result<Value, String> {
let schema_obj = schema.as_object().ok_or("Invalid schema")?;
if let Some(arg_ref) = schema_obj.get("x-registry-ref").and_then(|r| r.as_str()) {
if let Some(id) = value.as_str() {
let mut items = state.items.write().map_err(|e| e.to_string())?;
if let Some(item) = items.get(id) {
if item.kind().to_string() == arg_ref {
return Ok(value);
}
use std::str::FromStr;
let target_kind = RegistryItemKind::from_str(arg_ref)?;
match item.convert(target_kind) {
Ok(converted) => {
let new_id = format!("{}_as_{}", id, arg_ref);
items.insert(new_id.clone(), converted);
return Ok(serde_json::Value::String(new_id));
}
Err(e) => {
return Err(format!(
"Type mismatch for ID '{}': expected {}, found {}. Conversion failed: {}",
id,
arg_ref,
item.kind(),
e
))
}
}
}
drop(items);
let item = RegistryItem::load_from_path(&RegistryItemKind::from_str(arg_ref)?, id)?;
let stored_name = format!("A{}_{}", arg_name, uuid::Uuid::new_v4());
state.add(&stored_name, item);
return Ok(serde_json::Value::String(stored_name));
}
}
if let Some(val_str) = value.as_str() {
if schema_obj.get("type") == Some(&serde_json::json!("object"))
&& val_str.ends_with(".json")
{
let file = std::fs::File::open(val_str)
.map_err(|e| format!("Failed to open JSON file: {}", e))?;
let reader = std::io::BufReader::new(file);
let loaded_val: Value = serde_json::from_reader(reader)
.map_err(|e| format!("Failed to parse JSON file: {}", e))?;
return Ok(loaded_val);
}
}
if let Some(val_str) = value.as_str() {
let type_field = schema_obj.get("type").and_then(|t| t.as_str());
if matches!(type_field, Some("object") | Some("array")) {
if let Ok(parsed) = serde_json::from_str::<Value>(val_str) {
return Ok(parsed);
}
}
}
Ok(value)
}
pub fn call(binding: &Binding, args: &Value, state: &AppState) -> Result<Value, String> {
(binding.handler)(args, state)
}
pub fn list_functions() -> Vec<&'static Binding> {
inventory::iter::<Binding>.into_iter().collect()
}
pub fn list_functions_meta() -> Vec<BindingMeta> {
inventory::iter::<Binding>
.into_iter()
.map(BindingMeta::from)
.collect()
}
pub fn get_fn_binding(id: &str) -> Option<&'static Binding> {
inventory::iter::<Binding>.into_iter().find(|b| b.id == id)
}
mod slim_ocel_bindings;
#[register_binding]
pub fn num_objects<'a>(ocel: &'a impl LinkedOCELAccess<'a>) -> usize {
ocel.get_num_obs()
}
#[register_binding]
pub fn num_events<'a>(ocel: &'a impl LinkedOCELAccess<'a>) -> usize {
ocel.get_num_evs()
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct OCELTypeStats {
pub event_type_counts: HashMap<String, usize>,
pub object_type_counts: HashMap<String, usize>,
}
#[register_binding]
pub fn ocel_type_stats<'a>(ocel: &'a impl LinkedOCELAccess<'a>) -> OCELTypeStats {
OCELTypeStats {
event_type_counts: ocel
.get_ev_types()
.map(|et| (et.to_string(), ocel.get_evs_of_type(et).count()))
.collect(),
object_type_counts: ocel
.get_ob_types()
.map(|ot| (ot.to_string(), ocel.get_obs_of_type(ot).count()))
.collect(),
}
}
#[register_binding]
pub fn index_link_ocel(ocel: &OCEL) -> IndexLinkedOCEL {
IndexLinkedOCEL::from_ocel(ocel.clone())
}
#[register_binding]
pub fn slim_link_ocel(ocel: &OCEL) -> SlimLinkedOCEL {
SlimLinkedOCEL::from_ocel(ocel.clone())
}
#[register_binding]
pub fn test_some_inputs(s: String, n: usize, i: i32, f: f64, b: bool) -> String {
format!("s={},n={},i={},f={},b={}", s, n, i, f, b)
}
#[cfg(test)]
mod tests {
use crate::test_utils::get_test_data_path;
use super::*;
use std::collections::HashSet;
#[test]
fn export_bindings() {
let bindings = list_functions_meta();
let file = std::fs::File::create(
get_test_data_path()
.join("export")
.join(format!("bindings-v{}.json", env!("CARGO_PKG_VERSION"))),
)
.unwrap();
serde_json::to_writer_pretty(&file, &bindings).unwrap();
}
#[test]
fn test_consistent_registry_item_variants() {
let variants = RegistryItemKind::all_kinds();
let variant_names: HashSet<String> = variants.iter().map(|v| v.to_string()).collect();
let macro_types: &[&str] = macros_process_mining::big_types_list!();
let macro_type_names: HashSet<String> = macro_types.iter().map(|s| s.to_string()).collect();
for macro_type in ¯o_type_names {
assert!(
variant_names.contains(macro_type),
"Macro expects type '{}' which is missing in RegistryItem enum",
macro_type
);
}
for variant in &variant_names {
assert!(
macro_type_names.contains(variant),
"RegistryItem has variant '{}' which is missing in macros_process_mining::BIG_TYPES_NAMES",
variant
);
}
assert_eq!(
variant_names.len(),
macro_type_names.len(),
"Mismatch in number of types between RegistryItem and macros_process_mining"
);
}
}