1
2
3pub mod flatten;
4pub mod intake;
5pub mod validate;
6
7use chrono::{DateTime, FixedOffset};
8use serde::{Deserialize, Serialize};
9use std::collections::{BTreeMap, BTreeSet};
10use std::fmt::Display;
11
12#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
13pub struct OCEL {
14 #[serde(rename = "eventTypes")]
15 pub event_types: Vec<OCELType>,
16 #[serde(rename = "objectTypes")]
17 pub object_types: Vec<OCELType>,
18 #[serde(default)]
19 pub events: Vec<OCELEvent>,
20 #[serde(default)]
21 pub objects: Vec<OCELObject>,
22}
23
24#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
25pub struct OCELType {
26 pub name: String,
27 #[serde(default)]
28 pub attributes: Vec<OCELTypeAttribute>,
29}
30
31#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
32pub struct OCELTypeAttribute {
33 pub name: String,
34 #[serde(rename = "type")]
35 pub value_type: String,
36}
37
38#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
39pub struct OCELEventAttribute {
40 pub name: String,
41 pub value: OCELAttributeValue,
42}
43
44#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
45pub struct OCELEvent {
46 pub id: String,
47 #[serde(rename = "type")]
48 pub event_type: String,
49 pub time: DateTime<FixedOffset>,
50 #[serde(default)]
51 pub attributes: Vec<OCELEventAttribute>,
52 #[serde(default)]
53 pub relationships: Vec<OCELRelationship>,
54}
55
56#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
57pub struct OCELRelationship {
58 #[serde(rename = "objectId")]
59 pub object_id: String,
60 pub qualifier: String,
61}
62
63#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
64pub struct OCELObject {
65 pub id: String,
66 #[serde(rename = "type")]
67 pub object_type: String,
68 #[serde(default)]
69 pub attributes: Vec<OCELObjectAttribute>,
70 #[serde(default)]
71 pub relationships: Vec<OCELRelationship>,
72}
73
74#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
75pub struct OCELObjectAttribute {
76 pub name: String,
77 pub value: OCELAttributeValue,
78 pub time: DateTime<FixedOffset>,
79}
80
81#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
82#[serde(untagged)]
83pub enum OCELAttributeValue {
84 Integer(i64),
85 Float(f64),
86 Boolean(bool),
87 Time(DateTime<FixedOffset>),
88 String(String),
89 #[default]
90 Null,
91}
92
93impl Display for OCELAttributeValue {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 let s = match self {
96 OCELAttributeValue::Time(dt) => dt.to_rfc3339(),
97 OCELAttributeValue::Integer(i) => i.to_string(),
98 OCELAttributeValue::Float(f) => f.to_string(),
99 OCELAttributeValue::Boolean(b) => b.to_string(),
100 OCELAttributeValue::String(s) => s.clone(),
101 OCELAttributeValue::Null => String::default(),
102 };
103 write!(f, "{s}")
104 }
105}
106
107#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
114pub struct ObjectTypeCardinality {
115 #[serde(default, skip_serializing_if = "Vec::is_empty")]
117 pub created_by: Vec<String>,
118 #[serde(default, skip_serializing_if = "Vec::is_empty")]
120 pub terminated_by: Vec<String>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub schema: Option<String>,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub min_count: Option<usize>,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub max_count: Option<usize>,
130}
131
132impl ObjectTypeCardinality {
133 #[must_use]
135 pub fn admits(&self, count: usize) -> bool {
136 let above_min = self.min_count.is_none_or(|m| count >= m);
137 let below_max = self.max_count.is_none_or(|m| count <= m);
138 above_min && below_max
139 }
140}
141
142impl OCEL {
143 #[must_use]
155 pub fn event_set(&self) -> &[OCELEvent] {
156 &self.events
157 }
158
159 #[must_use]
161 pub fn object_set(&self) -> &[OCELObject] {
162 &self.objects
163 }
164
165 #[must_use]
168 pub fn eval(&self, event_id: &str) -> Option<BTreeMap<&str, &OCELAttributeValue>> {
169 let e = self.events.iter().find(|e| e.id == event_id)?;
170 Some(
171 e.attributes
172 .iter()
173 .map(|a| (a.name.as_str(), &a.value))
174 .collect(),
175 )
176 }
177
178 #[must_use]
184 pub fn oaval(
185 &self,
186 object_id: &str,
187 at: DateTime<FixedOffset>,
188 ) -> Option<BTreeMap<&str, &OCELAttributeValue>> {
189 let o = self.objects.iter().find(|o| o.id == object_id)?;
190 let mut latest: BTreeMap<&str, (&DateTime<FixedOffset>, &OCELAttributeValue)> =
192 BTreeMap::new();
193 for a in &o.attributes {
194 if a.time <= at {
195 latest
196 .entry(a.name.as_str())
197 .and_modify(|cur| {
198 if a.time >= *cur.0 {
199 *cur = (&a.time, &a.value);
200 }
201 })
202 .or_insert((&a.time, &a.value));
203 }
204 }
205 Some(latest.into_iter().map(|(k, v)| (k, v.1)).collect())
206 }
207
208 #[must_use]
211 pub fn object_attr_timeline(&self, object_id: &str) -> Vec<DateTime<FixedOffset>> {
212 let mut stamps: BTreeSet<DateTime<FixedOffset>> = BTreeSet::new();
213 if let Some(o) = self.objects.iter().find(|o| o.id == object_id) {
214 for a in &o.attributes {
215 stamps.insert(a.time);
216 }
217 }
218 stamps.into_iter().collect()
219 }
220
221 #[must_use]
224 pub fn e2o(&self, event_id: &str) -> Vec<(&str, &str)> {
225 self.events
226 .iter()
227 .find(|e| e.id == event_id)
228 .map(|e| {
229 e.relationships
230 .iter()
231 .map(|r| (r.object_id.as_str(), r.qualifier.as_str()))
232 .collect()
233 })
234 .unwrap_or_default()
235 }
236
237 #[must_use]
240 pub fn o2o(&self, object_id: &str) -> Vec<(&str, &str)> {
241 self.objects
242 .iter()
243 .find(|o| o.id == object_id)
244 .map(|o| {
245 o.relationships
246 .iter()
247 .map(|r| (r.object_id.as_str(), r.qualifier.as_str()))
248 .collect()
249 })
250 .unwrap_or_default()
251 }
252
253 #[must_use]
255 pub fn count_objects_of_type(&self, object_type: &str) -> usize {
256 self.objects
257 .iter()
258 .filter(|o| o.object_type == object_type)
259 .count()
260 }
261}
262
263impl OCELEvent {
264 pub fn new(id: String, event_type: &str) -> Self {
265 Self {
266 id,
267 event_type: event_type.to_string(),
268 time: chrono::Utc::now().into(),
269 attributes: Vec::new(),
270 relationships: Vec::new(),
271 }
272 }
273 pub fn with_attribute(mut self, attr: OCELEventAttribute) -> Self {
274 self.attributes.push(attr);
275 self
276 }
277}
278
279impl OCELEventAttribute {
280 pub fn string(name: &str, val: String) -> Self {
281 Self {
282 name: name.to_string(),
283 value: OCELAttributeValue::String(val),
284 }
285 }
286 pub fn integer(name: &str, val: i64) -> Self {
287 Self {
288 name: name.to_string(),
289 value: OCELAttributeValue::Integer(val),
290 }
291 }
292}
293
294impl OCELObject {
295 pub fn new(id: String, object_type: &str) -> Self {
296 Self {
297 id,
298 object_type: object_type.to_string(),
299 attributes: Vec::new(),
300 relationships: Vec::new(),
301 }
302 }
303 pub fn with_attribute(mut self, attr: OCELEventAttribute) -> Self {
304 self.attributes.push(OCELObjectAttribute {
305 name: attr.name,
306 value: attr.value,
307 time: chrono::Utc::now().into(),
308 });
309 self
310 }
311}
312
313impl OCELRelationship {
314 pub fn new(event_id: String, object_id: String) -> Self {
315 Self {
316 object_id,
317 qualifier: "".to_string(),
318 }
319 }
320 pub fn qualified(mut self, qualifier: &str) -> Self {
321 self.qualifier = qualifier.to_string();
322 self
323 }
324}
325
326impl OCEL {
327 pub fn new(events: Vec<OCELEvent>, objects: Vec<OCELObject>) -> Self {
328 Self {
329 events,
330 objects,
331 event_types: Vec::new(),
332 object_types: Vec::new(),
333 }
334 }
335}
336
337pub type OcelObject = Object;
341
342#[derive(Debug, Clone)]
344pub struct Object {
345 id: String,
346 object_type: String,
347}
348
349impl Object {
350 pub fn new(id: &str, object_type: &str) -> Self {
351 Object { id: id.to_owned(), object_type: object_type.to_owned() }
352 }
353 pub fn id(&self) -> &str { &self.id }
354 pub fn object_type(&self) -> &str { &self.object_type }
355}
356
357#[derive(Debug, Clone)]
359pub struct OcelEvent {
360 id: String,
361 activity: String,
362 timestamp_ns: u64,
363}
364
365impl OcelEvent {
366 pub fn new(id: &str, activity: &str) -> Self {
367 OcelEvent { id: id.to_owned(), activity: activity.to_owned(), timestamp_ns: 0 }
368 }
369
370 #[must_use]
371 pub fn at_ns(mut self, ns: u64) -> Self { self.timestamp_ns = ns; self }
372
373 pub fn id(&self) -> &str { &self.id }
374 pub fn activity(&self) -> &str { &self.activity }
375}
376
377#[derive(Debug, Clone)]
379pub struct EventObjectLink {
380 event_id: String,
381 object_id: String,
382 qualifier: Option<String>,
383}
384
385impl EventObjectLink {
386 pub fn new(event_id: &str, object_id: &str) -> Self {
387 EventObjectLink { event_id: event_id.to_owned(), object_id: object_id.to_owned(), qualifier: None }
388 }
389
390 #[must_use]
391 pub fn qualified(mut self, q: &str) -> Self { self.qualifier = Some(q.to_owned()); self }
392
393 pub fn event_id(&self) -> &str { &self.event_id }
394 pub fn object_id(&self) -> &str { &self.object_id }
395 pub fn qualifier(&self) -> Option<&str> { self.qualifier.as_deref() }
396}
397
398#[derive(Debug, Clone)]
400pub struct ObjectObjectLink {
401 from_id: String,
402 to_id: String,
403 qualifier: Option<String>,
404}
405
406impl ObjectObjectLink {
407 pub fn new(from: &str, to: &str) -> Self {
408 ObjectObjectLink { from_id: from.to_owned(), to_id: to.to_owned(), qualifier: None }
409 }
410
411 #[must_use]
412 pub fn qualified(mut self, q: &str) -> Self { self.qualifier = Some(q.to_owned()); self }
413
414 pub fn from_id(&self) -> &str { &self.from_id }
415 pub fn to_id(&self) -> &str { &self.to_id }
416}
417
418#[derive(Debug, Clone)]
420pub struct ObjectChange {
421 object_id: String,
422 attribute: String,
423 value: String,
424}
425
426impl ObjectChange {
427 pub fn new(object_id: &str, attribute: &str, value: &str) -> Self {
428 ObjectChange {
429 object_id: object_id.to_owned(),
430 attribute: attribute.to_owned(),
431 value: value.to_owned(),
432 }
433 }
434}
435
436#[derive(Debug, Clone)]
438pub struct OcelLog {
439 objects: Vec<Object>,
440 events: Vec<OcelEvent>,
441 e2o_links: Vec<EventObjectLink>,
442 o2o_links: Vec<ObjectObjectLink>,
443 changes: Vec<ObjectChange>,
444}
445
446impl OcelLog {
447 pub fn new(
448 objects: impl IntoIterator<Item = Object>,
449 events: impl IntoIterator<Item = OcelEvent>,
450 e2o_links: impl IntoIterator<Item = EventObjectLink>,
451 o2o_links: impl IntoIterator<Item = ObjectObjectLink>,
452 changes: impl IntoIterator<Item = ObjectChange>,
453 ) -> Self {
454 OcelLog {
455 objects: objects.into_iter().collect(),
456 events: events.into_iter().collect(),
457 e2o_links: e2o_links.into_iter().collect(),
458 o2o_links: o2o_links.into_iter().collect(),
459 changes: changes.into_iter().collect(),
460 }
461 }
462
463 pub fn objects(&self) -> &[Object] { &self.objects }
464 pub fn events(&self) -> &[OcelEvent] { &self.events }
465 pub fn event_object_links(&self) -> &[EventObjectLink] { &self.e2o_links }
466 pub fn object_object_links(&self) -> &[ObjectObjectLink] { &self.o2o_links }
467 pub fn object_changes(&self) -> &[ObjectChange] { &self.changes }
468
469 #[must_use]
470 pub fn validate(&self) -> Result<(), OcelRefusal> {
471 if self.e2o_links.is_empty() {
472 return Err(OcelRefusal::EmptyEventObjectLinks);
473 }
474 let object_ids: std::collections::HashSet<&str> =
475 self.objects.iter().map(|o| o.id.as_str()).collect();
476 for link in &self.e2o_links {
477 if !object_ids.contains(link.object_id.as_str()) {
478 return Err(OcelRefusal::DanglingEventObjectLink);
479 }
480 }
481 Ok(())
482 }
483}
484
485#[derive(Debug, Clone, PartialEq, Eq)]
487pub enum OcelRefusal {
488 DanglingEventObjectLink,
490 EmptyEventObjectLinks,
492}
493
494impl std::fmt::Display for OcelRefusal {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 match self {
497 OcelRefusal::DanglingEventObjectLink => write!(f, "DanglingEventObjectLink"),
498 OcelRefusal::EmptyEventObjectLinks => write!(f, "EmptyEventObjectLinks"),
499 }
500 }
501}
502
503impl std::error::Error for OcelRefusal {}