Skip to main content

wasm4pm_cli/
format.rs

1//! Event-log format detection shared across CLI commands.
2//!
3//! The Rust CLI's mining/audit/autoprocess paths operate on flattened XES event
4//! logs. OCEL 2.0 logs must be flattened first, so commands detect them up front
5//! and bail with an actionable message. Keeping that detection in one place avoids
6//! the per-command drift that previously let `audit` and `autoprocess` disagree on
7//! what counts as OCEL.
8
9use std::path::Path;
10
11/// Returns `true` if `path` appears to be an OCEL (object-centric) event log.
12///
13/// Detection is layered, cheapest signal first:
14/// 1. An OCEL extension (`.ocel`, `.jsonocel`).
15/// 2. A `.json` path whose name hints at OCEL (`*.ocel.json`, `vision_trace*`).
16/// 3. A `.json` file whose contents carry the OCEL `"ocel:"` marker.
17///
18/// The content sniff (step 3) only runs for ambiguous `.json` files and treats an
19/// unreadable file as "not OCEL", deferring the real I/O error to the caller's load
20/// step where it can be reported with proper context.
21pub fn is_ocel_log(path: &Path) -> bool {
22    let ext = path
23        .extension()
24        .and_then(|e| e.to_str())
25        .unwrap_or_default()
26        .to_lowercase();
27
28    match ext.as_str() {
29        "ocel" | "jsonocel" => true,
30        "json" => {
31            let name = path.to_string_lossy();
32            name.contains(".ocel")
33                || name.contains("vision_trace")
34                || std::fs::read_to_string(path)
35                    .unwrap_or_default()
36                    .contains("ocel:")
37        }
38        _ => false,
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use std::path::Path;
46
47    #[test]
48    fn detects_ocel_by_extension() {
49        assert!(is_ocel_log(Path::new("log.ocel")));
50        assert!(is_ocel_log(Path::new("log.jsonocel")));
51    }
52
53    #[test]
54    fn detects_ocel_by_json_name_hint() {
55        assert!(is_ocel_log(Path::new("orders.ocel.json")));
56        assert!(is_ocel_log(Path::new("vision_trace_42.json")));
57    }
58
59    #[test]
60    fn plain_xes_and_unrelated_json_are_not_ocel() {
61        assert!(!is_ocel_log(Path::new("log.xes")));
62        assert!(!is_ocel_log(Path::new("missing.json")));
63        assert!(!is_ocel_log(Path::new("README.md")));
64    }
65}