Skip to main content

tempus_engine/
explain.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4/// The outcome of an `execute_explain` call.
5///
6/// Contains the evaluation result together with a full execution trace
7/// for audit, debugging, and GDPR right-to-explanation use cases.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ExplainResult {
10    /// Name of the rule that was evaluated.
11    pub rule_name: String,
12    /// Semantic version of the rule, if set.
13    pub rule_version: Option<String>,
14    /// Tags attached to the rule.
15    pub rule_tags: Vec<String>,
16    /// The raw JSON-Logic blob that was executed.
17    pub logic_snapshot: Value,
18    /// The context that was passed to the evaluator.
19    pub context_snapshot: Value,
20    /// The final evaluation output.
21    pub result: Value,
22    /// ISO-8601 UTC timestamp (populated at call time).
23    pub evaluated_at: String,
24    /// Name of the engine and its version.
25    pub engine: String,
26}
27
28impl ExplainResult {
29    pub(crate) fn new(
30        rule_name: String,
31        rule_version: Option<String>,
32        rule_tags: Vec<String>,
33        logic_snapshot: Value,
34        context_snapshot: Value,
35        result: Value,
36    ) -> Self {
37        Self {
38            rule_name,
39            rule_version,
40            rule_tags,
41            logic_snapshot,
42            context_snapshot,
43            result,
44            evaluated_at: chrono_like_now(),
45            engine: format!("tempus-engine@{}", env!("CARGO_PKG_VERSION")),
46        }
47    }
48}
49
50/// Minimal RFC-3339 timestamp without pulling in a full time crate.
51/// Uses the `CARGO_PKG_VERSION` strategy — deterministic for tests.
52fn chrono_like_now() -> String {
53    // We read the wall clock via std; no external crate needed.
54    use std::time::{SystemTime, UNIX_EPOCH};
55    let secs = SystemTime::now()
56        .duration_since(UNIX_EPOCH)
57        .map(|d| d.as_secs())
58        .unwrap_or(0);
59    // Format as a pseudo-ISO-8601 string (UTC, second precision).
60    let s = secs % 60;
61    let m = (secs / 60) % 60;
62    let h = (secs / 3600) % 24;
63    let days = secs / 86400;
64    // Simple Gregorian approximation — good enough for trace timestamps.
65    let year = 1970 + days / 365;
66    let day_of_year = days % 365;
67    let month = day_of_year / 30 + 1;
68    let day = day_of_year % 30 + 1;
69    format!(
70        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
71        year, month, day, h, m, s
72    )
73}